一、 进程终止:exit 与 _exit 的区别
进程终止的本质是释放系统资源。正常的退出方法有 return、exit() 和 _exit()。这三个有什么区别?重点关注 exit 和 _exit:
-
exit(int status):标准库函数。它会先执行清理函数,刷新 C 标准库的缓冲区 ,然后把数据交给内核,最后调用_exit真正退出。 -
_exit(int status):系统调用。简单粗暴,直接陷入内核退出。不会刷新缓冲区。 -
踩坑记录 :如果
printf("hello");后面紧跟_exit(0);,因为不带\n,数据还在缓冲区没刷出来,程序就死了,屏幕上什么都不会打印!
二、 进程等待:消灭僵尸进程
子进程退出后,如果父进程不管不顾,子进程就会变成"僵尸进程",占用 PCB 且 kill -9 杀不掉。解决这个问题的终极办法就是:父进程进行进程等待。
三、 等待双子星:wait 和 waitpid
-
wait(&status):阻塞等待。父进程卡在这里,直到任意一个子进程退出。 -
waitpid(pid, &status, options):精准等待。可以指定 PID。-
options为 0 时,也是阻塞等待。 -
options为WNOHANG时,是非阻塞等待。如果子进程还在运行,立刻返回 0,父进程可以继续干别的事(轮询模式),效率更高。
-
四、 解析 status 退出状态
wait 和 waitpid 的 status 参数是一个输出型参数,它不能当作普通整数看,而是一个位图:
-
高 8 位代表子进程的退出码。
-
低 7 位代表终止信号。
因此,我们不能直接打印
status,而需要使用系统提供的宏: -
WIFEXITED(status):返回真,代表子进程正常退出。 -
WEXITSTATUS(status):提取子进程真实的退出码。
五、 实战代码演示
cpp
// 父进程阻塞等待子进程
pid_t ret = wait(&status);
if (WIFEXITED(status)) {
printf("子进程退出码: %d\n", WEXITSTATUS(status));
}
// 父进程非阻塞轮询等待
while (1) {
ret = waitpid(id, &status, WNOHANG);
if (ret == 0) {
// 子进程还在运行,父进程干点别的
} else {
break; // 子进程退出了
}
}
自我实战操作
~/linux_study/proc_test 目录下写代码。
实战 1:验证 exit 与 _exit 的缓冲区区别
bash
bash
vim exit_test.c
(输入以下代码)
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
printf("Hello Linux"); // 注意:这里没有加换行符 \n!
// 1. 尝试使用 exit(0)
exit(0);
// 2. 注释掉上面的,换成 _exit(0) 试试
// _exit(0);
return 0;
}
-
实验 A :用
exit(0)编译运行,终端会输出Hello Linux。 -
实验 B :改为
_exit(0)编译运行,终端什么都不会输出 (直接回到提示符),因为数据还在缓冲区,被_exit直接扼杀了。
实战 2:父进程获取子进程的退出码
bash
vim wait_test.c
(输入以下代码)
cpp
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main() {
pid_t id = fork();
if (id == 0) {
// 子进程
printf("我是子进程[%d],我要睡2秒,然后带着退出码 66 退出...\n", getpid());
sleep(2);
exit(66); // 子进程退出,退出码为 66
} else {
// 父进程
printf("我是父进程[%d],我要等我的子进程...\n", getpid());
int status = 0;
// 父进程在这里阻塞等待
pid_t ret = wait(&status);
// 检查是否正常退出,并提取退出码
if (WIFEXITED(status)) {
printf("等待成功!子进程PID: %d,它的退出码是: %d\n", ret, WEXITSTATUS(status));
}
}
return 0;
}
- 编译运行后,父进程会卡住 2 秒,然后精准打印出子进程的退出码
66。
实战 3:非阻塞等待(WNOHANG)
- 修改上面
wait_test.c的父进程部分:
cpp
} else {
int status = 0;
pid_t ret = 0;
// 循环非阻塞等待
while (1) {
ret = waitpid(id, &status, WNOHANG);
if (ret == 0) {
printf("子进程还没死,父进程去干点别的活...\n");
sleep(1); // 假装干了一秒钟的活
} else {
printf("子进程死了!退出码: %d\n", WEXITSTATUS(status));
break;
}
}
}
- 运行后发现,父进程不再卡死,而是每秒打印一次"子进程还没死",直到子进程退出。


