如果child没有状态变化,如何取消waitpid?

How to cancel waitpid if child has no status change?

免责声明:C 的绝对新手,我以前主要使用 Java。

在许多 C 初学者教程中,waitpid 在进程管理示例中用于等待其 child 进程完成(或使用 WUNTRACED 等选项更改状态)。但是,如果没有通过直接用户输入或程序化(例如超时)发生此类状态更改,我找不到有关如何继续的任何信息。那么撤消waitpid有什么好方法呢?类似于 SIGCONT 表示停止的进程,而不是延迟 waitpid 的进程。

或者,如果这个想法没有意义,那么知道为什么会很有趣。

waitpid 的第三个参数采用一组标志。您想要包含 WNOHANG 标志,如果没有 child 进程退出,它会立即通知 waitpid 到 return。

添加此选项后,您将在循环中休眠一段时间,如果没有退出,则重试。重复直到 child 已 returned 或直到您的超时已过。

如果我建议使用 alarm() 怎么样? alarm() 在倒计时结束后交付 SIGALRM(有关详细信息,请参阅 alarm() 手册页)。但是从 signals 手册页来看,SIGALRM 默认处理是终止进程。因此,您需要注册一个信号处理程序来处理 SIGALRM。代码如下...

#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void sigalrm(int signo)
{
    return; // Do nothing !
}

int main()
{
    struct sigaction act, oldact;

    act.sa_handler = sigalrm;   // Set the signal handler
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

#ifdef SA_INTERRUPT // If interrupt defined set it to prevent the auto restart of sys-call
    act.sa_flags |= SA_INTERRUPT;
#endif

    sigaction(SIGALRM, &act, &oldact);

    pid_t fk_return = fork();
    if (fk_return == 0) {   // Child never returns
        for( ; ; );
    }

    unsigned int wait_sec = 5;
    alarm(wait_sec);    // Request for SIGALRM

    time_t start = time(NULL);
    waitpid(-1, NULL, 0);
    int tmp_errno = errno;  // save the errno state, it may be modified in between function calls.
    time_t end = time(NULL);

    alarm(0);  // Clear a pending alarm
    sigaction(SIGALRM, &oldact, NULL);

    if (tmp_errno == EINTR) {
        printf("Child Timeout, waited for %d sec\n", end - start);
        kill(fk_return, SIGINT);
        exit(1);
    }
    else if (tmp_errno != 0)    // Some other fatal error
        exit(1);

    /* Proceed further */

    return 0;
}

输出

Child Timeout, waited for 5 sec

注意:你不需要担心SIGCHLD,因为它的默认配置是忽略。

编辑

为了完整起见,保证SIGALRM没有投递到child。这是来自 alarm()

的手册页

Alarms created by alarm() are preserved across execve(2) and are not inherited by children created via fork(2).

编辑 2

我不知道为什么它一开始没有打动我。一种简单的方法是阻止 SIGCHLD 并调用支持超时选项的 sigtimedwait()。代码是这样的...

#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
    sigset_t sigmask;
    sigemptyset(&sigmask);
    sigaddset(&sigmask, SIGCHLD);
    sigprocmask(SIG_BLOCK, &sigmask, NULL);

    pid_t fk_return = fork();
    if (fk_return == 0) {   // Child never returns
        for( ; ; );
    }

    if (sigtimedwait(&sigmask, NULL, &((struct timespec){5, 0})) < 0) {
        if (errno == EAGAIN) {
            printf("Timeout\n");
            kill(fk_return, SIGINT);
            exit(1);
        }
    }

    waitpid(fk_return, NULL, 0);    // Child should have terminated by now.

    /* Proceed further */

    return 0;
}

输出

Timeout

在典型的 Unix 系统上等待进程死亡是绝对的 PI​​TA。可移植的方法是使用各种信号来中断 wait 函数:SIGALARM 用于超时,SIGTERM/SIGINT 和其他用于 "user input" 事件。这依赖于全局状态,因此可能无法做到。

不可移植的方法是在 BSD 上使用 pidfd_open with poll/epoll on Linux, kqueueEVFILT_PROC 过滤器。

请注意,在 Linux 这允许等待进程终止,您仍然必须通过 waitidP_PIDFD.

检索状态

如果您仍想混入 "user events",请将 signalfd 添加到 Linux 上的描述符列表或 BSD 上 kqueueEVFILT_SIGNAL 过滤器.

另一种可能的解决方案是生成一个 "process reaper" 线程,该线程负责收集所有进程并在您选择的进程对象中设置一些事件:futex word, eventfd etc. Waiting on such objects can be done with a timeout. This requires everyone to agree to use the same interface for process spawning which might or might not be reasonable. Afaik Java implementations use this strategy.