理解了"进程是什么"之后,接下来就要回答另一个更现实的问题:一个进程在运行过程中会经历哪些状态变化,为什么有些进程会变成僵尸进程,以及操作系统究竟如何决定"下一个该让谁上 CPU"。
这几个问题既是 Linux 学习中的高频考点,也是面试里很常见的提问方向。这篇文章就把它们串在一起讲清楚。
本文你会看到三个重点:
- Linux 进程最常见的几种状态
- 僵尸进程和孤儿进程到底有什么区别
- 调度器是怎样从多个进程里挑选下一个运行者的

图 1:学习进程状态时,最重要的是抓住"状态会变化"这件事,而不是把进程理解成一直占着 CPU 不动。
一、进程并不总在运行
很多初学者会下意识地认为,只要程序启动了,它就一直在"运行"。但从操作系统的角度看,真正占用 CPU 的时间往往只是很短的一小段。进程的大部分时间,要么在等待,要么在切换。
在 Linux 中,我们经常会看到这些典型状态:
R:Running,运行态或就绪态S:Sleeping,可中断睡眠D:Disk Sleep,不可中断睡眠,常见于等待 I/OT:Stopped,暂停状态Z:Zombie,僵尸状态X:Dead,结束状态
可以通过下面的命令观察它们:
bash
ps aux
ps axj
top
对系统编程来说,最值得重点关注的,其实不是 R 和 S,而是 Z。
二、什么是僵尸进程
僵尸进程不是"还在干活的进程",恰恰相反,它已经执行结束了,只是它的退出信息还没有被父进程回收。
更准确地说,子进程退出后,绝大多数资源都会被内核释放,但它的退出状态、统计信息以及最基本的 PCB 记录还会暂时保留,等待父进程通过 wait() 或 waitpid() 来读取并回收。
如果父进程一直不回收,子进程就会进入 Z 状态,也就是僵尸进程。
三、僵尸进程是怎么产生的
下面这个例子很典型:子进程很快退出,但父进程故意睡很久,也不去回收子进程。
c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t id = fork();
if (id < 0) {
perror("fork");
return 1;
} else if (id > 0) {
printf("parent[%d] is sleeping...\n", getpid());
sleep(30);
} else {
printf("child[%d] is begin Z...\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
这段程序运行时,子进程结束后就很容易在 ps 里看到 Z 状态。
四、为什么僵尸进程不能放着不管
很多资料都会说"僵尸进程不占什么资源",这句话只说对了一半。
确实,僵尸进程不会再消耗 CPU,也不会继续执行用户代码,但它依然占着进程号和部分内核数据。如果父进程持续不回收,系统中的僵尸数量不断累积,就会浪费进程表项,严重时甚至会影响新进程创建。
所以,真正的重点不是"它大不大",而是"它不该长期存在"。
五、孤儿进程又是什么
和僵尸进程相对应的,还有一个经常一起出现的概念,叫孤儿进程。
它指的是:父进程先退出了,但子进程还在继续运行。此时,子进程不会没人管,系统会把它托管给 init 或 systemd 这样的 1 号进程,由它来完成后续回收工作。
所以:
- 僵尸进程的核心问题是"子进程已经退出,父进程却不回收"
- 孤儿进程的核心特征是"父进程先退出,子进程还活着"
这两个概念很容易混淆,但本质完全不同。

图 2:僵尸进程和孤儿进程的关键差别,不在于"有没有父进程",而在于"子进程是否已经退出、退出后有没有被回收"。
六、优先级和 nice 值:谁更容易拿到 CPU
当系统里有很多可运行进程时,调度器必须做出选择。最直观的影响因素之一,就是优先级。
在教学场景里,通常会用 PRI 和 NI 来帮助理解:
PRI可以理解为进程当前被调度时参考的优先级NI也就是nice值,用来表达"我愿不愿意让出更多 CPU 机会"
nice 值越低,通常意味着进程越"着急";nice 值越高,则意味着它更"客气"。
在用户态,我们常见的操作是查看或调整 nice 值:
bash
top
renice -n 5 -p <pid>
或者通过接口:
c
int getpriority(int which, int who);
int setpriority(int which, int who, int prio);
特别提醒一下:这里的优先级讲法适合建立概念模型,但如果你继续深入 Linux 内核,会发现真实调度逻辑要复杂得多,不能只用一条简单公式概括。
七、调度的本质:让有限的 CPU 服务更多任务
调度器存在的意义,并不是"让一个进程一直跑",而是尽可能公平、及时、可控地把 CPU 分给合适的任务。
这就引出了几个关键词:
- 时间片
- 上下文切换
- 运行队列
- 优先级
只要发生调度,当前进程的运行现场就需要被保存,另一个进程的现场需要被恢复,这个过程就是上下文切换。频繁切换会带来开销,所以调度永远是在"响应速度"和"切换成本"之间找平衡。
八、如何理解O(1) 调度器
Linux 2.6 时代非常有代表性的 O(1) 调度器,有 runqueue、优先级数组、位图、active 和 expired 队列这些结构。
如果你是第一次接触调度器,这一部分非常有价值,因为它回答了一个关键问题:调度器并不是"拍脑袋选一个进程",而是依赖明确的数据结构来快速找到合适的可运行任务。
可以把它概括为三层思路:
- 每个 CPU 维护自己的运行队列
- 不同优先级的进程分布在不同队列里
- 通过位图快速定位当前最高优先级的可运行任务
这也是为什么它被称为 O(1):在这个模型里,寻找下一个任务的代价可以被控制在常数量级。
九、最后
学进程,不能只停留在"会写 fork()"这一层。真正要学明白,至少要建立三种意识:
- 进程是在不断变化状态的
- 父子进程关系会直接影响资源回收
- CPU 调度背后依赖的是一套完整的数据结构和策略
当你能把进程状态、僵尸进程和调度机制放到同一张图里理解时,Linux 进程模型就不再是零散知识点,而开始变成一套完整系统。
如果用一句话总结本文,那就是:
进程不是"启动后一直跑着不动"的程序,而是会经历状态变化、资源回收和 CPU 竞争的一类动态对象。