当前位置: 首页 > news >正文

【Linux】Linux进程概念(三)(进程状态,僵尸进程,孤儿进程,进程优先级)

目录

  • 前言
  • 一、操作系统上的进程状态
    • 1. 运行状态
    • 2. 阻塞状态
    • 3. 挂起状态
  • 二、Linux的进程状态
    • 1. R运行状态
    • 2. S睡眠状态
    • 3. D磁盘休眠状态
    • 4. T停止状态
  • 三、僵尸进程(Z僵尸状态)
  • 四、孤儿进程
    • 1号进程
  • 五、进程优先级
    • 如何修改优先级
  • 六、进程之间关系
    • 1. 进程切换
    • 2. 进程并发

前言

【Linux】Linux进程概念(二):fork、getpid、getppid详情请点击查看,今天继续介绍【Linux】Linux进程概念(三):进程状态、僵尸进程、孤儿进程、进程优先级

一、操作系统上的进程状态

在这里插入图片描述

  • 状态本质就是一个数字(整数),决定了进程接下来要做的工作
  • 所有操作系统在实现进程状态变化时都要符合上图中的理论(指导思想)

1. 运行状态

  1. 进程竞争的资源分为两类:CPU资源、外设资源

  2. 在内存中有多个进程,那么进程之间会通过双链表链接起来,形成管理所有进程的全局双链表,当一个进程只是在全局链表中并没有添加到调度队列里,这个状态就叫做就绪状态/新建状态
    在这里插入图片描述

  3. 一个CPU(运算器+控制器)对应一个调度队列,一个进程在运行状态,该进程的PCB必须处在 CPU的调度队列中(r表示运行状态)
    在这里插入图片描述

  4. 这个调度队列其实是大量的进程在排队,排队其实是进程的PCB去排队,PCB中会使用指针指向其对应进程的代码和数据,在排排队的进程并不是都在运行,运行状态指的是处于运行队列中的进程已经准备好可以被调度器随时进行调度去运行(在调度队列中等待CPU资源,本质是在竞争CPU资源

  5. 处于队列头上的PCB会被优先调度,当头部的进程PCB被调度到CPU上运行片刻后会重新被放到队列的尾上继续排队,这个把进程在CPU上运行一段时间再从CPU放下来的动作叫做进程切换

  6. 电脑上可以同时运行许多应用软件,例如浏览器,QQ、画图等,这些软件运行其实就是进程的运行,我们人的肉眼看到这些进程在同时运行,事实其实不是这样的,由于CPU的运行速度非常快,进程是一个一个从进程队列的头上被调度器调度到CPU上去运行的,运行片刻后就被拿下来放到运行队列的尾继续排队,即CPU同一时刻之只能执行一个进程,但是由于CPU的运行速度非常快,人肉眼是无法感知到CPU的进程切换的

2. 阻塞状态

  1. 一个简单的现象:当我们编写下面程序运行时,如果我们不在键盘中输入a的值,那么该进程就是阻塞状态,当我们输入值之后,进程就会进入运行状态,完成打印,进程结束
    在这里插入图片描述
  2. 操作系统是管理的软件(先描述,再组织)---- 软硬件资源进行管理,对硬件(键盘、磁盘等)进行管,也是先描述,再组织,形成硬件全局管理链表

在这里插入图片描述
3. 对于上面的代码,当操作系统没有获得键盘数据时,当前运行的进程会从调度队列中断链,然后将该进程task_struct链接到键盘对应的阻塞队列中,等待键盘输入。本质就是竞争外设资源(操作系统最先知道键盘是否输入数据,因为操作系统是软硬件资源的管理者),当键盘输入数据后,进程继续进入调度队列等待运行后续代码
在这里插入图片描述
4. 阻塞和运行本质:是更改task_struct中的状态属性(是运行还是阻塞等),同时链接到不同的队列中(调度队列、需要某个硬件资源则链接到该硬件的等待队列中,等待硬件资源)

3. 挂起状态

当内存空间严重不足时,操作系统会将阻塞进程和就绪进程的代码和数据swap out到磁盘上的swap分区来释放内存空间,当进程运行时再将代码和数据swap in内存中,将代码和数据swap out磁盘上的进程状态就是挂起状态
在这里插入图片描述

二、Linux的进程状态

//linux源代码状态如下
static const char * const task_state_array[] = {
"R (running)"     //运行
"S (sleeping)"    //睡眠
"D (disk sleep)"  //深度睡眠
"T (stopped)"     //停止
"t (tracing stop)"//追踪停止
"X (dead)"        //死亡
"Z (zombie)",     //僵尸
};

1. R运行状态

R运行状态(running):并不是进程一定在运行中,它表明进程正在CPU运行中运行,要么在CUP对应的运行(调度)队列里。下面我编写一个死循环判断程序,用于讲解进程的运行状态

#include <stdio.h>int main()
{while(1){}return 0;
}

这里是引用

  1. 通过指令ps ajx |head -1 && ps ajx |grep proc |grep -v grep查看程序相关信息
  2. 可以看到当前程序处于R+状态,运行状态,R表示当前状态是运行状态,+表示当前进程是否是后台运行,没有+表示程序是后台运行

2. S睡眠状态

S睡眠状态(sleeping):意味着进程在等待事件完成,同时也叫做浅度睡眠,即睡眠期间可以被唤醒,对应操作系统学科中的阻塞状态

  1. 下面运行我编写的一段在显示屏上打印字符串“aaabbb”的一段代码
#include <stdio.h>int main()
{while(1){printf("aaabbb\n");}return 0;
}

这里是引用

  1. 将程序运行起来后,我们的程序一直在显示屏上进行打印
  2. 使用ps指令查看进程信息,发现进程状态是S+,因此我们的打印程序正在处于S状态下
  3. 那么为什么进程会处于睡眠状态?由于我们的程序会在屏幕上疯狂打印,那么就代表我们的进程要不断访问外设,即访问显示屏,在显示屏上进行写入数据,我们知道CPU的运行速度很快,进程运行的写入很快,由于进程运行的太快了,程序写入运行的那一瞬间是运行状态,我们使用ps很难捕捉到,并且显示屏的显示速度没有那么快,即显示屏并不是时时都是处于可写入状态,即打开状态,那么我们的进程就要不断的等待外设,即显示屏,此时就对应操作系统学科的阻塞状态,进程要链入显示屏的阻塞队列进行等待,所以此时我们的进程处于休眠状态
  1. 现在我们运行另外一个程序,需要在键盘输入数据
#include <stdio.h>int main()
{int a;scanf("%d", &a);printf("a = %d\n". a);return 0;
}

在这里插入图片描述

  1. 运行该程序,程序会一直等待键盘输入数据
  2. 使用ps指令查看进程信息,发现进程状态是S+,因此我们的打印程序正在处于S状态下
  3. 我们的程序需要从键盘上读取数据,那么进程就是不断等待外设即键盘准备好,此时我们一直不在键盘敲下数据,此时进程就会在键盘设备的阻塞队列中等待,此时就对应操作系统学科的阻塞状态,所以此时我们的进程处于休眠状态

3. D磁盘休眠状态

D磁盘休眠状态:也叫不可中断睡眠状态,处于这个状态的进程通常会等待IO的结束,不响应任何请求,同时其也对应操作系统学科上的阻塞状态的一种特殊情况,处于深度睡眠的进程,不响应操作系统的任何请求,也无法被 kill -9杀死

  1. 当我们的进程要向磁盘中写入数据(假设磁盘的内存空间不足以写入该数据),此时进程等待磁盘数据写入数据是否成功,即等待磁盘的反馈,但是将数据写入磁盘的速度肯定比CPU速度慢很多,恰好此时操作系统的内存资源严重不足,需要节约出内存空间,看到了这个进程正在等待,那操作系统看到,心想:“内存资源都要严重不足了,你还在这里等待”,此时操作系统就将这个进程给杀死了,那么当磁盘尝试写入后,发现磁盘空间不足以写入该进程的数据,那么磁盘心想,这个数据太大了,反正我没有办法写入成功,但是我剩余的空间还可以写入其它较小的数据,那么我就将该进程对应的数据全部丢弃去完成其它 进程对应的较小的数据的写入,磁盘需要给该进程反馈写入情况,但是现在已经无法找到该进程了
  2. 进程被操作系统杀死了,那么进程中对应的代码和数据也一并被释放了,同时磁盘也写入数据失败,将数据直接丢弃了,那么此时就造成了数据丢失问题,那么小编分析一波,操作系统是为了节约内存,维持良好的环境而杀死的进程,这没毛病,进程在等待磁盘的回应是被杀的,进程是受害者,磁盘放不下数据,将数据直接丢失了去完成其它进程对应的较小的数据的写入了,这也没毛病,这是不是很难评判是谁的责任,所以对于这种数据丢失情况无法评判是谁的责任,那在用户使用系统的时候数据肯定不能够频繁的丢失,如果数据频繁的丢失,那造成的后果是不可想象的
  3. 所以针对这种情况,那么会将该进程设定为深度睡眠,即对于操作系统的不响应任何请求,那么进程不能被操作系统杀死,那么进程对应的代码和数据也不会丢失,只有接收到磁盘对应的回应才会响应苏醒,那么此时再遇到上面这种情况数据就不会丢失了

4. T停止状态

  1. T停止状态:可以通过发送SIGSTOP信号给进程来停止(T)进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。
  2. tracing stop(即追踪暂停)指调试过程中的进程暂停,例如使用gdb调试器的过程中就对应追踪暂停状态,使用gdb对程序进行调试,那么到断点处,进程会暂停下来,那么gdb就可以控制进程,此时gdb就可以对当前程序的变量等信息进行查看,此时进程所处的状态就是tracing stop追踪暂停状态

在这里插入图片描述

  1. 使用ps指令查看进程状态是S+休眠状态(前台运行),这是正常的,因为进程大部分时间都在等待设备,即等待显示屏准备好,具体查看S随眠状态内容
  2. 使用kill -19 进程标识符,将进程暂停下来,观察程序运行情况,进程就被stopped暂停下来了,此时使用ps查看进程的状态就为对应的T停止状态
  3. 使用kill -18 进程标识符,让进程继续运行,观察程序运行情况,进程就被继续运行了,此时在使用ps查看进程的状态就为对应的S休眠状态(后台运行)
  4. 后台进程无法通过ctrl + c 来让进程停止,只能使用kill -9 标识符杀死进程

三、僵尸进程(Z僵尸状态)

僵尸进程(zombies):子进程退出的时候,如果父进程没有主动回收子进程信息,那么子进程会让自己一直处于Z僵尸状态,即对应子进程相关资源尤其是task_struct结构体不能释放

  1 #include <stdio.h>                                                                                                           2 #include <sys/types.h>                                                                                                       3 #include <unistd.h>                                                                                                          4 #include <stdlib.h>                                                                                                          5                                                                                                                              6 int main()                                                                                                                   7 {                                                                                                                            8     printf("I am a proc ,PID: %d, PPID: %d\n",getpid(),getppid());                                                           9   sleep(3);                                                                                                                  10                                                                                                                              11   pid_t id = fork();                                                                                                         12   if(id > 0)                                                                                                                 13   {                                                                                                                          14     while(1)                                                                                                                 15     {                                                                                                                        16       printf("我是父进程 ,PID: %d, PPID: %d\n",getpid(),getppid());                                                          17       sleep(2);                                                                                                              18     }                                                                                                                        19   }                                                                                                                          20   else                                                                                                                       21   {                                                                                                                          22     int cnt =3;                                                                                                              23     while(cnt--)                                                                                                             24     {                                                                                                                        25       printf("我是子进程 , PID:%d, PPID: %d\n",getpid(),getppid());                                                          26       sleep(2);                                                                                                              27     }                                                                                                                        28   }                                                                                                                          29   return 0;                                                                                                                                                                     30 }     

在这里插入图片描述

  1. 当前进程调用fork创建子进程,当前进程成为父进程,子进程和父进程通过if分流,进入不同的执行流去执行不同的代码块,使用ps查看此时子进程和父进程都是处于S睡眠状态
  2. 子进程打印三次进程的PID和PPID后while循环停止,此时父进程还在继续任务,没有主动对子进程的信息进行回收,所以使用ps查看子进程此时就会维持Z僵尸状态
  3. 当我使用ctrl+c将整个进程强制终止之后,那么父进程会被对应的bash进行回收,那么子进程在父进程结束的时候,那么子进程就没有父进程了,瞬间子进程就被1号进程收养了,由于子进程也是处于退出状态还维护着进程信息,那么就会被1号进程回收,进行资源的释放,具体讲解请见下文孤儿进程

僵尸进程的危害

  • 子进程的僵尸状态必须被维持下去,因为父进程需要对子进程进程管理,如果父进程一直不去管理已经完成任务的子进程的相关信息和完成情况等,那么子进程的task_struct将继续在内存中,不会被释放,导致内存泄露

四、孤儿进程

  • 孤儿进程:父子进程中,父进程先退出,子进程的父进程会被操作系统1号进程继承,我们称该子进程被操作系统领养了,那么像这种父进程是1号进程的子进程我们称为孤儿进程
  • 此时这个子进程就变成了后台进程,普通的ctrl+c无法进行退出,只能使用kill -9 进程标识符,将子进程杀死
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>int main()
{printf("我是一个进程, PID:%d, PPID:%d\n",getpid(),getppid());sleep(3);pid_t id = fork();if(id > 0){int cnt=2;while(cnt--){printf("我是父进程 ,PID: %d, PPID: %d\n",getpid(),getppid());sleep(2);}}else{while(1){printf("我是子进程 ,PID: %d, PPID: %d\n",getpid(),getppid());sleep(2);}}return 0;
}

在这里插入图片描述

  1. 刚开始只有父进程,父进程状态是S+
  2. 执行了三次父进程打印操作后,父进程任务完成,在ps指令查看可以看到,没有父进程相关信息,父进程结束后被bash直接自动回收管理,资源释放
  3. 同时可以看到子进程从S+状态,变成S状态(后台),且其PPID从8535变成了1,操作系统收养了当前的子进程
  4. 退出子进程无法使用ctrl + c,只能kill -9 标识符

1号进程

  1. 使用top指令显示Linux进程信息,可以看到1号进程对应就是systemd
    在这里插入图片描述
  2. 使用ps指令查看systemd,其对应就是1号进程
    在这里插入图片描述

五、进程优先级

优先级:对于资源的访问,谁先谁后的问题。进程优先级,即CPU资源分配的先后顺序,优先级高的进程有优先执行访问权限的权利

  • 为什么要有优先级:因为CPU的资源是有限的,进程有多个,所以就进程之间就必然有竞争关系
    操作系统必须保证进程的良性竞争,所以就要确定优先级
  • 如果非良性竞争,那么就会导致某些进程长时间得不到CPU资源,该进程的代码长时间得不到推进——进程的饥饿问题
  • 通常来讲,非必要情况用户不需要调整优先级,进程的优先级的调整由调度器管控

如何修改优先级

1. 使用下面程序讲解如何修改优先级

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{while(1){printf("我是一个进程, PID = %d, PPID = %d\n",getpid(),getppid());sleep(3);}return 0;
}
  1. 运行程序后,进程不断在屏幕上打印,那么我们复制SSH渠道,在复制的SSH渠道中,使用ps -l查看当前运行进程,发现没有我们的程序proc,这是因为ps -l只会在当前SSH渠道中显示当前运行的进程,对于其它SSH渠道会默认不显示
  2. 我们使用ps -al即,显示全部当前运行进程,即可查看到我们的运行的程序proc 在这里插入图片描述
  • PID为进程标识符,PPID为父进程标识符
  • UID表示执行者身份
  • PRI表示这个进程被执行的优先级,这个PRI的值越小代表越早被执行
  • NI表示这个进程的nice值,是这个进程优先级的修正数据
  • PRI(new)=PRI(old)+nice

2. PRI和NI

  • PRI表示这个进程被执行的优先级,这个PRI的值越小代表越早被执行
  • NI表示这个进程的nice值,是这个进程优先级的修正数据
  • PRI(new)=PRI(old)+nice,注意这个旧的PRI一直不变(在LInux中一直是80),初始值PRI为80,NI为10,那么运算后,新PRI为80+10为90,那么下次计算时,PRI仍然使用初始值进行计算,比如下一次NI为-10,那么运算后,新PRI为80-10为70
  • 用户可以调正优先级,那么我可不可以将nice设置的非常小,然后大大的提高我们进程的优先级,进而达到控制优先级,让我们的进程可以一直被调度呢?
  • 答案是不可以,linux本身是有调整优先级的调度器的,调度器决定哪一个进程被优先调度,即由调度器确定进程的优先级,调度器使用调度算法极为合理的安排进程的优先级,提高系统性能,所以linux不想让用户过多的参与优先级的调整,所以限定我们在一定范围进行调正, 即nice值对应[-20,19],一共40个值,那么当PRI为80时,那么对应优先级就为[60,99]

3. 修改NI值

  • 使用 nice - n 新nice值 bash:调整当前bash的nice值,那么bash创建出来的子进程的nice值都会遵循bash的nice值
    在这里插入图片描述
  1. nice调整当前bash的nice值,那么bash创建出来的子进程的nice值都会遵循bash的nice值
  2. 可以看到调整后运行我们的程序proc,使用ps -al在复制的SSH渠道中查看程序其对应的NI(nice)值就为10,那么使用ps -al查看对应的PRI对应也为80+10,nice在程序运行前的SSH渠道中使用不需要sudo,可以直接提权
  • renice -n 要设置的nice值 -p 进程标识符:对一个正在运行的程序调整其nice值 在这里插入图片描述
  • nice -n 设置的nice值 可执行程序路径:这里以当前路径下的进程proc运行,以一个nice值运行一个进程 在这里插入图片描述

使用top

  • 将程序运行起来,在使用top指令,继续输入r,按下回车
  • 此时它会提示要你输入要调整进程对应的PID,将我们进程的PID输入30191,按下回车
  • 这时会提示输入该进程要调整的nice值,输入再回车
    在这里插入图片描述
  • 使用指令ps -al,可以看到进程30191NI值已经变成10了
    在这里插入图片描述

六、进程之间关系

  • 竞争性:系统进程数目众多,而CPU资源只有少量,甚至只有一个,所以进程之间是具有竞争属性的。为了更高效完成任务,合理竞争资源,于是便有了优先级
  • 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰
  • 并行:在同一时间内,多个进程在多个CPU下分别同时进行运行,这称之为并行
  • 并发:多个进程在一个CPU下,采用进程切换的方式,在一段时间内,让多个进程都得以推进,称为并发

1. 进程切换

CPU上下文切换:其实际含义是任务切换,或者CPU寄存器切换。当多任务内核决定运行另外的任务时,它保存正在运行任务的当前状态,也就是CPU寄存器中的全部内容。这些内容被保存在任务自己的堆栈中,⼊栈工作完成后就把下⼀个将要运行的任务的当前状况从该任务的栈中重新装入CPU寄存器,并开始下⼀个任务的运行
在这里插入图片描述

  • 进程在被切换的时候,要保存上下文,恢复上下文

2. 进程并发

这是Linux2.6内核中进程队列的数据结构

在这里插入图片描述

  1. 如果根据优先级来确定CPU执行进程顺序 在这里插入图片描述
  1. 根据上面的遍历queue数组来遍历,但是如果前面都没有进程,只有在最后一个下标139的时候才有进程,这样遍历效率不高,如何解决?Linux运行队列中还有一个bitmap[5]的数组
  • bitmap[5] 是long类型的数组
    在这里插入图片描述
  • nr_active:总共有多少个运行状态的进程,当队列中没有进程了,nr-active为0,那么就不需要在去遍历了
  1. 根据上面的介绍,CPU已经能很快的找到它最先调用运行的进程了,但是还有一个问题,如果这个时候一直源源不断的在queue中插入了优先级为60的进程,那么是先执行后面的进程,还是先执行新插入的60的进程呢?如果先执行新60的进程,那么后面的优先级的进程将迟迟得不到执行,这个问题如何解决?
  • Linux中有两个队列,一个是活跃队列,一个是过期队列,CPU调度活跃队列中的进程,新插入的进程插入到过期队列中,活跃队列中还没有调度完的进程也根据优先级重新插入到过期队列中
    在这里插入图片描述
  • 在运行队列中还有两个指针,* active和expired,分支指向活跃队列和过期队列,CPU调度的时候调用active就可以找到活跃队列中的进程来运行,分时运行后如果还需要调度就将其从活跃队列中断链,再插入到过期队列中,活跃队列遍历调度完,交换 active和*expired两指针,即可将活跃队列变为过期队列
    在这里插入图片描述
http://www.dtcms.com/a/525992.html

相关文章:

  • 【NestJS】Reflect Metadata 全解
  • 如何做搜索引擎网站淄博 做网站
  • 网站开发怎么开发公司简介宣传文案
  • 在REBa2Cu3O7−δ块状超导磁体优异性能的可靠外延生长中,缓冲层辅助生长架构的进展 项目文献
  • 小米商城的网站建站淄博外贸网站哪家好
  • Jetson orin agx配置ultralytics 使用docker或conda
  • 营子区住房和城乡建设局网站做东南亚跨境电商平台有哪些
  • lesson76:Vue.js 核心特性详解:事件处理、计算属性与侦听器
  • 不申请域名可以 做网站吗短期职业技能培训班
  • 北京专业网站开发公司wordpress+没有+sql
  • 如何做关于旅游的网站页面怎么查询个人名下营业执照
  • 兴国做网站招聘网站建设推广
  • 下载男女做爰免费网站租一个服务器要多少钱
  • 又在今年1024时
  • wordpress 最简单模板下载wordpress seo怎么写
  • wdcp 快速迁移网站佛山电商网站制作团队
  • CI/CD(二)—— Git 基础操作全攻略:从入门到实战
  • 建设一个网站需要那些技术做网站的职位
  • OK432(AbMole,M9414)
  • 三种 Badcase 精度验证方案详解与 hbm_infer 部署实录
  • 全网门户网站制做互联网行业现状分析
  • 网站安全和信息化建设衣服网站模板
  • 古腾堡布局的网站平面设计做画册用网站
  • 仿魔客吧网站模板cms客户管理系统
  • SpringAI + DeepSeek本地大模型应用开发-智能会话
  • 可视化响应式网站建设下载别人网站的asp
  • ps4gta5网站建设中网站建设硬件计划
  • Windows安装Git教程
  • 付网站建设服务费什么科目专门培训seo的网站
  • 专门做app的网站厦门网站制作哪里好薇