Linux进程状态

发布于:2024-05-05 ⋅ 阅读:(26) ⋅ 点赞:(0)

前言

上一期我们对进程的概念做了介绍并尝试理解了进程。介绍了PCB属性的pid等,这一期我们来介绍进程的状态!

本期内容介绍

Linux的进程状态

僵尸进程和孤儿进程

理解进程的运行、阻塞和挂起状态

简单理解进程切换

Linux的进程状态

为了理解正在运行的进程是什么意思,我们需要搞清楚进程的不同状态!我们上一期介绍过进程的状态是进程PCB中的一个属性,也就是一个变量!例如:int status;即不同的进程状态就是内部的status的值不同而已!Linux的进程状态有7种,下面我来介绍一下!

R(running):运行状态

S(sleeping):可中断休眠状态

D(disk sleep):不可中断休眠状态

T(stopped):让进程暂停等待被唤醒(暂停状态)

t(tracing stop):让进程暂停等待被唤醒(追踪暂停状态)

X(dead):进程死亡/终止状态

Z(zombie):僵尸进程状态

上面的几种状态中除了绿色大的两种外其他都可以演示!D状态不可中断睡眠和X终止状态无验证!前者和磁盘I/O有关,后者几乎无法看到!所以,下面这两种状态就不在验证了,会直接说结论,其他的会验证~!

R状态

R(running)运行状态。运行状态并不意味着进程一定在运行中,而是他要么在运行中,要么在运行队列里。

OK,验证一下:

我们这个代码就是很简的一个死循环,根据运行状态的介绍,当着个进程运行起来时后他就是R状态!OK,是不是呢?我们来看看:
还是以前的监控脚本:

while :; do ps ajx | head -1 && ps ajx | grep status | grep -v grep; sleep 1; done

这怎么和我们的介绍不一样?不应该是R状态吗?怎么是S?OK,我们把执行的语句注释掉再看看:

这样就变成了R了,这也就说明了刚刚引起R变为S的原因就是这两条语句!OK,我们下面的S状态解释!

S状态

S(sleeping)可中断休眠状态。进程等待“资源”的就绪/事件的完成

等待资源的就绪其实上面的例子已经可以说明了!上面的这代码一开始没有注释时在一直想显示器写" I am a process!"本来应该是R状态的但是是S状态,其实这是合理的!因为CPU执行printf的那一句是非常非常快的!根据上上期的冯诺依曼体系结构可以知道:CPU执行完的结果是写在内存的,然后再由内存在刷新到外设的!由于CPU执行的很快,内存向外设刷时需要等待显示器资源的就绪,而显示器资源相较于CPU很慢所以上述进程就是S状态!

D状态

D(disk sleep):不可中断休眠状态/磁盘睡眠。通常是等待磁盘I/O

操作系统中有很多的进程,这些进程都是要占内存空间的,而如果进程很多导致内存严重不足时操作系统有权利杀掉一些进程来释放空间!此时,如果一个进程正在向磁盘中写入1GB的数据了,由分诺依曼可知磁盘是外设,CPU的速度是很快的,当在往磁盘写入时当前的进程就进入了S状态,此时内存不足了,操作系统一看这有个休眠的把他杀掉释放一点空间吧!此时这个进程被干掉了,而过了一会,磁盘写入后的反馈结果刚给进程时,找不到进程了,而此时还有很多同类的进程在等待,所以此时磁盘就不在管找不到上一个进程的事情了,又去忙着服务其他进进程了,这就导致了那1GB的数据丢失了!假设这是1GB的银行转账记录呢???这损失可就大了!所以,为了防止这种状态的发生,与磁盘进行I/O的进程会设置为D状态,表示不可中断睡眠状态!

T状态

T(stopped):让进程暂停等待被唤醒(暂停状态)

T 状态通常是用户向进程发送信号(SIGSTOP等)让进程进入暂停状态的,需要被唤醒才可以继续执行!

这里的信号如果是第一次听可能有点陌生,但是我们以前是用过的(kill -9就是)!OK,我们来看看Linux中的信号有哪些?(可以通过:kill -l查看

我们以前用过的9号信号就是杀死进程的信号!OK,这里看到信号不知道你有没有觉得他和我们以前在C语言学的某一个东西很像?没错,就是宏!所以这里你可以给进程发字符,但也一般都发对应的数字~!OK,这里如何通过发信号让进程暂停呢?我们可以通过给进程发送19号信号(SIGSTOP)来让进程暂停:kill -19 进程pid

OK,我们现在不想让这个进程暂停了,想让他继续跑起来如何做呢?还是给他发信号:上面的19信号是SGISTOP即暂停,而18号信号是SGICONT即进程继续的信号!我们可以通过 kill -18 进程pid让当前暂停的进程继续跑起来:

OK,关于信号我们后面会在来专门谈的这里知道如何启动和暂停进程就好了!

t状态

t(tracing stop):让进程暂停等待被唤醒(追踪暂停状态)

通常是由调试器或追踪器等工具导致进程暂停的,此时进程等待被调试器继续执行等操作!

这个状态不管是VS还是在Linux下我们可是遇到过的。当我们在某一行打一个断点时,他会在运行到这一行时停在断点处,此时进程的状态就是t状态。OK,我们来看看:

我们可以看到当前的进程在第8行设置了断点后就进入了t状态,而gdb status那是gdb的进程!如果我能继续执行就会变成S/R状态(这里的代码就是上面演示R状态的那个代码是一直printf所以是S状态);

X状态

X(dead):进程死亡/终止状态

我们要知道的一点是:子进程是帮父进程执行某项任务的,最后得给父进程一个反馈的!所以,进程在退出/结束的时候会把自己的退出信息存储在自己的PCB(task_struct)中,等待父进程来读取,如果父进程读取了,此时就会释放掉当前子进程的资源!即子进程就是X状态了,但由于释放是瞬时的所以是极其难查到的!当前这个就介绍到这里,具体父进程如何查看子进程的退出信息,我们在后面的进程控制在介绍!

Z状态

Z(zombie):僵尸进程状态

这里和下面另一个孤儿一起对比介绍!(具体请看下面)

僵尸进程和孤儿进程

僵尸进程

子进程退出后父进程没有对他的退出信息做处理,此时进程的状态就是僵尸状态。此时的子进程就是僵尸进程!

我们上面刚介绍了:所有进程在退出的时候都要将自己的退出信息保存在自己的PCB(task_struct)中,等待父进程的读取,如果读取了就是X状态了,但是如果没有读取会一直存在就是Z状态,即僵尸状态了!OK,演示一下:

这个代码是让子进程跑5次然后结束掉,而父进程是一直执行的,并没有对子进程做处理,此时就是僵尸:

僵尸进程的危害

这里没有读取子进程的PCB,而进程= PCB + 它的代码和数据,子进程结束后它的代码和数据可能释放了,但是它的PCB会一直存在(因为子进程要告诉父进程你给我的任务,我做的咋样了),这样会有一个问题就是,PCB是不是也是一个个的结构体对象啊!他也是要占内存空间的,但是没有父进程读取它的PCB会一直存在,即会一直占着这块内存不释放,这就会导致内存泄漏内存资源浪费(父进程创建多个子进程不读取子进程的PCB)的问题!

这里你可能会想我们以前启动的所有进程可都没有考虑过僵尸进程和内存泄漏呀?原因是我们以前在命令行启动的所有进程它们的父进程是bash,bash会自动回收子进程的!

孤儿进程

孤儿进程是,父进进程先于子进程而结束。此时的子进程就是孤儿进程!

OK,演示一下:

让父进程跑5秒后退出而子进程一直跑,此时的子进程就是孤儿进程!

此时,要想终止孤儿进程,是无法用ctrl+C终止的!而是应该使用kill向该孤儿进程发9号信号使其结束,原因是ctrl+c是父进程给子进程发9号信号使其终止,但是这里根本就没有父进程了,就无法发9号信号了!只能通过kill -9 pid来使其结束掉!

这里现在的问题是,我们上面介绍了僵尸进程,当子进程结束时需要父进程来读取他的PCB使其结束掉,如果没有父进程读取当前子进程就会变成僵尸状态。这里父进程都先退了,那不等子进程退出的时候铁铁的变成僵尸进程??是的!这个问题操作系统也想到了,当孤儿进程退出时由1号进程(这里理解成系统即可)领养,读取他的PCB使其结束释放掉!避免其变成僵尸进程!所以这就是上面的ppid是1的原因

理解进程的运行、阻塞和挂起状态

进程的几种常见的状态

一个进程被创建后会经历不同的状态,通常被称为进程的生命周期!我们先来介绍一下操作系统的几个状态:

  1. 新建状态(New):当一个进程被创建时,它处于新建状态。在这种状态下,操作系统为进程分配必要的资源,初始化进程控制块(PCB)等。但进程还不能被CPU调度执行。

  2. 就绪状态(Ready):新建进程完成初始化后,进入就绪状态。在就绪状态下,进程已经准备好运行,只等待CPU的分配和调度。多个就绪状态的进程会在CPU的就绪队列排队等待被调度!

  3. 运行状态(Running):当进程获得CPU时间片,开始执行指令时,它进入运行状态。在运行状态下,进程正在使用CPU执行自己的任务。

  4. 阻塞状态(Blocked):如果进程需要等待某些事件的完成(如I/O操作、信号等),则进程可能会进入阻塞状态。在阻塞状态下,进程暂时停止运行,等待事件完成后才能继续执行。

  5. 终止状态(Terminated):当进程执行完毕或被终止时,会进入终止状态。在这种状态下,进程的资源被释放,PCB被销毁,但仍需要等待操作系统回收进程所使用的资源。

  6. 挂起状态(Suspended)通常指进程在执行过程中被临时中断或挂起,暂时停止执行挂起到磁盘。挂起状态通常分为两种:

    1. 就绪挂起(Ready Suspended):进程处于就绪状态时,如果被挂起,说明该进程虽然已经准备好运行,但由于某些原因(如等待某些资源)暂时无法执行。在这种情况下,进程需要等待条件满足后恢复执行。

    2. 阻塞挂起(Blocked Suspended):进程处于阻塞状态时,如果被挂起,表示进程正在等待某些事件发生,而且暂时无法继续执行。在进程进入挂起状态后,将暂停等待事件发生,一旦事件完成即可恢复执行。

这里我们主要要理解的是:运行状态、阻塞状态、挂起状态。下面我就来理解一下站三种状态!

理解运行状态

运行状态就是CPU调度CPU运行队列中的进程!(Linux中的R状态)

我们想一个进程会不会一直被调度也是就是某进程抢到CPU了会不会是一直执行完该进程才执行下一个进程呢?答案是:不会!因为CPU是基于时间片进程轮转调度的(为了公平)也就是一个进程会运行他抢到的时间片的时间,到时间了,他会被CPU上剥离下来继续到运行队列中排队,等待再次被调度!

操作系统会把在CPU运行队列中的进程(未被调度的)称为就绪状态,而被调度的叫做运行状态。结合上面Linux的状态,操作系统的运行状态+就绪状态就是Linux的R状态,因为在上面介绍了,R状态是,要么正在被CPU执行要么在运行队列里!

上面这是最简单的进程调度的方式,也是操作系统学科中通常介绍的。但是Linux并不是这样调度的!具体Linux是如何调度的我们后面再谈!

并发和并行

上面介绍了CPU是基于时间片轮转调度的,这样会保证各个进程都可以被调度,而不至于某些进程占CPU时间很长!像这种让多个进程以切换的方式进行调度,一个时间段内得以同时推进的调度方式叫做并发假设现在两个CPU,一个在调度A进程,一个在调度B进程。像这种任何时刻都有多个进程真正的同时被调度的方式叫做并行

理解阻塞状态

阻塞状态就是当前进程等待某种资源而导致暂停的状态, 需要资源满足后才可继续运行。例如:等待I/O(Linux中的S和D状态)

光说阻塞态我们难免会有一些生疏,但是我们100%是遇到过的,而且是经常遇到!比如,你在写C语言代码的时候scanf从键盘读取的时候,如果你不输入你的代码是不是一直暂停着?其实这就是当前的进程进入了阻塞状态!

OK,这就是一个阻塞态到运行态的过程!下面我就接着用这个栗子,来再谈一谈阻塞态!我们知道所有的调度都是CPU到运行队列中调度排队进程的PCB的。

现在的问题是,如果当前进程需要某些资源而不能继续执行时即进入了阻塞状态后,其PCB从CPU的运行队列中被拿下来后,会在哪里?其实当前被阻塞进程的PCB会被拿到相关资源/设备的运行/就绪队列里面等待资源,直到等待/所需的资源满足了,才会被拿到CPU的就绪队列中继续等待被调度(即被唤醒)!

也即是如下图:

总结:

1、阻塞和运行状态的变化,往往是伴随着PCB在不同的队列中,而不是其进程的代码和数据在队列中!

2、不只是CPU有等待或就绪队列,各种设备都有等待或就绪队列!

3、和我们上面介绍的Linux的几种状态联系起来,阻塞状态就是S/D状态,在等待某种资源/设备的就绪!

理解挂起状态

挂起状态是当操作系统需要释放资源或等待特定的事件发生,而将一些进程暂时唤出内存到磁盘的一种管理资源的机制或手段,这样可以更加合理或高效的使用资源!再次运行时将进程唤入到内存即可(也就是将挂起的进程PCB+代码和数据重新加载到内存即可)例如:当操作系统的内存特别吃紧的时候会将某些进程例如阻塞/休眠的进程换出到磁盘来释放系统资源给其他进程, 如果要运行这个挂起的进程了,只需要将他重新加载到内存让他的PCB去运行队列排队即可!

注意:

1、这里的将进程唤出到磁盘是将进程的PCB+代码和数据(即整个进程)唤出到磁盘!

2、这里的唤出到磁盘不是磁盘的随便一个位置,而是唤出到一个叫swap的特定空间中!

3、这其实就是一种典型的用效率换空间的方式!

简单理解进程切换

上面在介绍理解进程运行状态时,说过CPU是基于时间片而轮转调度的,也就是如果某个进程正在执行但是它的时间片用完了,他会被强制的拿下来去到等待队列中排队!此时问题就来了:被强制拿下来的进程再次执行时是从头开始执行吗??答案显然不是!那他如何知道他上次执行到哪里呢???答案是:进程在切换时会保存它的上下文(CPU内部所有寄存器的临时数据叫上下文)数据(方便再次恢复调度)

我们知道CPU附件是有很多寄存器的,例如:eac、esp、ebp等,我们进程再被CPU执行时其PCB的数据会被首先加载到各种寄存器,然后CPU读取寄存器的数据来执行相关操作!但是如果这个进程执行到一半时如果时间片完了,它此时进程要被拿下来时,它的这些寄存器的数据是不是要保存起来?原因是寄存器只有一套且它是临时的保存数据的,但是进程有很多个,每个进程都要访问这些寄存器(如果不拿走下次调度就找不到上次调度是寄存器的数据了),所以当这个进程要被拿下来时就要把这些寄存器的数据让当前进程带走,等下次被调度时,查看其上下文数据(寄存器的数据)即可继续执行!

总结

进程切换最重要的一件事情是:上下文数据的保护和恢复!

CPU内的寄存器只有一套,但是CPU寄存器的数据有多套!

寄存器不等于寄存器的内容!

OK,好兄弟,本期分享就到这里,我们下期再见!

结束语:你是春风,不应该畏惧山,而应该跨越山!