018 进程控制 —— 进程等待

018 进程控制 —— 进程等待
小米里的大麦进程控制 —— 进程等待
1. 进程等待必要性
- 当父进程通过
fork()创建了子进程后,子进程终止时,其退出信息必须由父进程读取,父进程如果不管不顾,就可能造成 僵尸进程 的问题,进而造成内存泄漏。 - 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的
kill -9也无能为力,因为谁也没有办法杀死一个已经死去的进程。 - 最后,父进程派给子进程的任务完成的如何,我们需要知道。如子进程运行完成,结果对还是不对,或者是否正常退出。
- 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。
如果不等待会怎样?
- 子进程退出了,但父进程没有调用
wait()系列函数。- 子进程的“退出状态”会保留在内核中,直到父进程读取它。
- 此时子进程的 PCB 没有完全释放,占用系统资源。
- 如果产生大量僵尸进程,系统资源将耗尽,导致无法创建新进程。、
所以:父进程需要“等待”子进程终止并获取其退出状态,以释放系统资源。
面试点拨: 如果不调用
wait()会怎样?回答:子进程的退出信息留在内核,
PCB未释放,形成僵尸进程,长期不回收会占满系统资源。
2. 常用等待方法(重点掌握)
| 函数名 | 作用 |
|---|---|
wait(int *status) |
阻塞等待任意一个子进程结束,并获取其退出状态 |
waitpid(pid, &status, options) |
更灵活:等待指定子进程,或非阻塞等 |
1. wait() 示例(阻塞等待子进程)
wait():
- 原型:
pid_t wait(int *status);- 功能:阻塞等待任意一个子进程退出,并回收其资源。
- 参数:
status(输出型参数):保存/获取子进程退出状态(需用宏解析,如WIFEXITED)。不关心可设置为 NULL。- 返回值:成功返回子进程 PID,失败返回
-1。
实验目的:
- 学会使用
wait()函数阻塞等待子进程结束。 - 理解如何通过
status获取子进程的退出状态。 - 掌握如何判断子进程是否正常退出以及获取其退出码。
[!CAUTION]
下面代码会涉及部分知识盲区,在文章后面会讲到!
实验:
1 |
|
实验示例结果:
1 | 我是子进程...PID:1234, PPID:1233 |
2. waitpid() 示例(等待指定子进程,更灵活)
waitpid()
- 原型:
pid_t waitpid(pid_t pid, int *status, int options);- 功能:更灵活地等待指定子进程,支持非阻塞模式。
- 参数:
pid:指定子进程 PID,或 -1 表示任意子进程。options:常用的有 WNOHANG 表示非阻塞等待(立即返回,无子进程退出时返回0)。- 返回值:成功返回子进程 PID,WNOHANG 模式下无退出子进程时返回
0,失败返回-1。
实验目的:
- 学会使用
waitpid()函数等待指定子进程。 - 理解非阻塞等待(WNOHANG)的使用场景和优势。
- 掌握如何在等待子进程的同时处理其他任务。
实验 1:
1 |
|
实验示例结果:
1 | 父进程忙别的事... |
WNOHANG 的用途(后面详讲):它用于非阻塞轮询场景,让父进程可以边处理任务边检查子进程状态。
实验 2:
1 |
|
实验示例结果:
1 | 我是子进程,我已经运行了:1秒 PID:1234 PPID:1233 |
3. status 退出状态详解
1. 什么是 status?
当你用 wait() 或 waitpid() 等函数回收子进程时,会通过一个整型变量 status 返回子进程的 终止状态/状态码 status 信息。这个 status 是一个 32 位整数,它的 各个位(bit)存储了子进程退出的不同信息,主要包括:
- 子进程是否正常退出
- 退出的返回码
- 是否是被信号中断
- 是否是
core dump等
当子进程结束时,它就会返回一个 状态码 status,通过宏函数解读它:
| 宏函数 | 判断或提取内容 | 实现底层逻辑 | 本质 |
|---|---|---|---|
WIFEXITED() |
是否正常退出 | (status & 0x7F) == 0 |
判断是否未被信号终止(是否正常退出) |
WEXITSTATUS() |
获取退出码 | (status >> 8) & 0xFF |
提取退出码所在的 8 位(获取 exit 返回码) |
这些宏的 设计目的 就是为了 屏蔽底层实现细节,让你写代码时更易读。但其实就是对 status 进行的位运算封装。
2. status 的位布局(Linux 下)
通常(glibc 实现下),status 的位布局如下:
1 | 31...........16 | 15.....8 | 7......0 |
1 | 31 16 15 8 7 0 |
3. WIFEXITED 和 WEXITSTATUS 的底层原理
1. WIFEXITED(status)
判断子进程是否 正常退出(调用了 exit() 或 return)
1 |
🔸 它检测的是 低 7 位(status & 0x7F)是否为 0,即 没有被信号终止。
2. WEXITSTATUS(status)
获取子进程的 退出码(exit() 或 return 的值)
1 |
🔸 它提取的是 第 8~15 位,因为退出码就被编码在这里。
4. 实验测试
实验目的:
- 学会解析
status的各个位,了解子进程的退出状态。 - 掌握如何通过宏函数判断子进程是否正常退出以及获取其退出码。
- 理解如何手动解析
status的位信息。
1 |
|
输出示例:
1 | 原始 status:16896 (0x4200) |
示例:手动解析 status:
1 |
|
扩展:
写法模板:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 >
>
>
>
>int main()
>{
pid_t pid = fork();
if (pid == 0) // 子进程逻辑
{
exit(0);
}
else if (pid > 0) // 父进程逻辑
{
int status;
pid_t ret = waitpid(pid, &status, 0);
if (ret == -1)
{
perror("waitpid error");
}
else if (WIFEXITED(status))
{
printf("子进程正常退出,退出码: %d\n", WEXITSTATUS(status));
}
else
{
printf("子进程异常退出\n");
}
}
else
{
perror("fork error");
}
return 0;
>}
若子进程是被信号杀死的,还可用:
WIFSIGNALED(status):是否被信号终止。WTERMSIG(status):哪个信号导致的。这些也都是对
status特定位的封装。
面试点拨:
Q:只创建一个子进程也要
wait()吗?A:要,不然会产生僵尸进程。
Q:
wait(NULL)和wait(&status)有何不同?A:前者不关心子进程退出码,后者可以判断退出状态。
Q:
wait()和waitpid()的区别是什么?(详见下文)A:
wait()阻塞等待任意一个子进程,而waitpid()可以指定子进程,并支持非阻塞模式。Q:怎么判断子进程是否异常退出?
A:
WIFEXITED(status)为假时即为异常,可结合WIFSIGNALED查看是否被信号终止。
4. 非阻塞轮询
1. 什么是非阻塞轮询(Non-blocking Polling)?
非阻塞轮询 是一种在程序中检查某项资源状态(比如文件描述符、输入输出、子进程状态等)时,不会阻塞(挂起)当前线程或进程的技术。非阻塞轮询其实是 进程等待的一种特殊形式,本质上就是使用 waitpid() 函数时,配合选项 WNOHANG,来实现 非阻塞地检查子进程是否退出。
非阻塞轮询底层依赖:
waitpid(..., WNOHANG):设置为非阻塞检查子进程。read()/write()配合O_NONBLOCK标志。select()/poll()/epoll()这些高级接口也支持非阻塞 I/O 检测。
联系:
- 非阻塞轮询 ≈ 进程等待 +
WNOHANG参数。 - 是进程等待的一种实现方式,可以避免父进程“卡死”在等待中。
- 适合场景:父进程还有其他任务要处理、需要同时监控多个子进程、构造后台守护程序等。
这样说难以理解,我们用一个示例来帮助理解:假如你是快递员,你今天安排了送货任务,但你同时还在等一个客户签收你的包裹。现在有两种做法:
场景一:阻塞等待(wait)
你在客户门口等着他开门签字,你哪儿也不去,什么都不干,就在那儿等。就是 wait() 或 waitpid(pid, NULL, 0)。
- 优点:等到了就能马上处理。
- 缺点:你被“卡住”了,浪费了等的这段时间。
场景二:非阻塞轮询(WNOHANG)
你不一直站在门口,而是 每隔 10 分钟回来敲一次门,空闲的时候你还可以去送别的快递。就是 waitpid(pid, &status, WNOHANG) + sleep(1)。
- 优点:你不会被“卡住”,还能干其他事。
- 缺点:客户签收可能不能第一时间知道(需要“轮询”)。
场景三:阻塞轮询(极端示例)
你不停敲门、再敲门,一直不走,一直问:“你签了没?你签了没?” 程序中表现为没有 sleep 的非阻塞 waitpid(pid, WNOHANG) 死循环。
- 缺点:会让 CPU 疯狂运转(忙等待)。
术语与现实对应表
| 系统术语 | 现实中的你 |
|---|---|
| 阻塞等待 | 在门口站着等,不做别的事 |
| 非阻塞轮询 | 每隔一段时间回来问一次,期间干别的事 |
| 阻塞轮询 | 疯狂按门铃,问个不停,CPU 很累 |
| 进程等待 | 等子进程结束,获取退出状态 |
2. 联系总结(术语图谱)
1 | wait/waitpid |
3. 我该怎么选?怎么使用?
| 场景 | 推荐方法 | 原因 |
|---|---|---|
| 父进程只等子进程结束,没别的事干 | 阻塞等待 (wait) |
简单、直接、不会浪费资源 |
| 父进程还有其他重要任务 | 非阻塞轮询 (waitpid + WNOHANG) |
不中断其他逻辑,更灵活 |
| 你同时要监控多个子进程 | 非阻塞轮询 | 可以处理多个子进程,适合服务端/守护程序 |
| 写简单的练习题/实验代码 | 阻塞等待即可 | 写起来方便,看得懂 |
实验目的:
- 学会使用非阻塞轮询等待子进程结束。
- 理解如何在等待子进程的同时处理其他任务。
- 掌握如何通过
WNOHANG选项实现非阻塞等待。
1 |
|
实验示例结果:
1 | 父进程:子进程还在运行... |
4. 小结一句话
非阻塞轮询是一种“智能等待”方式,让父进程在等待子进程的同时,还能处理其他任务,是并发编程的常见技巧。
5. 总结记忆点
| 内容 | 说明 |
|---|---|
| 为什么等待 | 防止僵尸进程,释放系统资源,获取子进程退出信息,确保系统稳定性和资源高效利用。 |
| 常用函数 | wait():阻塞等待任意子进程结束;waitpid():灵活等待指定子进程,支持非阻塞模式。 |
| 状态解析 | 使用宏函数 WIFEXITED() 判断子进程是否正常退出,WEXITSTATUS() 获取退出码。 |
| 非阻塞轮询 | 适用于父进程需要同时处理其他任务或监控多个子进程的场景,通过 waitpid() 配合 WNOHANG 实现。 |
| 推荐写法 | 常用 waitpid(pid, &status, 0),安全灵活,适合大多数场景。 |
| 注意事项 | 父进程必须回收子进程资源,否则会导致僵尸进程,长期不回收会耗尽系统资源。 |
| 适用场景 | 简单程序使用阻塞等待,复杂程序或需要并发处理时使用非阻塞轮询。 |
实战技巧
- 调试技巧:在调试时,若发现僵尸进程,检查父进程是否正确调用了
wait()或waitpid()。 - 性能优化:在高并发场景下,使用非阻塞轮询避免父进程被长时间阻塞,提高系统响应速度。
- 代码健壮性:始终检查
wait()和waitpid()的返回值,处理可能的错误情况。

















