进程状态是进程的重要属性之一,也是Linux下task_struct中的重要属性,可以便于操作系统较为均衡的调度每个进程。本文将着重介绍Linux下的进程状态,并解释进程状态与操作系统调度进程的关系。
目录
基本概念
Linux下的进程状态
一、R状态
二、S状态
三、D状态
四、T状态
五、t状态
六、x状态
七、Z状态
僵尸进程的危害
孤儿进程
Linux下,进程的状态信息存储在task_struct中。有了进程状态,可以方便操作系统快速判断进程,完成特定的功能。
在操作系统的相关书籍中,我们会看到类似下面的图片:
这是一个笼统的概念,因为操作系统书籍需要对各种操作系统进行兼容,做到“放之四海而皆准”。
运行态:进程正在执行。
就绪态:进程做好了准备,只要有机会就开始执行。
挂起/等待/阻塞态:进程在某些事件发生前不能执行,如I/O操作完成。
初始态:刚刚创建的进程,操作系统还未把它加入可执行进程组,通常是进程控制块已经创建但还未加载到内存中的新进程。
结束:操作系统从可执行进程组中释放出的进程,要么它自身已停止,要么它因为某种原因被取消。
以上是操作系统书籍中的解释。本篇文章将继续从Linux的角度阐述各种进程状态。
Linux下共有如下几种状态:R(运行状态)、S(睡眠状态)、D(磁盘休眠状态)、T(停止状态)、t(追踪状态)、X(死亡状态)、Z(僵尸状态)。
R状态并不意味着进程一定在运行中,一定占有CPU资源,它表明进程要么在运行中,要么在运行队列里。下面画图作出解释:
正在占有CPU资源进程的R状态也就对应运行态,当它的时间片结束,将放到运行队列尾部,此时的R状态对应的状态是就绪态。
R状态在Linux下很好检测到,写下如下测试代码:
#include
#include
int main()
{
while(true)
{
}
return 0;
}
运行后使用ps axj | grep myproc指令可以发现进程处于R状态。
S状态的进程在等待某个事件的完成:如网络、I/O资源等。(这里的睡眠也称为可中断睡眠)。进程为了等待某个事件的完成,从运行队列(R状态)放到等待队列(S状态)中,叫做挂起等待;从等待队列(S状态)放到运行队列(R状态)中,CPU的调度就叫做进程唤醒。
写下如下测试代码:
#include
#include
int main()
{
sleep(20);
return 0;
}
运行后使用ps axj | grep myproc指令可以发现进程处于S状态。
D状态即不可中断睡眠状态,在这个状态的进程通常会等待IO结束。D状态的进程和S状态的进程一样,会被放入等待队列,设计D状态的独特意义是什么呢?
我们可以想象这样一种情况:进程向磁盘申请I/O资源,向磁盘写入。此时如果进程进入S状态,然而此时内存资源已经不充足了,操作系统为了缓解内存资源的高占用,选择杀死一些S状态的进程。此时就将正在等待I/O资源的进程杀掉了。此时磁盘完成了工作,需要将结果告诉进程(成功或失败),而进程已经被杀掉,结果无法向上传递。可能导致明明失败了,而用户却浑然不知的情况。
于是,此种情况下,进程会进入D状态,不可被OS杀掉,也即不可中断。
T状态即停止状态。可以通过发送SIGSTOP信号给进程来停止进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。
测试代码:
#include
#include
int main()
{
while(true)
{
}
return 0;
}
运行后该进程原本处于R状态,发送信号后,变为T状态
t状态即追踪状态,进程调试时所处的状态,这里不做过述。
所谓x状态指定是死亡状态,此状态只是一个返回状态,无法在任务列表中看到这个状态。因为回收进程是一瞬间发生的事情,我们很难直接捕捉到。
Z状态是指僵尸状态,该状态指的是一个比较特殊的状态。当进程退出且父进程没有回收进程时,就会产生僵尸进程。
僵尸进程会以终止状态保持在进程列表中,并会一直等待父进程读取退出状态码。因此只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就会进入Z状态。
测试代码:
#include
#include
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
while(true)
{
std::cout << "child is runing" << std::endl;
sleep(2);
}
}
else if(id > 0)
{
//父进程
std::cout << "parent is sleeping" << std::endl;
sleep(100);
}
else
{}
return 0;
}
进程运行后,父子进程都处于S状态
终止掉子进程。由于父进程正在睡眠,无法回收子进程,子进程进入僵尸状态
待父进程结束回收子进程后,两个进程最终都会终止
进程如果一直处于Z状态(即僵尸进程),该进程的task_struct也会一直被维护,也即相关的代码数据与内核数据结构会一直存在。因此系统中如果存在过多的僵尸进程,会导致内存泄漏,造成内存资源的泄漏,因此需要避免这种情况的出现。
下面再补充系统中的一种进程——孤儿进程
事想可能出现如下一种情况:父进程先于子进程退出。
这样一来,子进程是不是就无法被回收了?我们将这样的子进程称为孤儿进程,孤儿进程会被1号进程所领养,自然也会被1号进程所回收。
测试代码:
#include
#include
int main()
{
pid_t id = fork();
if(id == 0)
{
//子进程
while(true)
{
std::cout << "child is runing" << std::endl;
sleep(2);
}
}
else if(id > 0)
{
//父进程
std::cout << "parent is sleeping" << std::endl;
sleep(10);
exit(1);
}
else
{}
return 0;
}
运行后父进程将会于10秒后退出,之后我们再来看看子进程的状态
10秒后,子进程的父进程pid变为1,也就验证了孤儿进程会被1号进程所领养。