进程的终止与回收
当一个子进程完成其任务后,它会进入“僵尸进程”(Zombie Process)状态,子进程已经结束,不再占用内存和CPU资源,但在进程表中仍然保留一条记录,这条记录包含了子进程的进程ID(PID)、终止状态(是正常退出还是被信号杀死)以及退出码(程序 return 的值或 exit 的参数)。

为什么需要僵尸进程? 父进程可能需要知道子进程是如何结束的(成功还是失败,错误码是什么),僵尸进程就是子进程留给父进程的“成绩单”,如果父进程不读取这个“成绩单”,子进程就会一直保持僵尸状态。
wait 和 waitpid 的作用
这两个函数就是父进程用来“读取成绩单”的,父进程调用它们后,会阻塞(暂停执行)直到一个子进程结束,然后系统会回收该子进程的僵尸进程条目,使其彻底消失,这个过程称为进程回收或等待子进程。
wait 函数
wait 是最简单的等待函数,它会阻塞调用它的父进程,直到任何一个子进程结束。
函数原型
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status);
参数
int *status: 这是一个整型指针,用于获取子进程的终止状态。- 如果你对子进程的终止状态不感兴趣,可以传递
NULL。 - 如果你想获取状态信息,需要传递一个整型变量的地址。
wait函数会将状态信息写入这个变量。
- 如果你对子进程的终止状态不感兴趣,可以传递
返回值
- 成功:返回已结束子进程的进程ID (PID)。
- 失败:返回
-1,并设置errno,常见的失败情况是调用者没有子进程。
status 参数详解
status 是一个整型,它通过不同的位组合来传递信息,我们通常使用宏来解析它:

WIFEXITED(status): 如果子进程是正常退出(调用了exit或return),则此宏为真。- 如果为真,可以用
WEXITSTATUS(status)获取子进程的退出码(即exit()的参数或main函数的return值)。
- 如果为真,可以用
WIFSIGNALED(status): 如果子进程是被信号杀死(SIGKILL,SIGSEGV),则此宏为真。- 如果为真,可以用
WTERMSIG(status)获取导致子进程终止的信号编号。
- 如果为真,可以用
WIFSTOPPED(status): 如果子进程是暂停状态(由SIGSTOP或SIGTSTP信号导致),则此宏为真。(这在作业控制中更常见)。WIFCONTINUED(status): 如果子进程从暂停状态继续运行,则此宏为真。
注意:WIFEXITED 和 WIFSIGNALED 是互斥的,一个子进程只能以一种方式结束。
waitpid 函数
waitpid 是 wait 的一个更强大、更灵活的版本,它允许父进程等待特定的子进程,并且可以控制是否阻塞。
函数原型
#include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options);
参数
-
pid: 指定要等待的子进程。pid > 0: 等待进程ID为pid的子进程。pid == 0: 等待任何一个与调用者同组的子进程。pid == -1: 等待任何一个子进程。这和wait()的功能完全一样。pid < -1: 等待进程组ID为pid的绝对值的任何子进程。
-
int *status: 和wait函数一样,用于获取子进程的终止状态,不关心则传NULL。
(图片来源网络,侵删) -
int options: 控制函数行为的标志位,可以通过 组合使用。0: 默认行为,阻塞等待,直到子进程结束。WNOHANG: 非阻塞模式,如果指定的子进程没有结束,waitpid会立即返回0,而不是挂起父进程,父进程可以稍后再试。WUNTRACED: 同时也返回处于暂停状态的子进程信息。WCONTINUED: 同时也返回从暂停状态继续运行的子进程信息。
返回值
- 成功:
- 返回已结束子进程的进程ID (PID)。
- 在
WNOHANG模式下,如果没有子进程结束,返回0。
- 失败:返回
-1,并设置errno(没有符合条件的子进程)。
代码示例
下面通过几个例子来对比 wait 和 waitpid 的用法。
示例1:使用 wait 等待任意子进程
这个例子创建一个子进程,子进程休眠2秒后退出,父进程调用 wait 阻塞等待。
#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(2); // 模拟子进程工作
printf("Child process (PID: %d) is exiting with status 42.\n", getpid());
exit(42); // 以状态码42退出
} else {
// --- 父进程代码 ---
printf("Parent process (PID: %d) is waiting for child (PID: %d)...\n", getpid(), pid);
// 父进程阻塞,直到子进程结束
pid_t child_pid = wait(&status);
if (child_pid == -1) {
perror("wait failed");
exit(EXIT_FAILURE);
}
printf("Parent process: Child (PID: %d) has terminated.\n", child_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));
}
printf("Parent process is done.\n");
}
return 0;
}
编译与运行:
gcc wait_example.c -o wait_example ./wait_example
预期输出:
Parent process (PID: 1234) is waiting for child (PID: 1235)...
Child process (PID: 1235) is running...
(等待2秒...)
Child process (PID: 1235) is exiting with status 42.
Parent process: Child (PID: 1235) has terminated.
Child exited normally with status: 42
Parent process is done.
示例2:使用 waitpid 的非阻塞模式
这个例子中,父进程创建子进程后,使用 waitpid 的 WNOHANG 选项,每隔1秒检查一次子进程是否结束,而不是一直阻塞。
#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 and will sleep for 5 seconds...\n", getpid());
sleep(5);
printf("Child process (PID: %d) is exiting.\n", getpid());
exit(0);
} else {
// --- 父进程代码 ---
printf("Parent process (PID: %d) is waiting for child (PID: %d) with WNOHANG...\n", getpid(), pid);
int i = 0;
while (1) {
pid_t ret = waitpid(pid, &status, WNOHANG); // 非阻塞等待
if (ret == -1) {
perror("waitpid failed");
break;
} else if (ret == 0) {
// 子进程还没结束
printf("Parent: Child has not finished yet. Checking again in 1 second...\n");
sleep(1);
i++;
if (i > 10) { // 防止无限循环
printf("Parent: Giving up waiting.\n");
break;
}
} else if (ret == pid) {
// 子进程已经结束
printf("Parent process: Child (PID: %d) has terminated.\n", pid);
if (WIFEXITED(status)) {
printf("Child exited normally with status: %d\n", WEXITSTATUS(status));
}
break;
}
}
}
return 0;
}
编译与运行:
gcc waitpid_example.c -o waitpid_example ./waitpid_example
预期输出:
Parent process (PID: 1234) is waiting for child (PID: 1235) with WNOHANG...
Parent: Child has not finished yet. Checking again in 1 second...
Parent: Child has not finished yet. Checking again in 1 second...
Parent: Child has not finished yet. Checking again in 1 second...
Parent: Child has not finished yet. Checking again in 1 second...
Parent: Child has not finished yet. Checking again in 1 second...
Child process (PID: 1235) is running and will sleep for 5 seconds...
Parent: Child has not finished yet. Checking again in 1 second...
Parent: Child has not finished yet. Checking again in 1 second...
Parent: Child has not finished yet. Checking again in 1 second...
Parent: Child has not finished yet. Checking again in 1 second...
Parent: Child has not finished yet. Checking again in 1 second...
Child process (PID: 1235) is exiting.
Parent process: Child (PID: 1235) has terminated.
Child exited normally with status: 0
总结与最佳实践
| 特性 | wait |
waitpid |
|---|---|---|
| 功能 | 等待任意一个子进程结束。 | 等待特定PID的子进程结束,或任意子进程。 |
| 阻塞性 | 总是阻塞,直到有子进程结束。 | 可通过 options 参数控制是否阻塞。 |
| 灵活性 | 较低,功能单一。 | 非常高,可以指定PID、非阻塞、处理暂停/继续等。 |
| 返回值 | 已结束子进程的PID。 | 已结束子进程的PID;WNOHANG模式下无子进程结束则返回0。 |
最佳实践:
- 总是检查返回值:
wait和waitpid都可能失败,调用后务必检查返回值是否为-1。 - 使用
waitpid替代wait:在大多数情况下,waitpid是更好的选择,即使你想等待任意子进程,也可以使用waitpid(-1, &status, 0),这比wait更明确。 - 处理僵尸进程:父进程必须调用
wait或waitpid来回收子进程,如果父进程在子进程之前结束,子进程会被init进程(PID 1)接管,由init进程来回收,从而避免僵尸进程,但在一个程序中,父进程应该负责清理自己的子进程。 - 解析状态码:当你关心子进程的退出原因时,一定要使用
WIFEXITED,WEXITSTATUS等宏来解析status参数,而不是直接打印或使用它。
希望这个详细的解释能帮助你完全理解 Linux C 语言中的 wait 和 waitpid 函数!
