waitpid 是 Unix/Linux 系统中一个非常重要的系统调用,位于 <sys/wait.h> 头文件中,它用于父进程“等待”子进程的状态发生变化,并获取子进程的退出信息。

(图片来源网络,侵删)
为什么需要 waitpid?
当一个子进程结束后,它并不会立即从系统中消失,而是进入一个“僵尸进程”(Zombie Process)状态,在这个状态下,子进程已经停止运行,但内核中仍然保留着它的进程 ID 和一些退出信息(如退出码、是否被信号终止等),保留这些信息是为了让父进程能够获取。
问题: 如果父进程不关心子进程的退出状态,子进程就会一直处于僵尸状态,占用系统资源,这会导致资源泄漏。
解决方案:
- 父进程调用
wait或waitpid:父进程主动等待子进程结束,并获取其退出状态,之后,子进程的 PCB(进程控制块)会被内核彻底回收。 - 父进程忽略
SIGCHLD信号:父进程可以设置信号处理,告诉内核当子进程结束时,不要发送SIGCHLD信号给父进程,或者忽略该信号,这样,子进程结束后会立即被内核回收,不会成为僵尸进程。 - 父进程调用
signal(SIGCHLD, SIG_IGN):这是一种简单粗暴但有效的方法,让内核自动回收子进程。
waitpid 提供了比 wait 更强大的功能,是控制子进程生命周期的主要工具。

(图片来源网络,侵删)
waitpid 函数原型
#include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options);
参数详解
-
pid_t pid:- 这个参数指定了你想等待哪个子进程。
pid > 0: 等待进程 ID 为pid的子进程,这是最常用的情况。pid == -1: 等待任何子进程,其功能和wait()函数完全相同,如果没有任何子进程可等待,它会立即返回错误。pid == 0: 等待任何组 ID 等于当前进程组 ID 的子进程。pid < -1: 等待组 ID 等于pid的绝对值的任何子进程。
-
*`int status`**:
- 这是一个输出型参数,用于获取子进程的退出状态。
- 如果你不关心子进程的退出状态,可以传入
NULL。 - 如果传入一个有效的指针(
&status_val),waitpid会将子进程的退出信息写入这个地址指向的整型变量。 - 你需要使用宏来解析
status中的信息,这些宏定义在<sys/wait.h>中:WIFEXITED(status): 如果子进程是正常退出(通过调用exit或从main函数返回),则返回真。WEXITSTATUS(status):WIFEXITED(status)为真,则使用此宏获取子进程的退出码(exit()函数的参数)。WIFSIGNALED(status): 如果子进程是被信号终止(例如收到SIGKILL,SIGSEGV等),则返回真。WTERMSIG(status):WIFSIGNALED(status)为真,则使用此宏获取导致子进程终止的信号编号。WIFSTOPPED(status): 如果子进程当前是暂停状态(收到SIGSTOP或SIGTSTP信号),则返回真,这在作业控制中比较常见。WSTOPSIG(status):WIFSTOPPED(status)为真,则使用此宏获取导致子进程暂停的信号编号。
-
int options:- 这个参数通过位掩码来控制
waitpid的行为,最常用的选项是: 0: 默认行为,父进程会阻塞(即暂停执行),直到一个子进程状态发生变化(结束、暂停等)。WNOHANG: 非阻塞模式,如果指定的子进程没有状态变化,waitpid会立即返回,而不是等待,此时返回值为0。WUNTRACED: 除了等待已退出的子进程外,也等待那些被暂停(但未终止)的子进程。WCONTINUED: 等待那些从暂停状态被继续(SIGCONT信号)运行的子进程。
- 这个参数通过位掩码来控制
返回值
- 成功:
options设置为WNOHANG且没有子进程状态改变,返回0。- 否则,返回等待到的子进程的 PID。
- 失败:
- 返回
-1,并设置errno来指示错误原因,常见的错误包括:ECHILD: 调用进程没有子进程。EINTR:waitpid被一个信号中断。
- 返回
代码示例
下面通过几个例子来展示 waitpid 的不同用法。
示例 1:基本用法(阻塞等待)
父进程创建一个子进程,子进程睡眠几秒后退出,父进程调用 waitpid 等待子进程并打印其退出码。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main() {
pid_t pid;
int status;
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// 子进程
printf("Child process (PID: %d) is running...\n", getpid());
sleep(3); // 模拟子进程工作
printf("Child process (PID: %d) is exiting with code 42.\n", getpid());
exit(42); // 子进程以状态码 42 退出
} else {
// 父进程
printf("Parent process (PID: %d) is waiting for child (PID: %d)...\n", getpid(), pid);
// 阻塞等待子进程 pid 结束,并将状态存入 status
pid_t waited_pid = waitpid(pid, &status, 0);
if (waited_pid == -1) {
perror("waitpid failed");
exit(EXIT_FAILURE);
}
printf("Parent process detected child (PID: %d) has terminated.\n", waited_pid);
// 解析子进程的退出状态
if (WIFEXITED(status)) {
printf("Child exited normally with status: %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("Child killed by signal: %d\n", WTERMSIG(status));
}
}
return 0;
}
预期输出:
Parent process (PID: 1234) is waiting for child (PID: 1235)...
Child process (PID: 1235) is running...
(等待3秒后)
Child process (PID: 1235) is exiting with code 42.
Parent process detected child (PID: 1235) has terminated.
Child exited normally with status: 42
示例 2:非阻塞等待 (WNOHANG)
父进程创建子进程后,不希望被阻塞,而是可以继续做自己的事,并定期检查子进程是否结束。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main() {
pid_t pid;
int status;
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// 子进程
printf("Child process (PID: %d) is running...\n", getpid());
sleep(5);
printf("Child process (PID: %d) is exiting.\n", getpid());
exit(0);
} else {
// 父进程
printf("Parent process (PID: %d) is doing other work...\n", getpid());
while (1) {
pid_t waited_pid = waitpid(pid, &status, WNOHANG);
if (waited_pid == -1) {
perror("waitpid failed");
break;
} else if (waited_pid == 0) {
// 子进程还没结束
printf("Child is not finished yet. Parent continues working...\n");
sleep(1);
} else {
// 子进程已结束
printf("Parent process detected child (PID: %d) has terminated.\n", waited_pid);
if (WIFEXITED(status)) {
printf("Child exited normally with status: %d\n", WEXITSTATUS(status));
}
break;
}
}
}
return 0;
}
预期输出:
Parent process (PID: 1234) is doing other work...
Child is not finished yet. Parent continues working...
Child is not finished yet. Parent continues working...
Child is not finished yet. Parent continues working...
Child is not finished yet. Parent continues working...
Child is not finished yet. Parent continues working...
Child process (PID: 1235) is running...
(等待5秒后)
Child process (PID: 1235) is exiting.
Parent process detected child (PID: 1235) has terminated.
Child exited normally with status: 0
示例 3:等待任意子进程 (pid == -1)
父进程创建两个子进程,然后等待其中任意一个结束。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main() {
pid_t pid1, pid2;
pid1 = fork();
if (pid1 == 0) {
printf("Child 1 (PID: %d) will exit in 2s.\n", getpid());
sleep(2);
exit(10);
}
pid2 = fork();
if (pid2 == 0) {
printf("Child 2 (PID: %d) will exit in 4s.\n", getpid());
sleep(4);
exit(20);
}
// 父进程,等待任意一个子进程结束
printf("Parent (PID: %d) is waiting for any child...\n", getpid());
int status;
pid_t waited_pid = waitpid(-1, &status, 0);
printf("Parent detected child (PID: %d) has terminated.\n", waited_pid);
if (WIFEXITED(status)) {
printf("Child exited with status: %d\n", WEXITSTATUS(status));
}
// 父进程可以继续等待另一个子进程
printf("Parent is now waiting for the other child...\n");
waited_pid = waitpid(-1, &status, 0);
printf("Parent detected child (PID: %d) has terminated.\n", waited_pid);
if (WIFEXITED(status)) {
printf("Child exited with status: %d\n", WEXITSTATUS(status));
}
return 0;
}
预期输出 (顺序可能不同):
Parent (PID: 1234) is waiting for any child...
Child 1 (PID: 1235) will exit in 2s.
Child 2 (PID: 1236) will exit in 4s.
(等待2秒后)
Parent detected child (PID: 1235) has terminated.
Child exited with status: 10
Parent is now waiting for the other child...
(等待2秒后)
Parent detected child (PID: 1236) has terminated.
Child exited with status: 20
waitpid vs. wait
| 特性 | waitpid |
wait |
|---|---|---|
| 原型 | pid_t waitpid(pid_t pid, int *status, int options); |
pid_t wait(int *status); |
| 灵活性 | 高,可以指定等待哪个子进程,或任意子进程。 | 低,只能等待任意一个子进程,相当于 waitpid(-1, ...)。 |
| 阻塞模式 | 可通过 options 参数(如 WNOHANG)设置为非阻塞。 |
总是阻塞,直到有子进程状态改变。 |
| 功能 | 功能更全面,支持作业控制(WUNTRACED, WCONTINUED)。 |
功能单一,是 waitpid 的一个特例。 |
| 推荐度 | 强烈推荐,在现代编程中,waitpid 是更安全、更灵活的选择。 |
不推荐,除非有特殊理由或兼容性要求。 |
waitpid 是 C 语言进行进程控制的核心函数之一,理解并掌握它的用法对于编写健壮的、多进程的 Unix/Linux 应用程序至关重要。
关键点回顾:
- 目的:回收僵尸进程,获取子进程的退出状态。
- 核心参数:
pid:指定等待目标。status:获取退出信息,需用宏解析。options:控制行为,WNOHANG实现非阻塞是关键。
- 返回值:成功返回 PID,
WNOHANG时无变化返回 0,失败返回 -1。 - 实践:在服务器编程、并发任务处理等场景中,
waitpid结合WNOHANG实现非轮询式等待,是提高效率的常用模式。
