进程等待是操作系统中进程管理的一个重要概念,指的是一个进程暂停执行,直到某个特定条件满足后再继续执行的过程。
目录
一、基本概念
进程等待通常发生在以下几种情况:
-
等待I/O操作完成:如磁盘读写、网络数据传输等
-
等待子进程结束:父进程等待子进程执行完毕
-
等待资源可用:如等待锁、信号量等同步机制
-
等待时间条件:如sleep()函数调用
二、进程等待的必要性
- 当子进程退出时,若父进程未读取其退出信息,子进程将转为僵尸状态,导致内存泄漏。
- 僵尸进程无法通过
kill -9命令清除,因为已终止的进程无法被再次终止。 - 父进程作为子进程的直接管理者,需要了解其派发任务的完成情况。
- 通过进程等待机制,父进程不仅能回收子进程资源,还能获取其退出状态信息。
三、进程等待方法

1、wait方法
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int* status);
-
作用:等待任意一个子进程终止或进入停止状态。
-
特点:如果没有已终止的子进程,父进程会阻塞(除非设置
WNOHANG选项)。 -
返回值:成功时返回被等待进程的PID,失败时返回-1。
-
参数说明:status:输出型参数,用于获取子进程退出状态。若不关心可设置为NULL。
例如,父进程创建子进程后,可以通过调用wait函数来等待子进程结束,并获取其退出状态信息:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
//child
int count = 10;
while(count--)
{
printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid());
sleep(1);
}
exit(0);
}
//father
int status = 0;
pid_t ret = wait(&status);
if(ret > 0)
{
//wait success
printf("wait child success...\n");
if(WIFEXITED(status))
{
//exit normal
printf("exit code:%d\n", WEXITSTATUS(status));
}
}
sleep(3);
return 0;
}
WIFEXITED(status):检查进程是否正常退出(本质是判断是否收到信号),若正常退出,退出信号为0;不正常退出的话,退出信号则为非0WEXITSTATUS(status):获取进程的退出码
我们可以使用以下监控脚本对进程进行实时监控:
while :; do ps axj | head -1 && ps axj | grep demo | grep -v grep;echo "######################";sleep 1;done
这时我们可以看到,当子进程退出后,父进程读取了子进程的退出信息,子进程也就不会变成僵尸进程了:

2、waitpid方法
pid_t waitpid(pid_t pid, int *status, int options);
-
作用:等待指定的子进程(或一组子进程)终止或状态变化。
-
特点:可以精确控制等待哪个子进程,支持非阻塞模式。
返回值
- 当waitpid正常执行时,它会返回被收集子进程的进程ID;
- 若设置了WNOHANG选项且未发现已退出的子进程,则返回0;
- 若调用过程中发生错误,则返回-1并设置相应errno值以指示错误类型。
参数说明
通过 pid 参数指定目标进程,options 提供额外控制(如 WNOHANG)。
-
pid

=-1:等待任意子进程(等效于wait)>0:等待其进程ID与pid相等的子进程。
-
status(输出型参数)
WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
-
options
0:默认阻塞等待WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
例如,创建子进程后,父进程可使用waitpid函数一直等待子进程(此时将waitpid的第三个参数设置为0),直到子进程退出后读取子进程的退出信息:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
pid_t id = fork();
if (id == 0)
{
//child
int count = 10;
while (count--)
{
printf("I am child...PID:%d, PPID:%d\n", getpid(), getppid());
sleep(1);
}
exit(0);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if (ret >= 0)
{
//wait success
printf("wait child success...\n");
if (WIFEXITED(status))
{
//exit normal
printf("exit code:%d\n", WEXITSTATUS(status));
}
else
{
//signal killed
printf("killed by siganl %d\n", status & 0x7F);
}
}
sleep(3);
return 0;
}
WIFEXITED(status):检查进程是否正常退出(本质是判断是否收到信号),若正常退出,退出信号为0;不正常退出的话,退出信号则为非0WEXITSTATUS(status):获取进程的退出码
运行结果:

在父进程运行过程中,我们可以尝试使用kill -9命令将子进程杀死,这时父进程也能等待子进程成功:

注意: 被信号杀死而退出的进程,其退出码将没有意义。
3、注意事项
当调用 wait/waitpid 时:
- 若子进程已退出,函数会立即返回,并释放相关资源,同时获取子进程的退出信息
- 若子进程仍在正常运行,调用可能会阻塞
- 若指定的子进程不存在,函数将立即返回错误

4、核心区别
| 特性 | wait | waitpid |
|---|---|---|
| 目标进程 | 任意一个子进程 | 指定 PID 的子进程(或进程组) |
| 阻塞行为 | 默认阻塞 | 可通过 WNOHANG 非阻塞轮询 |
| 进程选择灵活性 | 无选择,任意子进程 | 支持多种 PID 选项(见下文) |
| 使用场景 | 简单等待所有子进程 | 精确控制等待的进程或非阻塞检查 |
5、waitpid 的 PID 参数选项
waitpid 通过 pid 参数指定目标进程:
-
pid > 0:等待指定的子进程(PID =pid)。 -
pid = -1:等待任意子进程(等效于wait)。 -
pid = 0:等待与父进程同进程组的任意子进程。 -
pid < -1:等待进程组 ID 等于|pid|的任意子进程。
6、waitpid 的 options 选项
-
WNOHANG:非阻塞模式,如果没有子进程退出,立即返回 0。 -
WUNTRACED:返回已停止(但未终止)的子进程状态(如被SIGSTOP暂停)。 -
WCONTINUED(某些系统):返回已恢复运行的子进程状态(如收到SIGCONT)。
7、联系
-
wait是waitpid的特例:
wait(&status)等价于waitpid(-1, &status, 0)。
-
共享底层机制:两者均通过检查子进程的退出状态(
status)来获取信息,使用宏如WIFEXITED、WEXITSTATUS解析状态。
四、获取子进程状态
进程等待使用的两个函数——wait和waitpid,都有一个输出型参数status,该参数由操作系统负责填充。当传入NULL值时,表示父进程不关心子进程的退出状态;否则,操作系统将通过该参数向父进程反馈子进程的退出信息。
虽然status是一个整型变量,但不能简单地将其视为普通整数。它的不同比特位承载着不同的信息(本说明仅分析status的低16位):

status的低16位中,高8位存储进程的退出状态(退出码)。若进程因信号终止,低7位记录终止信号,第8位则作为core dump标志位。
Core Dump(了解即可)
- 在Linux中,core dump标志位(通常指进程的
ulimit -c设置或文件系统路径中的/proc/sys/kernel/core_pattern配置)用于控制系统在程序崩溃时是否生成core dump文件,以及如何生成。 - Core Dump(核心转储)文件 是当 Linux 程序异常崩溃(如段错误、非法指令、被
kill -6终止等)时,操作系统自动生成的一个内存快照文件。它记录了程序崩溃时的内存状态、寄存器值、堆栈信息等关键数据,主要用于调试和分析程序崩溃原因。

通过位操作可以轻松地从status变量中提取进程的退出码和退出信号:
exitCode = (status >> 8) & 0xFF; // 获取退出码
exitSignal = status & 0x7F; // 获取退出信号
系统提供了两个便捷的宏来处理这些信息:
WIFEXITED(status):检查进程是否正常退出(本质是判断是否收到信号),若正常退出,退出信号为0;不正常退出的话,退出信号则为非0WEXITSTATUS(status):获取进程的退出码

使用示例:
exitNormal = WIFEXITED(status); // 判断是否正常退出
exitCode = WEXITSTATUS(status); // 获取实际退出码
需要注意的是,当进程异常退出(被信号终止)时,其退出码将失去实际意义。
五、阻塞与非阻塞等待
1、进程的阻塞等待方式
上述示例展示了父进程创建并等待单个子进程的情况。实际上,我们还可以同时创建多个子进程,让父进程依次等待它们退出,这就是多进程创建与等待的编程模式。
比如,在以下代码中,我们一次性创建了10个子进程,并将它们的进程ID存入ids数组。每个子进程的退出码被设置为它在数组中的下标位置。随后,父进程使用waitpid函数逐个等待这10个子进程结束。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t ids[10];
for (int i = 0; i < 10; i++)
{
pid_t id = fork();
if (id == 0)
{
//child
printf("child process created successfully...PID:%d\n", getpid());
sleep(3);
exit(i); //将子进程的退出码设置为该子进程PID在数组ids中的下标
}
//father
ids[i] = id;
}
for (int i = 0; i < 10; i++)
{
int status = 0;
pid_t ret = waitpid(ids[i], &status, 0);
if (ret >= 0)
{
//wait child success
printf("wiat child success..PID:%d\n", ids[i]);
if (WIFEXITED(status))
{
//exit normal
printf("exit code:%d\n", WEXITSTATUS(status));
}
else
{
//signal killed
printf("killed by signal %d\n", status & 0x7F);
}
}
}
return 0;
}
运行结果:
运行代码,这时我们便可以看到父进程同时创建多个子进程,当子进程退出后,父进程再依次读取这些子进程的退出信息。

2、进程非阻塞等待实现
在之前的例子中,当子进程尚未退出时,父进程会持续等待,在此期间父进程无法执行其他任务,这种等待方式称为阻塞等待。
实际上,我们可以采用非阻塞等待的方式:在子进程未退出时,父进程可以继续处理其他事务,待子进程退出后再获取其状态信息。
实现方法很简单:只需在调用waitpid函数时,将第三个参数options设为WNOHANG。这样设置后,若子进程仍在运行,waitpid会立即返回0;若子进程已正常结束,则返回该子进程的PID。
例如,父进程可以定期调用waitpid进行检查。若子进程尚未退出,父进程可先处理其他任务,稍后再尝试获取子进程的退出状态信息。
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
typedef void (*handler_t)(); // 函数指针类型定义
std::vector<handler_t> handlers; // 任务处理函数容器
void task_one() {
printf("执行临时任务1\n");
}
void task_two() {
printf("执行临时任务2\n");
}
void load_tasks() {
handlers.push_back(task_one);
handlers.push_back(task_two);
}
void execute_tasks() {
if (handlers.empty()) load_tasks();
for (auto task : handlers) {
task();
}
}
int main() {
pid_t pid = fork();
if (pid < 0) {
printf("%s: 进程创建失败\n", __FUNCTION__);
return 1;
}
else if (pid == 0) { // 子进程
printf("子进程运行中,PID: %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("子进程仍在运行\n");
}
execute_tasks();
} while (ret == 0);
if (WIFEXITED(status) && ret == pid) {
printf("成功等待子进程5秒,返回码: %d\n", WEXITSTATUS(status));
}
else {
printf("等待子进程失败\n");
return 1;
}
}
return 0;
}
该代码要使用到g++编译器,首先我们要安装g++:
sudo yum install gcc-c++

auto 是 C++11 引入的特性,如果编译器较旧,需添加 -std=c++11:
g++ demo1.cpp -o demo1 -std=c++11
运行结果:
父进程定时轮询检查子进程的退出状态。若子进程仍在运行,父进程会先处理其他任务,稍后再继续轮询检查,直至子进程退出后获取其终止信息。

1019

被折叠的 条评论
为什么被折叠?



