进程的状态
Linux内核源代码:
c
/*
*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),在这个状态的进程通常会等待I0的结束。
T停止状态(stopped):可以通过发送SIGSTOP信号给进程来停止(T)进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
我们可以在Xshell里进行验证
c
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t id=fork();
if(id==0)
{
//child
int count=10;
while(count)
{
printf("我是一个子进程, 我正在运行:->%d, 运行第%\n",getpid(),count);
sleep(2);
count--;
}
}
else
{
//father
while(1)
{
printf("我是父进程, 我正在运行:->%d\n",getpid());
sleep(2);
}
}
return 0;
}

运行后观察到子进程的状态是Z(僵尸进程).
另还有我们运行时会发现程序的运行状态是S,而不是R, 造成这样的原因也是CPU运行的太快了.
以上面的代码为例子, 每次循环sleep(2)会休眠两秒,这时候程序在等待队列, 也就是处于休眠状态, 2秒后继续执行, 执行循环也是几纳秒的事情. ps的指令又是一瞬间的就执行的, 所以很难捕捉到是R运行的状态.
还有代码中的printf打印到终端或文件,和一些像write写入scanf输入等函数, 也是由于CPU速度太快, CPU处理的循环的速度是(每秒几百万行)而写入到终端的是(每秒几万行).这样其实CPU运行时间很短, 虽然看上去程序还在打印内容, 其实是CPU已经处理好的内容, 所以在查看状态时就会显示在休眠状态.
另外补充一下
如果想查看程序进行的R状态, 可以在循环中持续进行计算.
c
int i=0;
while(1)
{
i++;
}

对于T状态, 在程序运行时, 按ctrl+Z就是作业控制停止.

或者是使用kill -19 PID进行停止
如果要恢复程序就是kill -CONT PID
对于t状态可以使用gdb调试观察到.
僵尸进程的危害
单个僵尸几乎无害,危害在于"堆积"。
-
占着进程表项不放
僵尸的 PCB(
task_struct等内核结构)不会被释放,PID 也一直被占用。它不占 CPU、不占用户内存,只占内核里这一小块"户口"。 -
耗尽 PID,导致无法创建新进程
PID 是有限资源(
pid_max默认几万)。僵尸攒多了,fork()会失败并报EAGAIN,表现为服务起不来、命令执行不了。容器里pidscgroup 限额更小,更容易被撑爆。 -
积累内核内存
每个僵尸仍占几 KB 内核内存,成千上万个就是几十 MB,且这部分内存无法回收。
-
杀不掉,排查和清理都麻烦
kill -9对它无效,只能找到父进程修代码或杀父进程,运维上很被动;监控里进程数、状态统计也会被污染。 -
往往是代码缺陷的信号
出现僵尸说明父进程没正确处理
SIGCHLD/ 没调wait,同一处代码通常还伴随 fd、内存等资源泄漏,值得顺着查。
孤儿进程
孤儿进程是"父进程先走了、自己还活着"的进程。
-
怎么产生
父进程先退出(正常结束或被
kill),子进程还在跑,它就成了孤儿。父进程可能是主动退出,也可能是崩了。 -
不会自生自灭,会被"收养"
内核会把孤儿的父进程改成
init(PID 1)或最近的一个 subreaper(Linux 3.4+ 的PR_SET_CHILD_SUBREAPER;容器里常是tini、dumb-init)。所以它的PPID会变成 1 或 subreaper 的 PID。 -
基本无害
孤儿进程照常运行,状态是正常的
R/S,照常占 CPU 和内存,也能被kill。它只是失去了父进程,不是异常状态。 -
会被收养者负责回收
等它退出时,
init/subreaper 会调用wait收尸,所以正常不会变成僵尸。这正是"孤儿"和"僵尸"的根本区别:
c
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t id=fork();
if(id==0)
{
//child
while(1)
{
printf("我是一个子进程, 我正在运行:->%d \n",getpid());
sleep(2);
}
}
else
{
//father
//
int cnt=10;
while(cnt)
{
printf("我是父进程, 我正在运行:->%d\n",getpid());
sleep(2);
cnt--;
}
}
return 0;
}

这里的孤儿进程的父进程将会被PPID为1的进程领养, 这个PPID为1的进程就相当与程序本身.