Linux进程状态
1. 一般的操作系统学科进程状态
1.1 Linux 内核调度系统和进程运行机制:
核心概念:
- Linux 使用
task_struct
描述进程,链入运行队列中。 - 每个 CPU 有一个
runqueue
,调度器从中选择任务运行。 - 每个进程被分配一个时间片,时间到了会被强制切换。
- 即使进程陷入死循环,也不会永远占用 CPU(如
while(1)
)。 - 多个进程在一个时间段内看似“同时”运行,是因为被快速轮换执行。
- 调度器负责将准备好的任务从
runqueue
中调度上 CPU 执行(状态为TASK_RUNNING
)。
1.2 操作系统中的进程阻塞机制
-
进程请求键盘输入:
-
操作系统发现键盘无数据可读。
-
进程进入阻塞状态,挂到键盘设备的
waitqueue
上。
-
-
系统内存紧张:
-
操作系统将部分阻塞进程换出(即保存到磁盘 swap 区),释放物理内存。
-
-
键盘收到数据:
-
操作系统通过中断机制通知。
-
唤醒在
waitqueue
中等待键盘输入的进程。 -
若进程被换出,还会将其从磁盘换入内存。
-
-
进程恢复运行。
2. Linux进程状态的维护
2.1 Linux内核源代码
为了弄明白正在运行的进程是什么意思,我们需要看到进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫任务)
在Linux源代码:
/** 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[] = {/* states in TASK_REPORT: */"R (running)", /* 0x00 */"S (sleeping)", /* 0x01 */"D (disk sleep)", /* 0x02 */"T (stopped)", /* 0x04 */"t (tracing stop)", /* 0x08 */"X (dead)", /* 0x10 */"Z (zombie)", /* 0x20 */"P (parked)", /* 0x40 *//* states beyond TASK_REPORT: */"I (idle)", /* 0x80 */
};
2.2 进程状态
R运行状态(running):并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
#include<stdio.h>
int main()
{while(1);return 0;
}
S睡眠状态(sleeping):意味着进程在等待事件完成(这里的睡眠有时候也叫可中断睡眠(interruptible sleep))。
#include<stdio.h>
int main()
{int n=0;scanf("%d",&n);//等待键盘输入,进入阻塞状态return 0;
}
D磁盘休眠状态(Disk sleep):有时候也叫不可中断睡眠(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
D 状态的关键特征:
- 不能被
kill -9
杀死。 - 不能被调度唤醒,除非资源就绪。
- 通常发生在驱动程序里,特别是 调用等待资源的函数(比如读取硬盘时阻塞)。
注意:
- 如果你在内核代码或驱动里使用
TASK_UNINTERRUPTIBLE
+schedule()
,却忘了设置条件唤醒(比如wake_up()
),那这个进程就会永久卡在 D 状态,用户空间无法杀掉它。 - 通常在等待硬件响应(如磁盘响应、USB响应)时,才需要 D 状态。
- 用户态程序无法主动进入 D 状态,只能是调用了某些内核接口后,被动地处于 D 状态。
T停止状态(stopped):可以通过发送SIGSTOP信号来停止进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。
#include <stdio.h>
#include<unistd.h>
int main()
{while (1){printf("I am a process\n");sleep(1);}return 0;
}
停止:
恢复运行:
t状态(tracing stop) :进程被调试器(如 gdb
)以 ptrace 方式挂起,正在停止(Stop)中。
X死亡状态:这个状态只是一个返回状态,不会在任务列表里看到这个状态。一旦状态为 X,内核很快就会释放 task_struct。
2.3 进程状态查看
ps aux
:显示所有进程的摘要信息命令含义:
a
:显示所有用户的进程
u
:显示用户/资源占用情况(User格式)
x
:显示没有控制终端的进程
ps axj
:显示父子关系 & 调试/调度相关信息命令含义:
a
:所有终端的进程
x
:包含没有终端的进程
j
:jobs 格式,显示进程层次结构(父子关系)
字段含义
字段 含义 PPID
父进程 ID PID
当前进程 ID PGID
进程组 ID SID
会话 ID TPGID
前台进程组 ID STAT
状态(比如 R
,S
,D
,T
,t
)COMMAND
启动命令
2.4 Z(zombie)-僵尸进程
- 僵尸状态(Zombie)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵尸(死)进程。
- 僵尸进程会以终止状态保持在进程表中,并且会一直等待父进程读取退出状态代码。
- 所以,只要子进程退出,父进程还在运行,但是父进程没有读取子进程状态,子进程进入Z状态。进程的相关信息尤其是task_struct结构体不能被释放。
#include <stdio.h>
#include<unistd.h>
int main()
{pid_t ret=fork();if(ret==0){//childprintf("I am child\n");return 0;}else if(ret>0){printf(" I am father\n");sleep(20);}return 0;
}
僵尸进程的危害
- 进程的退出状态必须被维持下去,因为它要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那么子进程就会一直处于Z状态。
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护。
- 如果一个父进程创建了很多子进程,但是不回收,就会造成内存资源的浪费。因为数据结构对象本身就要占用内存,C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!
- 内存泄漏
2.5 孤儿进程
- 父进程先退出,子进程就被称为孤儿进程
- 孤儿进程被1号init(操作系统)领养,并由init进程回收资源
#include <stdio.h>
#include <unistd.h>int main()
{pid_t ret = fork();if (ret == 0){// childprintf("I am child\n");sleep(30);}else if (ret > 0){printf(" I am father\n");sleep(5);return 0;}return 0;
}