目录
一、普适操作系统的进程状态
1.什么是进程状态
2.三种重要的进程状态
(1)运行状态
(2)阻塞状态
(3)挂起状态
二、Linux源代码中的进程状态
三、Linux进程状态
1.运行状态
2.睡眠状态(S)
3.磁盘休眠状态(D)
4.暂停状态(T)
5.追踪暂停状态(t)
6.僵尸状态(Z)
7.死亡状态(X)
四、两种特殊进程
1.僵尸进程
2.孤儿进程
一、普适操作系统的进程状态
1.什么是进程状态
前面说过,一个程序被加载到内存变成进程后,操作系统为每一个进程创建PCB,利用进程的数据对其进行管理。而进程状态,在本质上就是PCB内部的一个整形变量,不同的整形值就对应不同的进程状态,不同的进程状态也指示了操作系统对进程的不同管理方式。
如果站在操作系统这个学科的角度,进程状态会有很多,最常见的有:运行、挂起、阻塞、就绪、等待、死亡等等。
2.三种重要的进程状态
这么多的状态中最重要三种是:运行状态、阻塞状态和挂起状态。
(1)运行状态
操作系统为了合理分配CPU以及各种硬件资源,保证各个进程的正常运行。操作系统会为CPU创建一个进程队列,也为每一个硬件都创建一个等待队列。
而某一个进程处于运行状态本质上就是操作系统将该进程对应的PCB放入CPU的运行队列中,然后再将PCB中维护进程状态的变量修改为相应的值。
PCB里面有进程的各种属性值,以及对应的代码的地址。所以CPU从运行队列中找到PCB取出数据后,可以根据数据得到进程的各种属性值和指令,然后执行相应代码。进程处于运行状态并不意味着该进程此时一定正在被运行,只要该进程处于CPU的运行队列中即可。
注意:CPU是处理数据的速度在纳秒级,运算速度非常快,所以只要进程处于CPU的运行队列中,我们就可以认为该进程正在被运行。
(2)阻塞状态
CPU处理数据的速度极快,是我们计算机中的各种硬件处理数据的万倍,而且一个磁盘或者一个网卡同时只能为一个进程服务,但是在计算机中需要使用这些硬件资源的进程会有很多。
如果在硬件为一个进程服务时,有其他运行中的进程也需要使用该硬件资源,操作系统就会将该进程的PCB放入硬件的等待队列中,进程会等待硬件来为自己提供服务。
由于多个进程需要访问某种硬件,进程PCB在硬件等待队列中等待硬件服务自己的状态就被称为阻塞状态。阻塞状态在本质上就是将进程的PCB从CPU的运行队列中,放入硬件的等待队列中,然后将PCB中维护进程状态的变量也会修改为相应的值。当该进程获得对应的硬件资源服务时,再将该进程放回CPU的运行队列中。
注意:并不是只有等待硬件资源的进程才处于阻塞状态,一个进程等待另一个进程就绪、一个进程等待软件资源就绪等也是阻塞状态。
(3)挂起状态
上面我们学习了阻塞状态,处于阻塞状态的进程由于需要等待某种资源,所以它对应的代码和数据在短期内不会被处理(这里的短期指的是对于操作系统而言)。但它们的数据仍储存在内存中,占用存储空间但是执行,相当于浪费了内存资源。而如果当前操作系统处于高IO(大量向内存输入和向外部设备输出数据)的情况下,可用的内存空间不足,操作系统就会选择性的将这些处于阻塞状态的进程对应的代码和数据转移到磁盘中,从而节省出内存空间。
这种由于内存空间不足,操作系统将在等待资源的进程对应的代码数据放到磁盘中以节省内存空间的状态就被称为挂起状态。挂起状态不会移动进程的PCB,只会移动进程对应的代码和数据。
挂起并不是释放进程,因为对应的PCB仍然在硬件的等待队列中,当该进程获得对应的资源服务以后,操作系统仍然可以将该进程对应的代码和数据从磁盘加载到内存中来继续运行,其本质是对内存数据的唤入唤出。
所以说,阻塞不一定挂起,但挂起一定阻塞。
二、Linux源代码中的进程状态
为了认识正在运行的进程,我们需要知道进程的不同状态。一个进程可以有几个状态,在Linux内核里就已经定义好了(进程有时候也叫做任务)。下面的状态在kernel源代码里定义:
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠
(interruptible sleep))。
D磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程,进程就被暂停了。这个被暂停的进程也可以通过发送 SIGCONT 信号让进程继续运行。
t追踪状态(tracing stop):“跟踪”指的是进程暂停下来,等待跟踪它的进程对它进行操作,比如在gdb中对被跟踪的进程添加一个断点。
Z僵尸状态(zombie):在一个进程执行完毕时,需要有另一个进程来回收该进程执行完毕后的属性数据然后,如果这个回收数据的进程没有来得及回收进程的数据,这个执行完毕的进程就处于僵尸状态。
X死亡状态(dead):这个状态只是一个返回状态,表明进程资源已经被回收,你不会在任务列表里看到这个状态。
三、Linux进程状态
1.运行状态
就像我们在银行柜台前排队一样,CPU也有一个运行队列,当一个进程的PCB被加载进这个运行队列时这个程序就处在运行状态,但是处于运行状态不代表它此时就一定在执行。
下面是一个不断加一的代码,将它编译并执行
查看进程时我们可以看到./test进程处于R+状态,也就是前台运行状态
2.睡眠状态(S)
我们在原来的代码上加上打印
运行并查看进程,此时进程的状态就是睡眠状态。由于硬件的读写速度和CPU差了万倍,所以在大部分时间里这个进程都在等待屏幕这个外设,进程也就处于睡眠状态。
3.磁盘休眠状态(D)
这是一个很特殊的状态,如果一个进程中,有大量的数据需要存放到磁盘上,这个进程的PCB同样会被操作系统放在磁盘的等待队列中。但是因为数据量非常大,磁盘的读写速度又很慢,所以PCB需要等待很长的时间才能等到磁盘的应答信号。
如果此时,内存中又加载进来很多进程时,内存空间就会很吃紧。此时操作系统为了维护计算机的正常运行,就会将一些长时间处于睡眠状态的进程直接杀掉。这种向磁盘传送大量数据的进程就很有可能会被杀掉。
当磁盘准备好为这个进程存储数据时,它就会向内存中原本的进程发送应答信号,但是此时这个进程已经被操作系统杀掉了,所以就无法接收信号也无法给出相应的指示。磁盘没有收到指示后只能将这些数据弃用,如此一来就会导致巨量的数据丢失。如果这些数据是在服务器上的客户数据,就会导致很大的损失。
所以操作系统设置了深度睡眠状态,相当于一块免死金牌,一旦D状态进程加载进来,我们用Ctrl+c和kill -9都不能杀死它,甚至操作系统内部也不能杀死它,也就只有断电可以杀死这个进程了。
我们如果不去做服务器开发就会很难接触到这个状态,演示的高IO可能回导致机器的崩溃,在这里也就不演示了。
4.暂停状态(T)
顾名思义,当进程运行到一半被暂停时就处于这个状态。
我们再次运行上面的那段代码,这个进程处于睡眠状态。我们用kill -19暂停它,再次观察就会发现进程状态变成了T,进程被暂停了。
使用kill -18指令继续执行后,暂停状态的进程又变成了睡眠状态,T又变成了S,进程继续运行
5.追踪暂停状态(t)
一个被暂停的进程同时被另一个进程追踪,这个进程就处于追踪暂停状态。
将上面的程序重新编译,生成带有调试信息的Debug版本。
在10行处添加一个断点,然后执行代码,此时程序在执行到第10行的时候就会停下来,此时这个进程就处于追踪停止状态,对应下图的小写t。此时gdb也在运行,在追踪这个进程的运行,在等待用户的下一步指示。
现在这个被调试进程被暂停且被gdb跟踪,它的状态是追踪暂停状态。
6.僵尸状态(Z)
僵尸状态也叫僵死状态,它是在进程在死亡状态之前的状态。当一个进程运行完毕、出现问题或者被杀掉以后,它所占用的内存资源和退出状态没有被它的父进程回收,此时这个进程的状态就称为僵尸状态。
下面的讲解中我会详细讲解僵尸进程,就不再这里过多赘述。
7.死亡状态(X)
当一个进程执行结束或者是被操作系统杀掉,它的PCB被操作系统删除,并且对应加载到磁盘上的二进制代码也被删除,此时这个进程就处于死亡状态了。
当一个进程占有内存的所有资源被回收以后,这个进程就处于死亡状态。进程的死亡状态是看不到的,因为只有在回收完成的那一刻才会出现,PCB不存在也就搜索不到这个进程,所以也无法演示。
四、两种特殊进程
1.僵尸进程
当一个进程运行完毕时,它所占用的内存资源和执行的退出状态需要被它的父进程回收。如果父进程没有回收这些东西,这个进程就是僵尸进程。如果父进程长时间不回收进程数据,那么这个进程将长期处于僵尸状态,其PCB和代码依旧存储在内存中,占用空间但是不会被执行。
我们写一个代码:
父子进程同时运行,我杀掉子进程,子进程就停止运行了,但此时父进程还在忙着执行自己的代码,也就回收不了子进程的空间与返回状态,所以此时子进程就处于僵尸状态了,注意下面大写Z。
进程的占用的内存与退出结果对操作系统而言十分重要,不管成功还是失败子进程都要告诉它的父进程自己的任务完成得怎么样了。但是如果父进程一直不读取这些数据,那子进程既不执行也占用空间,也就会一直处于Z状态。
只要进程没有死亡进程基本信息就不会被清除,所以保存数据的PCB就会继续被操作系统维护。
如果一个父进程创建了很多子进程,子进程执行完毕后就是不回收,进程的PCB和执行完毕或者因错误终止的代码就会一直储存在内存中,就会造成内存资源的浪费。这很类似于我们C/C++中的内存泄漏问题。
2.孤儿进程
孤儿进程是指操作系统中父进程先于子进程退出,父进程死亡,子进程就没有了父进程,这时的子进程就会变成孤儿进程。但是父进程的死亡不代表孤儿进程就永远没有了父进程,每一个孤儿进程都会在第一时间被操作系统的一号进程领养。
我们用相同的代码,这次我们杀死父进程
杀死父进程,查看进程状态
我们可以发现子进程(pid:6374)的父进程pid变为了1,也就是说子进程会被1号进程收养,并在子进程执行完毕后回收它的空间和执行返回值。