
🎬 个人主页:HABuo
📖 个人专栏:《C++系列》 《Linux系列》《数据结构》《C语言系列》《Python系列》《YOLO系列》
⛰️ 如果再也不能见到你,祝你早安,午安,晚安

目录
[📖2.1 waitpid的参数status](#📖2.1 waitpid的参数status)
[📖2.2 waitpid的第三个参数option](#📖2.2 waitpid的第三个参数option)
前言 :
上篇博客我们讲解了进程创建和终止,进程状态的时候我们留了一个伏笔,就是一个子进程进入了僵尸状态,谁回收它我们清楚了,但是怎么回收当时我们并没有说,那在这篇博客我们就来介绍如何回收的问题,即进程等待!
本章重点:
本篇文章着重讲解进程等待的必要性 ,以及系统调用wait/waitpid的使用详情 ,并且重点讲解waitpid的三个参数的含义和不同的用法!
📚一、进程等待概念
进程等待就是父进程通过系统调用(如wait()、waitpid())来等待子进程的状态改变(终止或停止),并获取子进程的退出状态,从而释放子进程占用的系统资源。
进程等待的必要性:
- 若子进程退出,而父进程对它不管不顾此时会有僵尸进程问题,有内存泄漏风险
- 当一个进程变成僵尸进程,那它就刀枪不入了,因为无法杀掉一个死去的进程
- 父进程创建子进程是为了完成某任务,父进程需要知道它把任务完成得如何,所以等待子进程死亡是很有必要的!
- 父进程需要等待子进程死亡后,回收它的代码和数据!
进程等待的主要目的:
-
防止僵尸进程:子进程退出后,父进程如果不等待,子进程会成为僵尸进程,占用系统资源。
-
获取子进程的退出状态:父进程可以知道子进程是如何退出的(正常退出、异常退出等)。
进程等待的系统调用:
-
wait():等待任意一个子进程结束,如果当前没有子进程退出,则阻塞等待。
-
waitpid():可以等待指定的子进程,也可以设置非阻塞模式。
📚二、wait/waitpid

因为waitpid比wait更灵活,且waitpid懂了wait自然也就懂了,因此本篇文章着重讲解waitpid函数
waitpid函数的详情请看下图:

话不多说,上代码来测试:
cpp
int main()
{
pid_t pids[3];
// 创建3个子进程
for (int i = 0; i < 3; i++) {
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程 %d (PID=%d) 启动\n", i, getpid());
sleep(i + 1); // 每个子进程睡眠时间不同
exit(100 + i);
}
pids[i] = pid;
}
// 父进程等待特定子进程
for (int i = 0; i < 3; i++) {
// 等待特定PID,阻塞模式
pid_t ret = waitpid(pids[i], NULL, 0);
printf("我等待成功一个子进程!它的id是: %d\n", ret);
}
}

可以发现,当子进程执行它的代码时,父进程并不会进入if语句中,而是往下继续执行代码,但是父进程迟迟不打印"我等待成功一个子进程",这是因为父进程在阻塞等待子进程,如果子进程不退出,父进程就在这里等它死亡!
📖2.1 waitpid的参数status
如果父进程想要知道子进程的退出信息,也就是退出码和退出信号,就要用到这个输出型参数status
status参数可没有你想的这么简单,它的信息并不是按照整个整数来存储的,如果你学过位图的话,那么下面的内容会很好理解!

我们只研究status的低16比特位
低八位存储的是终止信号,次低八位存储的是退出状态
所以如果想要获取status中的这两个信息,我们需要使用下面的代码来解析:
cpp
退出码:
(status >> 8) & 0xFF
退出信号:
status & 0x7F
我们用下面的代码验证一下:
cpp
int main()
{
pid_t pids[3];
// 创建3个子进程
for (int i = 0; i < 3; i++) {
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程 %d (PID=%d) 启动\n", i, getpid());
sleep(i + 1); // 每个子进程睡眠时间不同
exit(100 + i);
}
pids[i] = pid;
}
// 父进程等待特定子进程
for (int i = 0; i < 3; i++) {
int status;
// 等待特定PID,阻塞模式
pid_t ret = waitpid(pids[i], &status, 0);
if (ret > 0)
{
printf("回收子进程 PID=%d,退出码: %d, 退出信号是: %d\n", ret, (status >> 8) & 0xFF, status & 0x7F);
}
}
}
运行结果如下:

对于退出信号,我们正常退出即为0,如果我们使用kill -9杀死进程,那么这个信号也即为9了,当然此时退出码也就没有意义了!
总有人会觉得,上面的位操作麻烦,因此接口提供了宏替换如下:
cpp
printf("子进程正常退出,退出码: %d\n", WEXITSTATUS(status));
printf("子进程被信号终止,信号: %d\n", WTERMSIG(status));
📖2.2 waitpid的第三个参数option
前面说到,option默认为0代表父进程阻塞等待子进程死亡,阻塞等待的意思就是父进程什么都不干,就在waitpid函数处停下等待子进程!那么假如父进程想要干一些自己的事情应该怎样做?
cpp
使用宏定义: WNOHANG
waitpid(pid,&status,WNOHANG);
WNOHANG就是wait no hang,hang也就是悬挂(挂起来也就有停的意思),no hang也就是不停也即是非阻塞等待子进程死亡,若父进程执行到waitpid时,子进程还没退出,则函数返回0后接着运行下面的代码,若执行到waitpid后子进程已经退出则返回退出子进程的pid
然而如果非阻塞等待一次,这样肯定是不行的,因为你不知道子进程什么时候退出
又因此父进程执行非阻塞waitpid时,``只要子进程不返回父进程就执行下一步代码
因此使用非阻塞等待时往往会循环访问,这个过程叫轮询
cpp
int main()
{
pid_t pids[3];
// 创建3个子进程
for (int i = 0; i < 3; i++) {
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程 %d (PID=%d) 启动\n", i, getpid());
sleep(i*3 + 2); // 每个子进程睡眠时间不同
exit(100 + i);
}
pids[i] = pid;
}
// 父进程等待特定子进程
for (int i = 0; i < 3; i++) {
int status;
int count = 0;
while (1)
{
pid_t ret = waitpid(pids[i], &status, WNOHANG);
if (ret > 0)
{
printf("回收子进程 PID=%d,退出码: %d, 退出信号是: %d\n", ret, (status >> 8) & 0xFF, status & 0x7F);
printf("-----------回收完成----------\n");
break;
}
sleep(1);
printf("我执行我的代码 %d 次\n", count++);
}
}
}

注:这里父进程可以执行任一任务,我使用printf打印只是为了明显的看到父进程是没有阻塞等待的!

所以我们学完waitpid之后回头再看wait那是相当ok,它无非就是把waitpid的第二个参数即获取子进程退出信息的参数拿过来作为参数!
📚三、总结
本篇博客我们主要了解了进程等待的相关知识点。
总结一下:
进程等待
- 是什么:
cpp
父进程通过系统调用(如wait()、waitpid())
来等待子进程的状态改变(终止或停止)
并获取子进程的退出状态,从而释放子进程占用的系统资源
- 为什么:
cpp
①防止僵尸进程,僵尸进程会占用系统资源
②获取子进程的退出状态
- 怎么办:
cpp
通过系统调用wait、waitpid。
waitpid返回值:
等待成功返回所等待进程的pid,
等待失败返回-1,
当在非阻塞模式即WNOHANG,可能返回0,这并不表示失败,而是表示没有子进程退出。
waitpid参数:
第一个表示要等待进程的pid,
第二个int* status
低八位表示退出信号,次低八位表示退出码
可以通过位移操作获取((status >> 8) & 0xFF、status & 0x7F)
也可通过宏替换获取(WEXITSTATUS(status)、WTERMSIG(status))
第三个表示以什么方式进行等待,阻塞为0,非阻塞为WNOHANG。
