waitid 系统调用及示例
好的,我们来深入学习 waitid 系统调用
1. 函数介绍
在 Linux 系统中,当一个进程创建了子进程(使用 fork),父进程通常需要知道子进程何时结束(退出或被终止),以及它是如何结束的(正常退出码、被哪个信号杀死等)。这是进程管理和资源回收的重要环节。
waitid 系统调用就是用来让父进程(或具有适当权限的进程)等待一个或一组子进程的状态发生变化,并获取该变化的详细信息。
你可以把它想象成一个“进程状态监听器”。父进程调用 waitid 后,它会挂起(阻塞),直到它感兴趣的子进程发生了指定类型的事件(比如退出、被信号终止、停止、继续等)。当事件发生时,waitid 会返回,并把详细信息(哪个子进程、如何结束的)填充到一个结构体中。
waitid 相比于老一些的 wait 和 waitpid,提供了更强大和灵活的功能。
简单来说,waitid 就是让你用程序来“等待”并“获取”子进程的“死亡/停止/恢复”通知书,并且通知书上写得非常详细。
2. 函数原型
#include <sys/wait.h> // 包含 waitid 函数声明和相关常量
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
3. 功能
挂起调用进程,直到由 idtype 和 id 指定的一个或多个子进程的状态发生变化(变化类型由 options 指定)。当满足条件的子进程状态改变时,将详细的状态信息填充到 infop 指向的 siginfo_t 结构体中。
4. 参数详解
idtype:
- idtype_t 类型。
指定要等待的进程的类型。它决定了 id 参数的含义。常见的值有:
-
P_PID: 等待由 id 指定的特定进程 ID (PID) 的子进程。
-
P_PGID: 等待进程组 ID (PGID) 等于 id 的所有子进程。
-
P_ALL: 等待调用进程的所有子进程(此时 id 参数被忽略)。
id:
- id_t 类型。
其含义由 idtype 决定:
-
如果 idtype 是 P_PID,则 id 是要等待的子进程的 PID。
-
如果 idtype 是 P_PGID,则 id 是要等待的子进程组的 PGID。
-
如果 idtype 是 P_ALL,则 id 被忽略(通常设为 0)。
infop:
-
siginfo_t * 类型。
-
一个指向 siginfo_t 结构体的指针。当 waitid 成功返回时,该结构体会被内核填充为关于已改变状态的子进程的详细信息。
siginfo_t 结构体包含很多字段,关键的有:
-
si_pid: 导致状态改变的子进程的 PID。
-
si_status: 子进程的退出状态或导致其状态改变的信号编号。
si_code: 状态改变的原因代码,例如:
-
CLD_EXITED: 子进程通过 exit() 或从 main 返回正常退出。
-
CLD_KILLED: 子进程被信号杀死。
-
CLD_DUMPED: 子进程被信号杀死并产生了核心转储 (core dump)。
-
CLD_STOPPED: 子进程被信号(如 SIGSTOP)停止。
-
CLD_CONTINUED: 子进程从停止状态被 SIGCONT 信号恢复继续运行。
… 还有其他字段。
options:
- int 类型。
一个位掩码,用于指定要等待的状态变化类型以及调用的行为。可以是以下值的按位或 (|) 组合:
状态类型 (必须至少指定一个):
-
WEXITED: 等待子进程正常退出(调用 exit() 或从 main 返回)。
-
WSTOPPED: 等待子进程被停止(通常是收到 SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU 信号)。
-
WCONTINUED: 等待被停止的子进程恢复运行(收到 SIGCONT 信号)。
行为标志 (可选):
-
WNOHANG: 非阻塞。如果没有任何子进程的状态符合条件,waitid 立即返回 0,而不挂起调用进程。
-
WNOWAIT: 不收割。获取子进程状态信息,但不将其从内核的子进程表中删除。这意味着后续的 wait 调用仍可能获取到该子进程的信息。
5. 返回值
-
成功: 返回 0。
-
失败: 返回 -1,并设置全局变量 errno 来指示具体的错误原因。
6. 错误码 (errno)
-
ECHILD: 没有符合条件的子进程。例如,指定了一个不存在的 PID,或者使用 WNOHANG 时没有子进程处于可收割状态。
-
EINTR: 系统调用被信号中断。
-
EINVAL: idtype 或 options 参数无效。
7. 相似函数或关联函数
-
wait: 最基础的等待子进程退出的函数。它等待任意一个子进程退出,并返回 PID 和状态码(需要使用宏如 WIFEXITED, WEXITSTATUS 等来解析)。pid_t wait(int *wstatus);
-
waitpid: wait 的增强版。允许指定等待特定 PID 的子进程,或使用 WNOHANG 等选项。pid_t waitpid(pid_t pid, int *wstatus, int options);
-
wait3 / wait4: 更老的函数,功能与 waitpid 类似,但可以额外返回资源使用信息(struct rusage)。
-
siginfo_t: waitid 使用的关键数据结构,包含详细的子进程状态信息。
8. 示例代码
下面的示例演示了如何使用 waitid 来等待不同类型的子进程事件。
#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h> // 包含 waitid, siginfo_t 等
#include <signal.h> // 包含 kill, SIG* 常量
#include <string.h>
#include <errno.h>
// 辅助函数:打印 siginfo_t 中的信息
void print_siginfo(const siginfo_t *info) {
printf(" Child PID: %d\n", info->si_pid);
printf(" Signal/Exit Code: %d\n", info->si_status);
printf(" Reason Code: ");
switch (info->si_code) {
case CLD_EXITED:
printf("CLD_EXITED (Child called exit())\n");
printf(" Exit Status: %d\n", info->si_status);
break;
case CLD_KILLED:
printf("CLD_KILLED (Child was killed by signal)\n");
printf(" Signal Number: %d\n", info->si_status);
break;
case CLD_DUMPED:
printf("CLD_DUMPED (Child killed by signal and dumped core)\n");
printf(" Signal Number: %d\n", info->si_status);
break;
case CLD_STOPPED:
printf("CLD_STOPPED (Child was stopped by signal)\n");
printf(" Stop Signal Number: %d\n", info->si_status);
break;
case CLD_CONTINUED:
printf("CLD_CONTINUED (Child continued)\n");
// si_status for CLD_CONTINUED is not defined to be meaningful
break;
default:
printf("Unknown reason code: %d\n", info->si_code);
break;
}
}
int main() {
pid_t pid1, pid2, pid3;
siginfo_t info;
printf("--- Demonstrating waitid ---\n");
printf("Parent PID: %d\n", getpid());
// 1. 创建一个会正常退出的子进程
pid1 = fork();
if (pid1 == 0) {
// --- Child 1 ---
printf("[Child 1, PID %d] Running for 3 seconds then exiting with status 42.\n", getpid());
sleep(3);
exit(42);
}
// 2. 创建一个会被信号杀死的子进程
pid2 = fork();
if (pid2 == 0) {
// --- Child 2 ---
printf("[Child 2, PID %d] Running for 5 seconds then will be killed by SIGTERM.\n", getpid());
sleep(5);
// 这行不会执行到
exit(0);
}
// 3. 创建一个会停止和恢复的子进程
pid3 = fork();
if (pid3 == 0) {
// --- Child 3 ---
printf("[Child 3, PID %d] Running, then will stop and continue.\n", getpid());
printf("[Child 3] Entering loop, press Ctrl+Z in another terminal to stop me (if I'm foreground).\n");
printf("[Child 3] Or, the parent will send SIGSTOP and SIGCONT.\n");
int counter = 0;
while (counter < 10) {
printf("[Child 3] Working... %d\n", counter++);
sleep(1);
}
printf("[Child 3] Finished normally.\n");
exit(100);
}
// --- Parent Process ---
printf("[Parent] Created children: PID1=%d, PID2=%d, PID3=%d\n", pid1, pid2, pid3);
// 稍等一下,让子进程启动
sleep(1);
// 4. 向 Child 3 发送 SIGSTOP 使其停止
printf("\n[Parent] Sending SIGSTOP to Child 3 (PID %d)...\n", pid3);
if (kill(pid3, SIGSTOP) == -1) {
perror("[Parent] kill SIGSTOP");
}
// 等待 Child 3 停止
printf("[Parent] Waiting for Child 3 to stop using waitid(WSTOPPED)...\n");
memset(&info, 0, sizeof(info)); // 清零结构体
if (waitid(P_PID, pid3, &info, WSTOPPED) == -1) {
perror("[Parent] waitid for stop");
} else {
printf("[Parent] Detected Child 3 stopped:\n");
print_siginfo(&info);
}
// 5. 等待 Child 1 正常退出
printf("\n[Parent] Waiting for Child 1 to exit using waitid(WEXITED)...\n");
memset(&info, 0, sizeof(info));
if (waitid(P_PID, pid1, &info, WEXITED) == -1) {
perror("[Parent] waitid for exit pid1");
} else {
printf("[Parent] Detected Child 1 exited:\n");
print_siginfo(&info);
}
// 6. 向 Child 2 发送 SIGTERM 使其终止
printf("\n[Parent] Sending SIGTERM to Child 2 (PID %d)...\n", pid2);
if (kill(pid2, SIGTERM) == -1) {
perror("[Parent] kill SIGTERM");
}
// 等待 Child 2 被杀死
printf("[Parent] Waiting for Child 2 to be killed using waitid(WEXITED)...\n");
memset(&info, 0, sizeof(info));
if (waitid(P_PID, pid2, &info, WEXITED) == -1) {
perror("[Parent] waitid for exit pid2");
} else {
printf("[Parent] Detected Child 2 killed/exited:\n");
print_siginfo(&info);
}
// 7. 向 Child 3 发送 SIGCONT 使其恢复
printf("\n[Parent] Sending SIGCONT to Child 3 (PID %d)...\n", pid3);
if (kill(pid3, SIGCONT) == -1) {
perror("[Parent] kill SIGCONT");
}
// 等待 Child 3 恢复运行 (这个可能不会立即发生,取决于子进程何时真正恢复)
// 更常见的是等待它最终退出
printf("[Parent] Waiting for Child 3 to continue and then exit using waitid(WCONTINUED | WEXITED)...\n");
printf("[Parent] (WCONTINUED detection might be unreliable, waiting for exit instead)\n");
memset(&info, 0, sizeof(info));
// 通常我们只等待最终的退出
if (waitid(P_PID, pid3, &info, WEXITED) == -1) {
perror("[Parent] waitid for exit pid3");
} else {
printf("[Parent] Detected Child 3 exited:\n");
print_siginfo(&info);
}
// 8. 演示 WNOHANG (非阻塞)
printf("\n[Parent] Demonstrating WNOHANG...\n");
printf("[Parent] Calling waitid(P_ALL, 0, info, WEXITED | WNOHANG)...\n");
memset(&info, 0, sizeof(info));
int result = waitid(P_ALL, 0, &info, WEXITED | WNOHANG);
if (result == -1) {
perror("[Parent] waitid WNOHANG");
} else if (result == 0) {
// 如果返回 0,表示成功调用,但没有符合条件的子进程状态改变
// 因为我们已经等待了所有子进程退出,所以这里应该没有更多可收割的
printf("[Parent] WNOHANG returned 0: No children available to wait for.\n");
}
printf("\n[Parent] All children have been waited for. Parent exiting.\n");
printf("\n--- Summary ---\n");
printf("1. waitid(idtype, id, infop, options) waits for child process state changes.\n");
printf("2. idtype/id let you specify which child/children to wait for (PID, PGID, ALL).\n");
printf("3. options specify what events to wait for (WEXITED, WSTOPPED, WCONTINUED).\n");
printf("4. WNOHANG makes it non-blocking. WNOWAIT gets status without reaping.\n");
printf("5. infop (siginfo_t*) provides detailed information about the event.\n");
printf("6. It's more flexible and informative than wait/waitpid.\n");
return 0;
}
9. 编译和运行
# 假设代码保存在 waitid_example.c 中
gcc -o waitid_example waitid_example.c
# 运行程序
./waitid_example
10. 预期输出
--- Demonstrating waitid ---
Parent PID: 12345
[Child 1, PID 12346] Running for 3 seconds then exiting with status 42.
[Child 2, PID 12347] Running for 5 seconds then will be killed by SIGTERM.
[Child 3, PID 12348] Running, then will stop and continue.
[Child 3] Entering loop, press Ctrl+Z in another terminal to stop me (if I'm foreground).
[Child 3] Or, the parent will send SIGSTOP and SIGCONT.
[Parent] Created children: PID1=12346, PID2=12347, PID3=12348
[Parent] Sending SIGSTOP to Child 3 (PID 12348)...
[Parent] Waiting for Child 3 to stop using waitid(WSTOPPED)...
[Child 3] Working... 0
[Child 3] Working... 1
[Parent] Detected Child 3 stopped:
Child PID: 12348
Signal/Exit Code: 19
Reason Code: CLD_STOPPED (Child was stopped by signal)
Stop Signal Number: 19
[Parent] Waiting for Child 1 to exit using waitid(WEXITED)...
[Child 1] Running for 3 seconds then exiting with status 42.
[Parent] Detected Child 1 exited:
Child PID: 12346
Signal/Exit Code: 42
Reason Code: CLD_EXITED (Child called exit())
Exit Status: 42
[Parent] Sending SIGTERM to Child 2 (PID 12347)...
[Parent] Waiting for Child 2 to be killed using waitid(WEXITED)...
[Child 2] Running for 5 seconds then will be killed by SIGTERM.
[Parent] Detected Child 2 killed/exited:
Child PID: 12347
Signal/Exit Code: 15
Reason Code: CLD_KILLED (Child was killed by signal)
Signal Number: 15
[Parent] Sending SIGCONT to Child 3 (PID 12348)...
[Parent] Waiting for Child 3 to continue and then exit using waitid(WCONTINUED | WEXITED)...
[Parent] (WCONTINUED detection might be unreliable, waiting for exit instead)
[Child 3] Working... 2
[Child 3] Working... 3
[Child 3] Working... 4
[Child 3] Working... 5
[Child 3] Working... 6
[Child 3] Working... 7
[Child 3] Working... 8
[Child 3] Working... 9
[Child 3] Finished normally.
[Parent] Detected Child 3 exited:
Child PID: 12348
Signal/Exit Code: 100
Reason Code: CLD_EXITED (Child called exit())
Exit Status: 100
[Parent] Demonstrating WNOHANG...
[Parent] Calling waitid(P_ALL, 0, info, WEXITED | WNOHANG)...
[Parent] WNOHANG returned 0: No children available to wait for.
[Parent] All children have been waited for. Parent exiting.
--- Summary ---
1. waitid(idtype, id, infop, options) waits for child process state changes.
2. idtype/id let you specify which child/children to wait for (PID, PGID, ALL).
3. options specify what events to wait for (WEXITED, WSTOPPED, WCONTINUED).
4. WNOHANG makes it non-blocking. WNOWAIT gets status without reaping.
5. infop (siginfo_t*) provides detailed information about the event.
6. It's more flexible and informative than wait/waitpid.
11. 总结
waitid 是一个功能强大且信息丰富的系统调用,用于等待子进程状态变化。
核心优势:
-
灵活性高:可以精确指定等待哪个进程/进程组,以及等待哪种类型的事件(退出、停止、恢复)。
-
信息详细:通过 siginfo_t 结构体返回非常详细的子进程状态信息,比 wait/waitpid 的 wstatus 整数更易于理解和使用。
-
功能完整:支持停止/恢复事件的等待(WSTOPPED, WCONTINUED)。
参数:idtype/id 定义范围,options 定义事件类型和行为,infop 接收结果。
使用场景:
-
需要精确控制等待哪个子进程。
-
需要区分子进程是正常退出、被信号杀死还是停止/恢复。
-
编写复杂的进程管理器或守护进程。
与 wait/waitpid 的关系:
-
wait(&status) 基本等价于 waitpid(-1, &status, 0)。
-
waitpid(pid, &status, options) 功能是 waitid 的子集。
-
waitid 提供了 waitpid 所没有的 WSTOPPED/WCONTINUED 等选项(除非使用非标准扩展),以及更详细的信息返回方式。