大家好,我是程序员小青蛙,今天讲解进程终止和进程等待问题。

一、进程退出的场景
进程退出主要分为 3 种情况:
进程退出码可以通过 echo $? 查看,$? 永远记录最近一个进程的退出码。
二、进程退出的 4 种方式
1. main 函数 return 返回
- main 函数的 return n,本质上等同于调用 exit(n)。
- 进程退出码由 return 的值决定,只有低 8 位有效(0-255)。
- 示例:return 0 表示程序正常结束;return 1 表示程序执行出错。
2. 调用 exit() 函数(库函数)
#include <stdlib.h>
void exit(int status);
- status:进程的退出状态,父进程可通过 wait 获取。
- 核心特点:退出前会做清理工作
- 执行用户通过 atexit/on_exit 注册的清理函数。
- 刷新所有打开的流缓冲区(如 printf 的缓冲区)。
- 关闭所有打开的文件流。
- 最终调用 _exit() 终止进程。
3. 调用 _exit() 函数(系统调用)
#include <unistd.h>
void _exit(int status);
参数:status定义了进程的终止状态,父进程通过wait来获取该值
说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,
在终端执行$?发现返回值是255。
- 直接终止进程,不会刷新缓冲区、不执行清理函数。
- 示例对比:
// 用 exit() 时,缓冲区会被刷新,输出 "hello"
printf("hello");
exit(0);// 用 _exit() 时,缓冲区不会被刷新,无输出
printf("hello");
_exit(0);

4. 被信号终止(异常退出)
- 如 Ctrl+C(SIGINT)、kill -9(SIGKILL)、段错误(SIGSEGV)。
- 这种情况下,进程退出码无意义,终止原因记录在 status 中。
三、exit() vs _exit() 核心区别
| 类型 | C 标准库函数 | 系统调用 |
| 刷新缓冲区 | ✅ 会刷新用户态缓冲区 | ❌ 不会刷新 |
| 执行清理函数 | ✅ 执行 atexit 注册的函数 | ❌ 不执行 |
| 关闭文件流 | ✅ 关闭所有打开的流 | ❌ 直接交给内核处理 |
| 适用场景 | 正常退出程序 | 异常情况下强制终止 |
四、进程等待:为什么必须 wait?
必要性:
子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9也无能为力,因为谁也没有办法杀死一个已经死去的进程。
最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
五、进程等待的两种方法
1. wait() 函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
- 功能:阻塞等待任意一个子进程退出,回收其资源。
- 参数:status 是输出型参数,用于获取子进程的退出状态(不关心可传 NULL)。
- 返回值:成功返回退出子进程的 pid;失败返回 -1。
2. waitpid() 函数
pid_t waitpid(pid_t pid, int *status, int options);
- 功能:等待指定的子进程退出,支持非阻塞等待。
- 参数详解:
- pid:
- pid > 0:等待 pid 等于指定值的子进程。
- pid = -1:等待任意子进程(和 wait 等价)。
- pid = 0:等待和调用者同组的任意子进程。
- pid < -1:等待进程组 ID 等于 |pid| 的任意子进程。
- status:同 wait(),获取退出状态。
- options:
- 0:阻塞等待(默认行为)。
- WNOHANG:非阻塞等待,若子进程未退出,函数立即返回 0。
- pid:
如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
如果不存在该子进程,则立即出错返回。

六、解析 status:判断子进程退出原因
wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
如果传递NULL,表示不关心子进程的退出状态信息。
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

分析一下这个16位
st 是一个 16 位的状态数字
Linux 中,wait(&st) 拿到的 st 是一个 int 整数,但真正有用的只有低 16 位:
15 14 13 12 11 10 9 8 │ 7 │ 6 5 4 3 2 1 0
退出码(8位) │core│ 信号号(7位)
结构固定死了:
- bit 0~6(低 7 位):终止信号编号
- bit 7:core dump 标志
- bit 8~15(高 8 位):进程退出码(exit code)
第一句:st & 0x7F → 取低 7 位(信号编号)
1. 0x7F 是什么?
二进制:
0111 1111
刚好 7 个 1。
2. 按位与 & 作用
st & 0x7F → 只保留 最低 7 位 → 高位全部清零
3. 这 7 位存的是什么?
如果进程被信号杀死,这 7 位就是信号编号:
- 9 → SIGKILL
- 15 → SIGTERM
- 2 → SIGINT
所以:
st & 0x7F → 拿到杀死子进程的信号编号
第二句:(st >> 8) & 0xFF → 取高 8 位(退出码)
1. st >> 8
把 16 位数字向右移动 8 位。
移动前:
高8位(退出码) 低8位(信号+core)
[15………….8][7………….0]
右移 8 位后:
原来的高8位 来到了低8位
[15………….8] → 低8位
2. & 0xFF
只保留低 8 位,也就是退出码。
3. 最终结果
(st >> 8) & 0xFF → 拿到子进程的 exit(10) 里的 10
status 是一个 int 类型,其低 16 位包含了子进程的退出状态信息:
- 正常终止:低 8 位为 0,高 8 位为退出码(通过 WEXITSTATUS(status) 获取)。
- 异常终止:低 7 位为终止信号(通过 status & 0x7F 获取),第 8 位为 core dump 标志。
测试代码
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main( void )
{
pid_t pid;
if ( (pid=fork()) == -1 )
perror("fork"),exit(1);
if ( pid == 0 ){
sleep(20);
exit(10);
} else {
int st;
int ret = wait(&st);
if ( ret > 0 && ( st & 0X7F ) == 0 ){ //正常退出
printf("child exit code:%d\\n", (st>>8)&0XFF);
} else if( ret > 0 ) {//异常退出
printf("sig code : %d\\n", st&0X7F );
}
}
}
关键点:
- st & 0x7F:取低 7 位,信号编号在这里
- (st>>8)&0xFF:取高 8 位,退出码在这里
正常退出


杀死之后异常退出
常用宏定义
WIFEXITED(status) // 若为真,表示子进程正常退出
WEXITSTATUS(status) // 获取子进程的退出码(仅正常退出时有效)
WIFSIGNALED(status) // 若为真,表示子进程被信号终止
WTERMSIG(status) // 获取终止子进程的信号编号
七、代码示例:进程等待的两种方式
1. 阻塞等待(waitpid + options=0)
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return 1;
}
else if (pid == 0) { // 子进程
printf("child[%d] is running…\\n", getpid());
sleep(3); // 模拟任务执行
exit(10); // 子进程退出,退出码为10
}
else { // 父进程
printf("parent[%d] waiting for child…\\n", getpid());
int status;
pid_t ret = waitpid(pid, &status, 0); // 阻塞等待
if (ret > 0) {
if (WIFEXITED(status)) {
printf("child exited normally, exit code: %d\\n", WEXITSTATUS(status));
} else {
printf("child exited abnormally, signal: %d\\n", status & 0x7F);
}
}
}
return 0;
}
int main()
{
pid_t pid;
pid = fork();
if(pid < 0){
printf("%s fork error\\n",__FUNCTION__);
return 1;
} else if( pid == 0 ){ //child
printf("child is run, pid is : %d\\n",getpid());
sleep(5);
exit(257);
} else{
int status = 0;
pid_t ret = waitpid(-1, &status, 0);//阻塞式等待,等待5S
printf("this is test for wait\\n");
if( WIFEXITED(status) && ret == pid ){
printf("wait child 5s success, child return code is :%d.\\n",WEXITSTATUS(status));
}else{
printf("wait child failed, return.\\n");
return 1;
}
}
return 0;
}

1. fork() 创建子进程
pid = fork();
- 调用后变成两个进程
- 父进程返回子进程 pid
- 子进程返回 0
2. 子进程做了什么?
else if( pid == 0 ){
printf("child is run, pid is : %d\\n",getpid());
sleep(5);
exit(257);
}
- 打印自己的 PID
- sleep(5):休眠 5 秒
- exit(257):退出,退出码是 257
3. 父进程做了什么?
pid_t ret = waitpid(-1, &status, 0);
- -1:等待任意子进程
- &status:输出型参数,拿到子进程退出状态
- 0:阻塞等待(子进程不退出,父进程就一直等)
4. 判断子进程是否正常退出
if( WIFEXITED(status) && ret == pid )
- WIFEXITED(status) → 为真 → 子进程正常退出(不是被信号杀死)
- ret == pid → 确保等待的就是目标子进程
5. 获取退出码
WEXITSTATUS(status)
- 拿到子进程 exit(n) 中的 n
- 只取低 8 位!
最重要考点:exit (257) 为什么输出 1?
- 257 的二进制:
257 = 00000001 00000001
- 退出码只保留低 8 位
- 所以:
257 & 0xFF = 1
所以程序最终输出:
wait child 5s success, child return code is :1.
2. 非阻塞等待(waitpid + WNOHANG)
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return 1;
}
else if (pid == 0) { // 子进程
printf("child[%d] is running…\\n", getpid());
sleep(3);
exit(10);
}
else { // 父进程
printf("parent[%d] waiting non-blocking…\\n", getpid());
int status;
pid_t ret;
do {
ret = waitpid(pid, &status, WNOHANG); // 非阻塞等待
if (ret == 0) {
printf("child is still running, parent do other work…\\n");
sleep(1);
}
} while (ret == 0);
if (ret > 0) {
if (WIFEXITED(status)) {
printf("child exited normally, exit code: %d\\n", WEXITSTATUS(status));
}
}
}
return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid = fork();
if(pid < 0){
printf("%s fork error\\n",__FUNCTION__);
return 1;
}else if( pid == 0 ){ //child
printf("child is run, pid is : %d\\n",getpid());
sleep(5);
exit(1);
} else{
int status = 0;
pid_t ret = 0;
do
{
ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待
if( ret == 0 ){
printf("child is running\\n");
}
sleep(1);
}while(ret == 0);
if( WIFEXITED(status) && ret == pid ){
printf("wait child 5s success, child return code is :%d.\\n",WEXITSTATUS(status));
}else{
printf("wait child failed, return.\\n");
return 1;
}
}
return 0;
}

1. 父进程 非阻塞等待 子进程
- waitpid(-1, &status, WNOHANG)
- WNOHANG:非阻塞模式
- 如果子进程还在运行 → waitpid 立即返回 0
- 如果子进程已经退出 → 返回子进程 pid
2. 父进程做了什么?
- 每秒轮询一次
- 没等到就打印:child is running
- 等到了就打印退出码
运行结果
child is run, pid is :
child is running
child is running
child is running
child is running
wait child 5s success, child return code is :1.
一共打印 5 次 child is running 因为子进程 sleep (5),父进程每秒问一次。
关键函数讲解
1. waitpid(-1, &status, WNOHANG)
- -1:等待任意子进程
- &status:拿到退出状态
- WNOHANG:非阻塞等待
- 子进程没退出 → 返回 0
- 子进程已退出 → 返回 子进程 pid
2. WIFEXITED(status)
判断子进程是否正常退出(不是被信号杀死)
3. WEXITSTATUS(status)
拿到子进程的退出码
阻塞 vs 非阻塞(总结)
-
阻塞等待(wait /waitpid 0) 父进程一直等,啥也不干,直到子进程退出
-
非阻塞等待(waitpid WNOHANG) 父进程每隔一段时间问一次,不等的时候可以干别的


![[LangChain] 学习笔记(02):LCEL 链式调用与动态模型配置实战-171主机测评](https://www.171host.com/wp-content/uploads/2026/10/20261030203301-6ae4fefdb54f3-220x150.png)
![[LangChain] 学习笔记(02):LCEL 链式调用与动态模型配置实战-171主机测评](https://www.171host.com/wp-content/uploads/2026/10/20261030202745-6ae4fdc1556bf-220x150.png)

