Linux C中wait函数如何获取子进程退出状态?

99ANYc3cd6
预计阅读时长 27 分钟
位置: 首页 C语言 正文

进程的终止与回收

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

linux c语言wait
(图片来源网络,侵删)

为什么需要僵尸进程? 父进程可能需要知道子进程是如何结束的(成功还是失败,错误码是什么),僵尸进程就是子进程留给父进程的“成绩单”,如果父进程不读取这个“成绩单”,子进程就会一直保持僵尸状态。

waitwaitpid 的作用 这两个函数就是父进程用来“读取成绩单”的,父进程调用它们后,会阻塞(暂停执行)直到一个子进程结束,然后系统会回收该子进程的僵尸进程条目,使其彻底消失,这个过程称为进程回收等待子进程


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 是一个整型,它通过不同的位组合来传递信息,我们通常使用宏来解析它:

linux c语言wait
(图片来源网络,侵删)
  • WIFEXITED(status): 如果子进程是正常退出(调用了 exitreturn),则此宏为真。
    • 如果为真,可以用 WEXITSTATUS(status) 获取子进程的退出码(即 exit() 的参数或 main 函数的 return 值)。
  • WIFSIGNALED(status): 如果子进程是被信号杀死SIGKILL, SIGSEGV),则此宏为真。
    • 如果为真,可以用 WTERMSIG(status) 获取导致子进程终止的信号编号
  • WIFSTOPPED(status): 如果子进程是暂停状态(由 SIGSTOPSIGTSTP 信号导致),则此宏为真。(这在作业控制中更常见)。
  • WIFCONTINUED(status): 如果子进程从暂停状态继续运行,则此宏为真。

注意WIFEXITEDWIFSIGNALED 是互斥的,一个子进程只能以一种方式结束。


waitpid 函数

waitpidwait 的一个更强大、更灵活的版本,它允许父进程等待特定的子进程,并且可以控制是否阻塞。

函数原型

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);

参数

  1. pid: 指定要等待的子进程。

    • pid > 0: 等待进程ID为 pid 的子进程。
    • pid == 0: 等待任何一个与调用者同组的子进程。
    • pid == -1: 等待任何一个子进程。这和 wait() 的功能完全一样
    • pid < -1: 等待进程组ID为 pid 的绝对值的任何子进程。
  2. int *status: 和 wait 函数一样,用于获取子进程的终止状态,不关心则传 NULL

    linux c语言wait
    (图片来源网络,侵删)
  3. int options: 控制函数行为的标志位,可以通过 组合使用。

    • 0: 默认行为,阻塞等待,直到子进程结束。
    • WNOHANG: 非阻塞模式,如果指定的子进程没有结束,waitpid 会立即返回 0,而不是挂起父进程,父进程可以稍后再试。
    • WUNTRACED: 同时也返回处于暂停状态的子进程信息。
    • WCONTINUED: 同时也返回从暂停状态继续运行的子进程信息。

返回值

  • 成功:
    • 返回已结束子进程的进程ID (PID)
    • WNOHANG 模式下,如果没有子进程结束,返回 0
  • 失败:返回 -1,并设置 errno(没有符合条件的子进程)。

代码示例

下面通过几个例子来对比 waitwaitpid 的用法。

示例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 的非阻塞模式

这个例子中,父进程创建子进程后,使用 waitpidWNOHANG 选项,每隔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。

最佳实践:

  1. 总是检查返回值waitwaitpid 都可能失败,调用后务必检查返回值是否为 -1
  2. 使用 waitpid 替代 wait:在大多数情况下,waitpid 是更好的选择,即使你想等待任意子进程,也可以使用 waitpid(-1, &status, 0),这比 wait 更明确。
  3. 处理僵尸进程:父进程必须调用 waitwaitpid 来回收子进程,如果父进程在子进程之前结束,子进程会被 init 进程(PID 1)接管,由 init 进程来回收,从而避免僵尸进程,但在一个程序中,父进程应该负责清理自己的子进程。
  4. 解析状态码:当你关心子进程的退出原因时,一定要使用 WIFEXITED, WEXITSTATUS 等宏来解析 status 参数,而不是直接打印或使用它。

希望这个详细的解释能帮助你完全理解 Linux C 语言中的 waitwaitpid 函数!

-- 展开阅读全文 --
头像
织梦系统默认幻灯片怎么用?
« 上一篇 02-21
织梦首页不见了怎么找回?
下一篇 » 02-21

相关文章

取消
微信二维码
支付宝二维码

目录[+]