【Linux】——进程概念(万字解读)

一  冯诺依曼体系结构

在此之前,我们先要理解我们计算机的冯诺依曼体系结构,因为是进程的基础

我们所有的操作其实都是基于这样一个模型,比如你在qq上,和别人发送消息,这个消息肯定是先通过输入设备进行输入,输入到存储器(这里只是单指内存),然后通过控制器和运算器的控制和计算,把消息发送到输出设备(这里可以是网卡和显示器)

那我们这些操作肯定需要有人控制吧,没有人控制,单凭一个cpu能去接受这么多信息?别完了,cpu的运行速度是很快的,如果这些事情都要他去处理,那就没有那么高效了。

所有我们引出了操作系统这个概念

二  操作系统

概念


任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:


内核(进程管理,内存管理,文件管理,驱动管理)
其他程序(例如函数库,shell程序等等)


设计OS的目的


与硬件交互,管理所有的软硬件资源
为用户程序(应用程序)提供一个良好的执行环境


定位


在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件

由于我们的文件和进程错综复杂,都会占据内存,同时也有谁先进行谁后进行的顺序,所以这里我们需要一个管理者去管理好这些资源,不然我们的电脑用几下就会导致死机,所以我们开机第一个启动的软件就是操作系统这个系统软件

管理

那我们操作系统是用什么方式去管理这些资源的呢?? 

这张图表示了我们在进行操作的轮廓图,操作系统在管理底层的硬件的时候,并不能直接去访问,而是应该经过驱动程序间接访问,所以操作系统在管理硬件资源的时候,只是拿到了硬件的数据,并没有直接见到了底层的硬件,既然我们拿到了数据,我们就可以通过数据去判断这个硬件程序是否需要关闭,当然这些数据太多了,而且有些属于一个类型,有些不是,那既然这样,我们就可以通过描述这个硬件的基本属性,同时加上他的数据,这样归纳起来就可以统一管理了,但是这么多硬件,怎么去找呢??创建一个双链表,一个红黑树?把他们管理起来?,可行!!

所以经过以上步骤就形成了一个先描述,再组织的操作

之前说了,只能从上到下访问,那我们电脑上的软件有各种功能,这些功能直接或者间接的操作了我们操作系统,那是不是这些软件可以肆意妄为??并不是!操作系统会防止用户去乱动操作系统里面的东西,但是它又得让用户去访问,于是就有了系统调用这个概念,由于系统调用的存在,使得我们可以在电脑上开发各种应用,使得我们的电脑功能性更全,这种选择性提供也是操作系统管理的一种手段

总结


计算机管理硬件,先用struct结构体描述,再用多种数据结构进行组织,这里为什么是struct呢?因为linux是用c语言写的。

系统调用和库函数概念


在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。
系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。

三  进程

有了上面的认识,我们就可以再来认识进程的概念,一个进程也就是我们所运行的程序,一个qq,一个微信,或者一个英雄联盟,他们都属于进程

所以一句话概括就是进程就是用来吃系统资源的——担当分配系统资源(CPU时间,内存)的实体。

进程也是受操作系统所管理的,那操作系统怎么管理?,和上面一样——先描述再组织

描述进程

对于进程我们得先描述。进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct

这个PCB里面就包含了进程的诸多信息,比如:优先级,上下文数据,指针之类的

在linux下的PCB——task_struct

✨标示符: 描述本进程的唯一标示符,用来区别其他进程。
✨状态: 任务状态,退出代码,退出信号等。
✨优先级: 相对于其他进程的优先级。
✨程序计数器: 程序中即将被执行的下一条指令的地址。
✨内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
✨上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
✨I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
✨记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
✨其他信息

组织进程

所有运行在系统里的进程都以task_struct链表的形式存在内核里。

查看进程

查看一个进程,查看的是进程的信息

我们可以通过 ls /proc这个指令查看所有进程的信息

如果我们现在运行一个程序

 如果我们想看他的信息,那么我们就使用一个看起来比较直观的命令(ps/top),因为/proc这个指令可能看起来并不直观,因为/proc 目录下的文件和目录提供的是系统的底层信息,它们的内容通常是以文本形式展示的,但格式和结构可能相对复杂。

这里我们使用ps查看,我们先运行这个程序

这里可能查看到三个进程,但是只有第一个是我们要查的,其他两个是因为我们输入的指令也是一个进程,所以也把他们的进程信息输出了

 这就是进程的pid也就是进程的标识符,linux也是通过这个去区分进程的不同的

除了这种方法可以获取进程的pid,我们还可以通用系统调用去查看;

通过系统调用获取进程标示符

#include <stdio.h>
#include <unistd.h>
#include<sys/types.h>
int main()
{printf("pid:%d\n",getpid());return 0;
}

通过这段代码就可以获取到该进程的pid

运行这段程序就可以获得该进程的pid,但是我们注意到其实他还有一个表示符 

这一段是他们的父进程的pid,也就是ppid,每个子进程都有一个父进程,当然,最大的父亲就是我们的bash

我们也可以通过系统调用把他的ppid也打印出来

#include <stdio.h>
#include <unistd.h>
#include<sys/types.h>
int main()
{printf("pid:%d  ppid:%d\n",getpid(),getppid());return 0;
}

通过系统调用创建进程-fork初识

对于fork也是一个系统调用,他的作用就是创建一个子进程

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{int ret = fork();if(ret < 0){perror("fork");return 1;}else if(ret == 0){ //childprintf("I am child : %d!, ret: %d\n", getpid(), ret);}else{ //fatherprintf("I am father : %d!, ret: %d\n", getpid(), ret);}sleep(1);return 0;
}

对于上面的代码来说,因为fork创建子进程会有一个返回值,如果创建成功对于子进程会返回0,对于父进程会返回子进程的pid,如果失败则是小于0的值

所以根据返回值的不同我们就可以去执行两块程序

fork之后的代码子进程和父进程是共享的

那现在可能会困惑为什么一个变量会有两个返回值,就算是两个进程,但是也就一个变量,一个变量的地址是固定,那么就不可能存在一个变量有两个值的情况

所以我们得先了解虚拟地址空间这个概念

文字说明:我们创建进程的时候会有操作系统先给进程描述一个PCB在linux里也叫tast_struct

这里面包含了进程的信息,既然是信息那么就会包括数据的存放地址,所以这里这就有一个虚拟地址的指针,指向这块内存,这块内存就是我们之前认识到的什么栈和堆,静态区,数据段...这类的空间

这个空间并不是实际数据存放的空间,它的出现只是为了方便管理,形成统一性,那我们怎么去找到物理内存呢(也就是实际的地址),这里采取的方式就算通过页表去映射,把虚拟内存和物理内存的地址一 一 对应起来,如果我们创建子进程,操作系统会创建子进程的PCB,然后会把父进程的虚拟内存的表继承下来,这样,子进程和父进程虚拟内存相同,但是有两份,因为子进程和父进程的代码相同(因为是继承下来的),又因为进程之间是相互独立互不干扰的,所以当子进程中的变量改变的时候,会产生临时拷贝,也就是对于发生改变的数据,会在物理内存中产生一个新的位置,然后再通过页表的映射,改变对应的虚拟内存的映射,这样就完成了两个变量有不同的值,虽然他们的虚拟地址是相同的,但是物理地址却是不同的

图片说明:

这里的进程空间其实是内存的一种数据结构 

注意:这里不要理解为是一个存在的空间,只是一种数据结构而已

进程地址空间就类似于一把尺子,尺子的刻度由0x00000000到0xffffffff,尺子按照刻度被划分为各个区域,例如代码区、堆区、栈区等。而在结构体mm_struct当中,便记录了各个边界刻度,例如代码区的开始刻度与结束刻度,如下图所示:

 

 

进程状态

运行状态

进程 PCB 被调度到 CPU 运行队列中且已被分配 CPU 资源,就叫做 ------ 运行状态

每个进程都有自己的PCB+自己的数据构成,当我们存在多个进程的时候,我们就需要用特定的数据结构把他们组织起来,比如链表,平衡树之类的,这样我们就可以很轻松的通过一个进程找到其他的进程

所以需要运行的进程,会把他们的PCB放在CPU的运行队列中,然后通过相应的数据结构组织起来,为了更加形象下面给出一张图片说明

这张图就是用的双向链表把他们组织起来的,可能还会用到其他的数据结构,因为操作系统的组织比这个可复杂太多了 

如果这个进程需要执行了,就把代码和数据放进CPU中就行了

那如果这个进程运行了很久呢??那不是一直等他完成?

  • 不是,每一个进程都有一个叫做:时间片的概念! 其时间大概是在10 ms左右。所以并不是一个进程一直在执行,而是这多个进程在一个时间段内所有代码都会被执行 —— 这就叫做【并发执行】 
  • 所以呢这就一定会存在大量的进程 被CPU放上去、拿下来的动作 —— 这就叫做【进程切换】 

这个速度很快,我们感受不到是很正常的

阻塞状态

  • 阻塞 就是 进程 因等待某种条件就绪,而导致的一种不推进状态(比如等待 键盘输入)。
  • 通俗的来说 阻塞 就是 进程卡住了原因就是缺少资源

注意:这里要和上面运行状态的进程切换区分开,上面的是在运行,不缺条件,这里是因为条件的缺失而去等待资源

那么进程需要什么资源呢?

  • 比如 磁盘网卡显卡 等各种外设
  • 假设你现在想在 steam 上下载游戏,当你点击下载按钮后提示磁盘空间不足,此时是无法运行 steam下载 这个进程的,因为此 进程 需要等待足够大的 磁盘资源
  • 此时我们就称此 进程 为 阻塞 状态

和上面的注意关联起来

总结:进程阻塞就是不被调度

  • 此时 PCB(task_struct) 就会被设置为 阻塞状态,并链入等待的资源提供的等待队列
  • 没错,这里的等待队列 类似于 CPU 运行队列 

 挂起状态

  • 当 CPU 资源紧张时,将 进程的数据和代码 交换至 磁盘 中挂起,此时内存中只有 PCB
     
  • 挂起 可以看作一种特殊的 -- 阻塞状态

可能你买苹果电脑的时候就会考虑内存够不够用的问题,也许你从网上看了很多,有说够用,有说不够用,所以这里很多人提到的词就swap,这里的swap就是当我们的内存不够用的时候,会把代码和数据放进磁盘里面,等到需要用的时候,再拿出来,这个操作就算swap,这个是有成本的,所以多次的swap可能会导致电脑性能下降,发热等等

  • 当计算机资源比较吃紧时,操作系统一定要确保自身不会因为资源的紧张而崩溃,所以就会将一些等待资源(阻塞)的进程的代码和数据交换到磁盘的 swap分区 中,这个过程称为唤出
  • 当需要调度此进程时,就会将磁盘的 swap分区 中保存的内容换回到内存中,这个过程称为唤入

注意:交换的是进程的代码和数据,不是PCB!!如果PCB被交换出内存了,那操作系统如何管理呢?

所以当某个进程的代码和数据不在内存中,而被换出到磁盘上时,该进程就为挂起状态。

linux系统下的7种进程状态

运行状态

我们先看一段代码

   #include <stdio.h>#include <unistd.h>int main(void){while(1);                                                                {printf("Hello process, pid: %d\n",getpid());sleep(1);}return 0;}

从图中可以看到状态是S+,实际上却是R+才表示是运行状态,哪为什么 会这样呢?

再看一段代码就知道了

   #include <stdio.h>#include <unistd.h>int main(void){while(1);                                                                {//printf("Hello process, pid: %d\n",getpid());//sleep(1);}return 0;}

 

这样就变成了运行状态

  • 原因就在于 printf 打印语句它是属于 IO流 的一种,第一次因为是循环的缘故,它一直在等IO设备就绪,所以其进程状态就一直为 S+,对应的即是在操作系统中的阻塞状态】;但是当我们去掉 printf 这种IO流之后呢,它就是在纯纯运行,没有IO,那也就变成了 R 状态
  • 这里的 R+ 代表的就是这个进程是在前台运行的,所以我们在输入任何指令后不会对其造成 任何的影响

  • 那若是我们不以正常的方式去启动这个进程的话,其进程的状态就会不一样了,可以看到我在 ./mytest 的后面加上了一个 &;那么其状态变成了 R,此代表的意思就是这个进程它是运行在了【后台】的

  •  不过呢,R状态并不代表这个进程就在运行,而代表其在运行队列中排队而已.

所以总的一句话来说就是

 "+"代表是前台运行,无"+"代表后台运行,后台运行时可在命令行继续输入指令并执行,但无法用ctrl+c结束,需要用kill -9 pid杀死。想要后台运行某个程序就在后面加"&",如:./test & 

浅度睡眠状态

这个状态上面也提到过就上S+这个状态,等待io设备的输入

   #include <stdio.h>#include <unistd.h>int main(){int a = 0;printf("Enter# ");scanf("%d", &a);printf("echo : %d\n", a);return 0;                                                                         } 

  • 将该进程运行起来我们可以看到其是出于 S+ 的状态,因为【shell】此时正在等待用户的输入,这个就对应到了我们上面所讲到的 阻塞状态
深度睡眠状态

除了【浅度睡眠】之外呢,还有一种叫做【深度睡眠】,它们俩呢,都是 阻塞状态

  • 对于浅度睡眠来说,之所以称为 “浅度”,是有原因的:也就是处于这种状态的进程容易被唤醒。例如说我们在上面所讲到的这个处于阻塞状态的进程,我们使用 kill -9 PID 向这个进程发送【9号信号】,那么这个进程就被杀死了,你也可以认为被唤醒了

一个进程处于深度睡眠状态(disk sleep),表示该进程不会被杀掉,即便是操作系统也不行,只有该进程自动唤醒才可以恢复。该状态有时候也叫不可中断睡眠状态(uninterruptible sleep),处于这个状态的进程通常会等待IO的结束。

例如,某一进程要求对磁盘进行写入操作,那么在磁盘进行写入期间,该进程就处于深度睡眠状态,是不会被杀掉的,因为该进程需要等待磁盘的回复(是否写入成功)以做出相应的应答。(磁盘休眠状态)

  • 如果在这个过程中,操作系统能够杀死该进程,那么就有可能丢失数据。

所以也就是因为这样,操作系统不敢去打扰这个进程

暂停状态
  • 首先我们要通过下面这句命令来查看一下对应的进程信号

kill - l

我们使用的就是18,19信号

  • 暂停进程

kill -19 PID

  • 启动进程

kill -18 PID

那可能就有疑问了,这个暂停状态和上面的睡眠状态的区别是什么呢??

  1. stopped状态 进程 完全暂停了, 其不会再接收任何信号了
  2. 一个进程通过 stopped 状态可以控制另一个
  3. S 和 D 一定是在等待某种资源,而 T状态 可能在等待某种资源,也可能被其他进程控制

死亡状态

死亡状态只是一个返回状态,当一个进程的退出信息被读取后,该进程所申请的资源就会立即被释放,该进程也就不存在了,所以你不会在任务列表当中看到死亡状态(dead)。

  • 第一种方法就是向这个进程发送9号信号,就可以杀掉这个进程
  • 第二种方法就是通过这个进程的名称来杀掉它
kill -9 PID
killall 进程名
僵尸状态

前面说到,一个进程若是正在等待其退出信息被读取,那么我们称该进程处于僵尸状态。而处于僵尸状态的进程,我们就称之为僵尸进程

之所以有这个状态就是因为,我门需要对退出的进程进行资源的查看,不能直接就退出了,可能该进程里有我们需要的信息或者资源

例如,对于以下代码,fork函数创建的子进程在打印5次信息后会退出,而父进程会一直打印信息。也就是说,子进程退出了,父进程还在运行,但父进程没有读取子进程的退出信息,那么此时子进程就进入了僵尸状态。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{printf("I am running...\n");pid_t id = fork();if(id == 0){ //childint count = 5;while(count){printf("I am child...PID:%d, PPID:%d, count:%d\n", getpid(), getppid(), count);sleep(1);count--;}printf("child quit...\n");exit(1);}else if(id > 0){ //fatherwhile(1){printf("I am father...PID:%d, PPID:%d\n", getpid(), getppid());sleep(1);}}else{ //fork error}return 0;
} 

运行该代码后,我们可以通过以下监控脚本,每隔一秒对该进程的信息进行检测。

while :; do ps axj | head -1 && ps axj | grep proc | grep -v grep;echo "######################";sleep 1;done

僵尸进程的危害
1.僵尸进程的退出状态必须一直维持下去,因为它要告诉其父进程相应的退出信息。可是父进程一直不读取,那么子进程也就一直处于僵尸状态。
2.僵尸进程的退出信息被保存在task_struct(PCB)中,僵尸状态一直不退出,那么PCB就一直需要进行维护。
3.若是一个父进程创建了很多子进程,但都不进行回收,那么就会造成资源浪费,因为数据结构对象本身就要占用内存。
4.僵尸进程申请的资源无法进行回收,那么僵尸进程越多,实际可用的资源就越少,也就是说,僵尸进程会导致内存泄漏。

进程一般退出的时候,一般其不会立即彻底退出。如果父进程没有主动回收子进程信息,子进程会一直让自己处于Z状态,这也是为了方便后续父进程读取子进程的相关退出结果。

孤儿状态

在Linux当中的进程关系大多数是父子关系,若子进程先退出而父进程没有对子进程的退出信息进行读取,那么我们称该进程为僵尸进程但若是父进程先退出,那么将来子进程进入僵尸状态时就没有父进程对其进行处理,此时该子进程就称之为孤儿进程。
若是一直不处理孤儿进程的退出信息,那么孤儿进程就会一直占用资源,此时就会造成内存泄漏。因此,当出现孤儿进程的时候,孤儿进程会被1号init进程领养,此后当孤儿进程进入僵尸状态时就由int进程进行处理回收。

例如,对于以下代码,fork函数创建的子进程会一直打印信息,而父进程在打印5次信息后会退出,此时该子进程就变成了孤儿进程。

同时我们一直打印子进程的PID与PPID,这样便于观察现象

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{printf("I am running...\n");pid_t id = fork();if(id == 0){ //childint count = 5;while(1){printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid(), count);sleep(1);}}else if(id > 0){ //fatherint count = 5;while(count){printf("I am father...PID:%d, PPID:%d, count:%d\n", getpid(), getppid(), count);sleep(1);count--;}printf("father quit...\n");exit(0);}else{ //fork error}return 0;
} 


四  进程优先级

基本概念

什么是优先级?
优先级实际上就是获取某种资源的先后顺序,而进程优先级实际上就是进程获取CPU资源分配的先后顺序,就是指进程的优先权(priority),优先权高的进程有优先执行的权力。

优先级存在的原因?
优先级存在的主要原因就是资源是有限的,而存在进程优先级的主要原因就是CPU资源是有限的,一个CPU一次只能跑一个进程,而进程是可以有多个的,所以需要存在进程优先级,来确定进程获取CPU资源的先后顺序。

查看进程优先级信息

 ps -al

列出的信息当中有几个重要的信息,如下:

  • UID:代表执行者的身份。
  • PID:代表这个进程的代号。
  • PPID:代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号。
  • PRI:代表这个进程可被执行的优先级,其值越小越早被执行。
  • NI:代表这个进程的nice值。

PRI与NI
1.PRI代表进程的优先级(priority),通俗点说就是进程被CPU执行的先后顺序,该值越小进程的优先级别越高。
2.NI代表的是nice值,其表示进程可被执行的优先级的修正数值。
3.PRI值越小越快被执行,当加入nice值后,将会使得PRI变为:PRI(new) = PRI(old) + NI。
4.若NI值为负值,那么该进程的PRI将变小,即其优先级会变高。
5.调整进程优先级,在Linux下,就是调整进程的nice值。
6.NI的取值范围是-20至19,一共40个级别。

注意: 在Linux操作系统当中,PRI(old)默认为80,即PRI = 80 + NI。

通过top命令更改进程的nice值

top命令就相当于Windows操作系统中的任务管理器,它能够动态实时的显示系统当中进程的资源占用情况。

使用top命令后按“r”键,会要求你输入待调整nice值的进程的PID。

 输入进程PID并回车后,会要求你输入调整后的nice值。

输入nice值后按“q”即可退出,如果我们这里输入的nice值为10,那么此时我们再用ps命令查看进程的优先级信息,即可发现进程的NI变成了10,PRI变成了90(80+NI)。

注意: 若是想将NI值调为负值,也就是将进程的优先级调高,需要使用sudo命令提升权限。 

通过renice命令更改进程的nice值

使用renice命令,后面跟上更改后的nice值和进程的PID即可。

四个重要概念
竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便有了优先级。

独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰。

并行: 多个进程在多个CPU下分别同时进行运行,这称之为并行。

并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。

五  环境变量

基本概念

环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。

例如,我们编写的C/C++代码,在各个目标文件进行链接的时候,从来不知道我们所链接的动静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。

常见环境变量

  • PATH: 指定命令的搜索路径。
  • HOME: 指定用户的主工作目录(即用户登录到Linux系统中的默认所处目录)。
  • SHELL: 当前Shell,它的值通常是/bin/bash。

查看环境变量的方法

我们可以通过echo命令来查看环境变量,方式如下:

echo $环境变量名称 

测试PATH

大家有没有想过这样一个问题:为什么执行ls命令的时候不用带./就可以执行,而我们自己生成的可执行程序必须要在前面带上./才可以执行?

要执行一个可执行程序必须要先找到它在哪里,既然不带./就可以执行ls命令,说明系统能够通过ls名称找到ls的位置,而系统是无法找到我们自己的可执行程序的,所以我们必须带上./,以此告诉系统该可执行程序位于当前目录下。

 这和linux一切皆文件就是一个道理了,指令也是一个可执行程序,我们需要找到它才能执行它

而系统就是通过环境变量PATH来找到ls命令的,查看环境变量PATH我们可以看到如下内容:

 上图就是默认的搜索路径

可以看到环境变量PATH当中有多条路径,这些路径由冒号隔开,当你使用ls命令时,系统就会查看环境变量PATH,然后默认从左到右依次在各个路径当中进行查找。
而ls命令实际就位于PATH当中的某一个路径下,所以就算ls命令不带路径执行,系统也是能够找到的。

那既然是这样,我们就可以把我们写的程序的路径加上去,这样,默认搜索也可以搜索到我们的程序了

方式一:将可执行程序拷贝到环境变量PATH的某一路径下。
 

 sudo cp proc /usr/bin

因为涉及到权限,所以需要sudo命令

方式二:将可执行程序所在的目录导入到环境变量PATH当中。

将可执行程序所在的目录导入到环境变量PATH当中,这样一来,没有指定路径时系统就会来到该目录下进行查找了。

 export PATH=$PATH:/home/cl/dirforproc/ENV

测试HOME

任何一个用户在运行系统登录时都有自己的主工作目录(家目录),环境变量HOME当中即保存的该用户的主工作目录。

 

测试SHELL

我们在Linux操作系统当中所敲的各种命令,实际上需要由命令行解释器进行解释,而在Linux当中有许多种命令行解释器(例如bash、sh),我们可以通过查看环境变量SHELL来知道自己当前所用的命令行解释器的种类

而该命令行解释器实际上是系统当中的一条命令,当这个命令运行起来变成进程后就可以为我们进行命令行解释。

 和环境变量相关的命令

1、echo:显示某个环境变量的值。

2、export:设置一个新的环境变量。

3、env:显示所有的环境变量。

4、set:显示本地定义的shell变量和环境变量。

 注意:more 命令类似 cat ,不过会以一页一页的形式显示,更方便使用者逐页阅读,而最基本的指令就是按空白键(space)就往下一页显示,按 b 键就会往回(back)一页显示,而且还有搜寻字串的功能(与 vi 相似)

5、unset:清除环境变量。 

环境变量的组织方式

每个程序都会收到一张环境变量表,环境表是一个字符指针数组,每个指针指向一个以’\0’结尾的环境字符串,最后一个字符指针为空。

通过代码获取环境变量

main函数其实有三个参数,只是我们平时基本不用它们,所以一般情况下都没有写出来。
我们可以在Windows下的编译器进行验证,当我们调试代码的时候,若是一直使用逐步调试,那么最终会来到调用main函数的地方。

我们可以在linux环境下编写以下代码

现在我们来说说main函数的前两个参数,main函数的第二个参数是一个字符指针数组,数组当中的第一个字符指针存储的是可执行程序的位置,其余字符指针存储的是所给的若干选项,最后一个字符指针为空,而main函数的第一个参数代表的就是字符指针数组当中的有效元素个数

 

 main函数的第三个参数接收的实际上就是环境变量表,我们可以通过main函数的第三个参数来获取系统的环境变量。

 除了使用main函数的第三个参数来获取环境变量以外,我们还可以通过第三方变量environ来获取。

这里的extern修饰的变量是指这个变量的的声明在其他文件中,并不在当前文件和头文件中

通过系统调用获取环境变量

除了通过main函数的第三个参数和第三方变量environ来获取环境变量外,我们还可以通过系统调用getenv函数来获取环境变量。
getenv函数可以根据所给环境变量名,在环境变量表当中进行搜索,并返回一个指向相应值的字符串指针。

例如,使用getenv函数获取环境变量PATH的值。

因为是字符串指针返回,所以打印的时候用%s打印就行了

 

六  Linux2.6内核进程调度队列

一个CPU拥有一个runqueue

如果有多个CPU就要考虑进程个数的父子均衡问题。

 

优先级

queue下标说明:

  • 普通优先级:100~139。
  • 实时优先级:0~99。

我们进程的都是普通的优先级,前面说到nice值的取值范围是-20~19,共40个级别,依次对应queue当中普通优先级的下标100~139。

注意: 实时优先级对应实时进程,实时进程是指先将一个进程执行完毕再执行下一个进程,现在基本不存在这种机器了,所以对于queue当中下标为0~99的元素我们不关心。

也就是说,这种方式的机器太慢了,因为需要等一个进程完毕以后才弄下一个进程,计算机的任务可是很重的,这种时间的损耗对于计算机来说是很大的

活动队列

时间片还没有结束的所有进程都按照优先级放在活动队列当中,其中nr_active代表总共有多少个运行状态的进程,而queue[140]数组当中的一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进程排队调度。

调度过程如下:

  1.从0下标开始遍历queue[140]。
  2.找到第一个非空队列,该队列必定为优先级最高的队列。
  3.拿到选中队列的第一个进程,开始运行,调度完成。
  4.接着拿到选中队列的第二个进程进行调度,直到选中进程队列当中的所有进程都被调度。
  5.继续向后遍历queue[140],寻找下一个非空队列。


注意:bitmap[5]:queue数组当中一共有140个元素,即140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5 × 32个比特位表示队列是否为空,这样一来便可以大大提高查找效率。这里相当于位图的结构,1代表有,0代表没有

总结: 在系统当中查找一个最合适调度的进程的时间复杂度是一个常数,不会随着进程增多而导致时间成本增加,我们称之为进程调度的O(1)算法。

过期队列


  过期队列和活动队列的结构相同。
  过期队列上放置的进程都是时间片耗尽的进程。
  当活动队列上的进程被处理完毕之后,对过期队列的进程进行时间片重新计算。


active指针和expired指针


  active指针永远指向活动队列。
  expired指针永远指向过期队列。


由于活动队列上时间片未到期的进程会越来越少,而过期队列上的进程数量会越来越多(新创建的进程都会被放到过期队列上),那么总会出现活动队列上的全部进程的时间片都到期的情况,这时将active指针和expired指针的内容交换,就相当于让过期队列变成活动队列,活动队列变成过期队列,就相当于又具有了一批新的活动进程,如此循环进行即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/877595.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一个注解轻松搞定审计日志服务!

【审计日志】&#xff0c;简单的说就是系统需要记录谁&#xff0c;在什么时间&#xff0c;对什么数据&#xff0c;做了什么样的更改&#xff01;任何一个 IT 系统&#xff0c;如果要过审&#xff0c;这项任务基本上也是必审项&#xff01; 实现【审计日志】这个需求&#xff0…

整体思想以及取模

前言&#xff1a;一开始由于失误&#xff0c;误以为分数相加取模不能&#xff0c;但是其实是可以取模的 这个题目如果按照一般方法&#xff0c;到达每个节点再进行概率统计&#xff0c;但是不知道为什么只过了百分之十五的测试集 题目地址 附上没过关的代码 #include<bits…

联想闪电鲨移动硬盘文件没删除却消失了怎么办

在日常的数据存储与管理中&#xff0c;移动硬盘作为便携且容量可观的存储设备&#xff0c;深受用户青睐。然而&#xff0c;当您发现联想闪电鲨移动硬盘中的文件突然消失&#xff0c;而您确信并未进行删除操作时&#xff0c;这无疑会令人感到困惑与焦虑。本文旨在为您揭开这一谜…

vue-element-admin——<keep-alive>不符合预期缓存的原因

vue-element-admin——<keep-alive>不符合预期缓存的原因 本文章&#xff0c;以现在中后台开发用的非常多的开源项目vue-element-admin为案例。首先&#xff0c;列出官方文档与缓存<keep-alive>相关的链接&#xff08;请认真阅读&#xff0c;出现缓存<keep-ali…

在IEDA里打包Maven项目记录

之前在网上查找到的方式发现比较繁琐&#xff0c;所以把自己的解决办法记录一下分享给兄弟们 <plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-shade-plugin</artifactId><version>3.2.4</vers…

集合及数据结构第一节————初识集合框架和数据结构

系列文章目录 集合及数据结构第一节————初识集合框架和数据结构 初始集合框架和数据结构 什么是集合框架&#xff1f;集合框架的重要性背后所涉及的数据结构以及算法数据结构的基本概念和术语逻辑结构和物理结构数据类型 文章目录 系列文章目录集合及数据结构第一节——…

文件的读写(标准库函数与系统调用函数),文件描述符的复制

文件描述符 为了解决内核对象在可访问性与安全”性之间的矛盾&#xff0c;Unix系统通过所谓的文件描述符&#xff0c;将位于内核空间中的文件表项间接地提供给运行于用户空间中的程序代码。为了便于管理在系统中运行的各个进程&#xff0c;内核会维护一张存有各进程信息的列表&…

C++智能指针配合STL模板类

代码 #include <unordered_map> #include <set> #include <memory> class ResID { public:using SP std::shared_ptr<ResID>;ResID() default;ResID(const std::string& id, const std::string& type): m_id(id), m_type(type){}public:~Re…

HoloLens 坐标系统 Coordinate systems

Hololens 和 Unity 空间坐标系统-CSDN博客文章浏览阅读79次。这意味着&#xff0c;在 X、Y 或 Z 轴上相距 2 个单位的物体&#xff0c;在混合现实中的渲染效果是相距 2 米。虽然左手坐标和右手坐标是最常见的系统&#xff0c;但 3D 软件中也会使用其他坐标系。例如&#xff0c;…

Veritas NBU8.3.0.2 安装部署环境备份实施介绍(篇一)

1、本次有两台NBU服务器&#xff0c;都是Windows Server 2016 Standard 2、一台作为Master Server角色&#xff0c;另外一台则作为Media Server角色 3、两台服务器均已加入域并关闭防火墙 4、后期备份客户端会有Linux和Windows系统&#xff0c;也会对接VCenter无代理备份虚拟…

xss靶场详解

目录 1.第一题 2.第二题 3.第三题 4.第四题 5.第五题 6.第六题 7.第七题 8.第八题 1.第一题 在源码script标签里边&#xff0c;innerhtml是用于访问或修改 HTML 元素内的 HTML 内容的&#xff0c;这里是访问spaghet这个元素的&#xff0c;并通过括号里面的东西搜索当前…

图搜v1.1.3 - 图库几千张图片再也不怕了

图搜是一款使用先进的自然语言处理技术的相册搜索助手&#xff0c;支持通过简单的描述来搜索相册中的图片&#xff0c;如“草丛中的猫”或“公园里的狗”。这款应用完全免费、无需内购&#xff0c;且完全离线使用&#xff0c;保证了用户数据的安全与私密性。它的高效索引系统确…

法律知识有奖竞答

关于开展线上法律知识竞赛活动的通知 根据字【2024】008号 《关于进一步推动普法贯穿于各行业各领域的倡议》的相关要求&#xff0c;特举办本次线上答题活动。 一、活动时间 2024年08月19日08:00至08月25日24:00 二、活动对象 公司全体成员 三、竞赛规则 1、每人每天拥有1次…

Jenkins UI与接口自动化测试持续集成实战

篇幅较长&#xff0c;要耐心阅读哦~ 基础知识简要回顾 持续集成、持续交付的好处与产生的必然性Jenkins服务的搭建方法Jenkins节点管理与用户权限Jenkins插件Jenkins父子多任务关联运行Jenkins报警机制 目录 SeleniumUI自动化测试持续集成演练接口自动化测试持续集成演练 …

vue+echarts:echarts地图页面跳转

在setOption的平级写点击事件&#xff0c;给chart添加click监听 getmapChart.setOption({......})//和数据对应即可 //点击区域实现页面跳转getmapChart.on(click,function(params){// console.log(params);switch(params.name){case "xxxx":top.location.href"…

JUC阻塞队列(四):DelayQueue

1、DelayQueue介绍 DelayQueue 是一个延迟队列&#xff0c;生产者写入一个数据&#xff0c;这个数据具有被直接消费的延迟时间&#xff0c; 让数据具有延迟的特性。 DelayQueue底层也是基于二叉堆来实现的&#xff0c;DelayQueue本就是基于PriorityBQueue 实现的。 二叉堆结构每…

Kubernetes Pod入门

在 Kubernetes 中&#xff0c;一个重要的概念就是 Pod(豆英)&#xff0c;Kubernetes 并不是直接管理容器他的最小管理单元叫做 Pod。的&#xff0c; 在 Docker 的应用中&#xff0c;我们把一个应用程序封装在一个镜像中&#xff0c;之后启动这个镜像并映射一个宿主机端…

x-cmd pkg | dua - 一个可以方便地了解给定目录的磁盘空间使用情况的工具

目录 简介用户首次快速实验指南技术特点竞品和相关项目进一步阅读 简介 dua 是 Disk Usage Analyzer 的简写&#xff0c;该工具可以快速查看给定目录的磁盘空间使用情况。 对于想要深入了解磁盘空间使用情况并有效管理存储的用户来说&#xff0c;Dua 是一个很有价值的工具。通…

华硕飞行堡垒键盘全部失灵【除电源键】

华硕飞行堡垒FX53VD键盘全部失灵【除电源键】 前言一、故障排查二、发现问题三、使用方法总结 前言 版本型号&#xff1a; 型号 ASUS FX53VD&#xff08;华硕-飞行堡垒&#xff09; 板号&#xff1a;GL553VD 故障情况描述&#xff1a; 键盘无法使用&#xff0c;键盘除开机键外…

轮式自主移动机器人的研究发展与ROS环境搭建

前言&#xff1a; 在轮式自主移动机器人的研发过程中&#xff0c;编程技术的重要性不言而喻。编程不仅涉及到机器人各种功能模块的协调运作&#xff0c;还决定了机器人能否实现复杂的行为决策和控制。首先&#xff0c;编程技术为机器人提供了“大脑”&#xff0c;从而使其能够感…