繁体   English   中英

在 C 中等待子进程终止的最佳实践

[英]Best practice for waiting for a child process termination in C

我正在编写一个 C 库,它在某些时候会派生另一个进程,然后等待其完成。

我想以最健壮和通用的方式编写等待子进程完成的代码,以处理所有可能的情况,例如调用进程产生其他子进程、接收信号等。

以下 C 代码是否正确使用了waitpid ,即以最健壮的方式?

void waitForChildProcess(int child_pid) {
    int rc, err;
    do {
        //waiting only for my own child and only for its termination.
        //The status value is irrelevant (I think) because option '0' should mean 
        //to only wait for a child termination event 
        // and I don't care about the child's exit code:
        rc = waitpid(child_pid, NULL, 0);
        err = errno;
    } while (rc == -1 && err == EINTR); //ignoring a signal
}

是的, waitpid(child_pid, ...)是最健壮的方式。

如果子进程已经退出,它将返回 child_pid,如果发生错误,则返回 -1 并errnoECHILD如果子进程不存在(从未创建或已经被收割)或者不是这个进程的子进程, EINVAL如果选项(第三个参数)有一个无效值,或者EINTR如果信号被传递到没有安装 SA_RESTART 标志的信号处理程序),或者0如果指定了WNOHANG选项(第三个参数)并且子进程尚未退出.

但是,我建议稍作更改:

/* Wait for child process to exit.
 * @child_pid   Process ID of the child process
 * @status      Pointer to where the child status
 *              is stored; may be NULL
 * @return       0  if success
 *              -1  if an error occurs, see errno.
*/
int waitForChildProcess(pid_t child_pid, int *status)
{
    int rc;

    if (child_pid <= 1) {
        errno = EINVAL;
        return -1;
    }

    do {
        rc = waipid(child_pid, status, 0);
    } while (rc == -1 && errno == EINTR);
    if (rc == child_pid)
        return 0;

    /* This should not happen, but let's be careful. */
    if (rc != -1)
        errno = ECHILD;

    return -1;
}

在 Linux 和 POSIXy 系统中,进程 ID 是正整数。 正如您在man 2 waitpid手册页中所见,零和负 PID 表示进程组,-1 表示任何子进程。 进程 1 比较特殊, init 它永远不会退出并设置用户空间的其余部分。 因此,当前进程的子进程可以拥有的最小 PID 是 2。

我确实认为为这些使用正确的类型是明智的: pid_t用于进程 ID,例如size_t用于对象的内存大小(包括 say strlen()的返回值。)

提供status指针(以便调用者可以使用WIFEXITED() + WEXITSTATUS()WIFSIGNALED() + WTERMSIG() )很方便,因为任何对其不感兴趣的调用者都可以提供NULL NULL明确允许用于wait()waitpid()的状态指针。)

从技术上讲,使用options==0waitpid()应该只返回子 PID 或 -1( errno )。 但是,由于检查非常便宜,我更愿意将其他所有内容都视为 ECHILD 错误,因为这样可以提供最可靠的结果。

调用者可以随意忽略返回值。 但是,如果他们想知道,如果成功,返回值为 0,否则返回 -1 并errno (并且strerror(errno)提供文本原因)。

我想以最健壮和通用的方式编写等待子进程完成的代码。

子进程由fork系统调用创建。 最坏的情况是SIGCHLDfork返回之前传递给父进程。 SIGCHLD的默认信号操作是忽略该信号,以便后续的waitpid调用无限期挂起。


在任何/多线程程序中处理子进程终止的健壮 POSIX 方法是:

  1. 在创建任何额外线程之前,主线程使用sigprocmask/pthread_sigmask阻止SIGCHLD 子线程继承父线程的信号掩码。 换句话说, main函数应该最早阻塞信号。 (除非您的全局 C++ 对象构造函数或平台特定的构造函数在进入main之前产生新线程,但据我所知,这超出了 C++ 标准或任何平台特定标准的范围/要求glibc甚至可能永远挂起,如果在进入main之前创建新线程,这glibc一个长期存在的错误)。
  2. 一旦创建了子进程,一个线程必须调用sigwaitsigwaitinfo来接收已挂起的SIGCHLD (如果有)或等待它。 在这种情况下,信号不会丢失。

有关此处提到的问题和解决方案的完整说明,请参阅sigwaitinfo

另请参阅名为“多线程进程中的信令”的pthread_sigmask示例。


另一个 POSIX 选项是在调用fork之前安装SIGCHLD信号处理程序。 在信号处理程序内部,只能调用一小部分异步信号安全函数。 这通常过于严格,因此使用自管道技巧将信号处理委托给非信号上下文。 其他一些线程从信号处理程序read管道并在“正常”非信号上下文中处理信号


Linux 提供了signalfd系统调用,它本质上为您完成了自管道技巧,这是处理信号的最不棘手和最健壮的方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM