资料合集
链接:https://pan.quark.cn/s/770d9387db5f
我们已经知道, 创造生命,而父进程则肩负着为子进程“善后”的重任,以防“僵尸”肆虐。
fork() 函数就是父进程履行这一职责最直接的工具。但
wait() 的世界远比
wait() 丰富得多。它是一扇窗,让我们得以窥见子进程生命终点时的最后状态:是功成身退,还是意外离场?
wait(NULL)
今天,我们将深入这门艺术,从 的基础用法出发,学习如何解读子进程的“最终遗言”,并最终解锁其更强大、更灵活的“升级版”——
wait()。
waitpid()
一、
wait():不仅仅是等待
wait()
函数的核心使命有三:
wait()
阻塞等待:暂停父进程,直到任意一个子进程结束。清理后事:回收终止子进程的PCB(进程控制块),彻底释放其内核资源,杜绝僵尸进程。解读遗言:通过传出参数 获取子进程的详细退出状态。
wstatus
解码子进程的“最终遗言” (
wstatus)
wstatus
是一个整型数,但它内部像密码一样编码了子进程的退出信息。我们必须使用一组专用的宏来安全地解码它:
wstatus
| 宏函数 | 作用 | 如何获取具体值(当宏为真时) |
|---|---|---|
|
判断子进程是否正常终止(通过 或 )。 |
获取退出码 |
|
判断子进程是否被信号杀死(异常终止)。 | 获取信号编号 |
|
(较少用) 判断子进程是否被信号暂停。 | 获取暂停信号 |
|
(较少用) 判断被暂停的子进程是否已恢复运行。 | – |
黄金法则:必须先用 系列宏判断类型,再用
WIF... 系列宏提取具体值。
W...STATUS/SIG
二、 实战演练:两个截然不同的结局
让我们编写一个程序,通过它来亲自验证这两种最常见的子进程结局。
代码 ()
wait_investigation.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
// 封装一个错误处理函数,方便调用
void sys_err(const char *str) {
perror(str);
exit(1);
}
int main(int argc, char *argv[]) {
pid_t pid = fork();
if (pid < 0) {
sys_err("fork error");
} else if (pid == 0) {
// --- 子进程的世界 ---
printf(" [Child] My PID is %d. I'm running...
", getpid());
sleep(2);
#if 1 // --- 控制开关:设为 1 测试正常退出,设为 0 测试异常退出 ---
// 场景一:正常退出
printf(" [Child] I'm exiting normally with code 73.
");
exit(73);
#else
// 场景二:异常终止(段错误)
printf(" [Child] I'm about to cause a segmentation fault...
");
char *p = NULL;
*p = 'a'; // 非法内存访问,将导致 SIGSEGV 信号
#endif
} else {
// --- 父进程的世界 ---
int status;
// 等待子进程,并将状态存入 status
pid_t ret_pid = wait(&status);
if (ret_pid < 0) {
sys_err("wait error");
}
// --- 开始解码 ---
if (WIFEXITED(status)) {
printf("[Parent] Child %d exited normally. Exit code: %d
",
ret_pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("[Parent] Child %d was killed by signal. Signal number: %d
",
ret_pid, WTERMSIG(status));
}
}
return 0;
}
案例一:正常寿终 (
#if 1)
#if 1
编译与运行
gcc wait_investigation.c -o wait_demo
./wait_demo
运行结果
[Child] My PID is 71234. I'm running...
[Child] I'm exiting normally with code 73.
[Parent] Child 71234 exited normally. Exit code: 73
分析:
子进程执行 ,正常终止。父进程的
exit(73) 捕捉到该事件。
wait() 返回真。
WIFEXITED(status) 成功提取出我们设定的退出码
WEXITSTATUS(status)。
73
案例二:意外身亡 (
#if 0)
#if 0
修改代码,将 改为
#if 1,重新编译运行。
#if 0
重新编译与运行
gcc wait_investigation.c -o wait_demo
./wait_demo
运行结果
[Child] My PID is 71238. I'm running...
[Child] I'm about to cause a segmentation fault...
Segmentation fault (core dumped)
[Parent] Child 71238 was killed by signal. Signal number: 11
分析:
子进程试图向 指针写入数据,操作系统立即发送
NULL (信号11) 将其终止。父进程的
SIGSEGV 捕捉到该事件。
wait() 返回假。
WIFEXITED(status) 返回真。
WIFSIGNALED(status) 成功提取出导致子进程死亡的信号编号
WTERMSIG(status)。
11
小贴士:在终端输入
可以查看所有信号及其编号。
kill -l就是段错误。
11) SIGSEGV
三、
waitpid():
wait()的超集与进化
waitpid()
wait()
函数虽然好用,但有两个局限:
wait()
只能等任意子进程:无法指定等待某一个特定的子进程。必须阻塞:一旦调用,父进程就得“坐牢”,直到子进程结束。
函数完美地解决了这些问题。
waitpid()
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
几乎等价于
wait(&status)。
waitpid(-1, &status, 0)
参数解析
: 与
wstatus 完全相同,用于接收状态。
wait(): 控制等待的目标。
pid
: 等待进程ID等于
> 0 的那个子进程。
pid: 等待任意子进程(等同于
-1)。
wait(): 等待与当前进程组ID相同的任何子进程。
0: 等待进程组ID等于
<-1 绝对值的任何子进程。
pid
: 赋予
options 特殊能力,最常用的是:
waitpid
: 默认行为,阻塞等待。
0: 非阻塞模式!
WNOHANG 会立即返回。如果子进程还没结束,它会返回
waitpid,而不是阻塞父进程。
0
实战:非阻塞回收子进程
非阻塞模式是 的精髓,它允许父进程在等待子进程的同时,还能处理自己的事情。
waitpid
代码 ()
waitpid_nohang.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
exit(1);
} else if (pid == 0) {
// 子进程工作5秒
printf(" [Child] Working for 5 seconds...
");
sleep(5);
printf(" [Child] Done.
");
exit(0);
} else {
pid_t ret_pid;
do {
// 使用 WNOHANG 进行非阻塞轮询
ret_pid = waitpid(pid, NULL, WNOHANG);
if (ret_pid == 0) {
// 子进程还在运行,父进程可以做点别的事情
printf("[Parent] No news from child yet, I'll check again in 1 sec.
");
sleep(1);
}
} while (ret_pid == 0); // 循环直到回收成功或出错
printf("[Parent] Finally reaped child %d.
", ret_pid);
}
return 0;
}
编译与运行
gcc waitpid_nohang.c -o waitpid_demo
./waitpid_demo
运行结果
[Child] Working for 5 seconds...
[Parent] No news from child yet, I'll check again in 1 sec.
[Parent] No news from child yet, I'll check again in 1 sec.
[Parent] No news from child yet, I'll check again in 1 sec.
[Parent] No news from child yet, I'll check again in 1 sec.
[Child] Done.
[Parent] Finally reaped child 71450.
分析:父进程不再是傻等,而是在一个循环中“轮询”子进程的状态。在子进程结束前的5秒内,父进程每隔1秒就打印一次信息,表明它有能力处理其他任务。这在需要父进程保持响应的复杂应用中至关重要。
总结
| 函数 | 灵活性 | 阻塞行为 | 核心应用场景 |
|---|---|---|---|
|
低 (等待任意子进程) | 阻塞 | 简单场景,父进程创建子进程后无事可做,只需等待其结束。 |
|
高 (可指定PID,可非阻塞) | 可阻塞/可非阻塞 | 复杂场景,如管理多个子进程、父进程需保持响应、轮询任务等。 |
从 到
wait(),我们不仅学会了如何避免僵尸进程,更掌握了与子进程进行深度“沟通”的技巧。理解并善用这些工具,是编写出健壮、高效、优雅的Linux多进程程序的关键所在。
waitpid()
