简体   繁体   English

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

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

I am writing a C library that at some point forks another process and then waits for its completion.我正在编写一个 C 库,它在某些时候会派生另一个进程,然后等待其完成。

I'd like to write the code that waits for the child process completion in the most robust and generic way, to take care of all possible scenarios, such as the calling process spawning other child processes, receiving signals etc.我想以最健壮和通用的方式编写等待子进程完成的代码,以处理所有可能的情况,例如调用进程产生其他子进程、接收信号等。

Does the following C code use waitpid properly, ie in the most robust way?以下 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
}

Yes, waitpid(child_pid, ...) is the most robust way.是的, waitpid(child_pid, ...)是最健壮的方式。

It will return child_pid if the child process has exited, -1 with errno set if an error occurs ( ECHILD if the child process does not exist (was never created or has already been reaped) or is not a child of this process, EINVAL if the options (third parameter) had an invalid value, or EINTR if a signal was delivered to a signal handler that was not installed with SA_RESTART flags), or 0 if WNOHANG option (third parameter) was specified and the child process has not yet exited.如果子进程已经退出,它将返回 child_pid,如果发生错误,则返回 -1 并errnoECHILD如果子进程不存在(从未创建或已经被收割)或者不是这个进程的子进程, EINVAL如果选项(第三个参数)有一个无效值,或者EINTR如果信号被传递到没有安装 SA_RESTART 标志的信号处理程序),或者0如果指定了WNOHANG选项(第三个参数)并且子进程尚未退出.

I would recommend a slight change, however:但是,我建议稍作更改:

/* 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;
}

In Linux and POSIXy systems, process ID's are positive integers.在 Linux 和 POSIXy 系统中,进程 ID 是正整数。 As you can see in the man 2 waitpid man page, zero and negative PIDs refer to process groups, and -1 to any child process.正如您在man 2 waitpid手册页中所见,零和负 PID 表示进程组,-1 表示任何子进程。 Process 1 is special, init ;进程 1 比较特殊, init it is the one that never exits and sets up the rest of the userspace.它永远不会退出并设置用户空间的其余部分。 So, the smallest PID a child of the current process can ever have is 2.因此,当前进程的子进程可以拥有的最小 PID 是 2。

I do consider it sensible to use the proper types for these: pid_t for process IDs, and for example size_t for memory sizes of objects (including the return value of say strlen() .)我确实认为为这些使用正确的类型是明智的: pid_t用于进程 ID,例如size_t用于对象的内存大小(包括 say strlen()的返回值。)

Providing the status pointer (so that the caller can check it with WIFEXITED() + WEXITSTATUS() or WIFSIGNALED() + WTERMSIG() ) is a convenience, since any callers not interested in it can provide a NULL .提供status指针(以便调用者可以使用WIFEXITED() + WEXITSTATUS()WIFSIGNALED() + WTERMSIG() )很方便,因为任何对其不感兴趣的调用者都可以提供NULL ( NULL is explicitly allowed for the status pointer for wait() and waitpid() .) NULL明确允许用于wait()waitpid()的状态指针。)

Technically, with options==0 , waitpid() should only ever return either the child PID, or -1 (with errno set).从技术上讲,使用options==0waitpid()应该只返回子 PID 或 -1( errno )。 However, since the check is so cheap, I prefer to treat everything else as an ECHILD error, since that gives the most robust results.但是,由于检查非常便宜,我更愿意将其他所有内容都视为 ECHILD 错误,因为这样可以提供最可靠的结果。

The caller is free to ignore the return value.调用者可以随意忽略返回值。 However, if they want to know, the return value is 0 if successful, otherwise -1 with errno set (and strerror(errno) provides the textual reason).但是,如果他们想知道,如果成功,返回值为 0,否则返回 -1 并errno (并且strerror(errno)提供文本原因)。

I'd like to write the code that waits for the child process completion in the most robust and generic way.我想以最健壮和通用的方式编写等待子进程完成的代码。

A child process is created by fork syscall.子进程由fork系统调用创建。 The worst case scenario is that SIGCHLD is delivered to the parent process before fork returns.最坏的情况是SIGCHLDfork返回之前传递给父进程。 The default signal action for SIGCHLD is to ignore the signal, so that the subsequent waitpid call hangs indefinitely. SIGCHLD的默认信号操作是忽略该信号,以便后续的waitpid调用无限期挂起。


The robust POSIX way to handle termination of child processes in any/multi-threaded program is:在任何/多线程程序中处理子进程终止的健壮 POSIX 方法是:

  1. The main thread blocks SIGCHLD using sigprocmask/pthread_sigmask before any extra threads are created.在创建任何额外线程之前,主线程使用sigprocmask/pthread_sigmask阻止SIGCHLD Child threads inherit the signal mask of the parent thread.子线程继承父线程的信号掩码。 In other words, main function should block the signal earliest.换句话说, main函数应该最早阻塞信号。 (Unless your global C++ object constructor functions or platform specific constructior functions spawn new threads before main is entered, but that's outside of the scope/requirements of the C++ standard, or any platform specific standard, to my knowledge. glibc may even hang forever if new threads are created before main is entered, and that has been a long standing bug of glibc ). (除非您的全局 C++ 对象构造函数或平台特定的构造函数在进入main之前产生新线程,但据我所知,这超出了 C++ 标准或任何平台特定标准的范围/要求glibc甚至可能永远挂起,如果在进入main之前创建新线程,这glibc一个长期存在的错误)。
  2. Once child processes are created, one thread must call sigwait or sigwaitinfo to recieve a SIGCHLD that has been pending, if any, or wait for it.一旦创建了子进程,一个线程必须调用sigwaitsigwaitinfo来接收已挂起的SIGCHLD (如果有)或等待它。 No signal loss is possible in this case.在这种情况下,信号不会丢失。

See sigwaitinfo for full description of the issues mentioned here and the solution.有关此处提到的问题和解决方案的完整说明,请参阅sigwaitinfo

Also see pthread_sigmask example called "Signaling in a Multi-Threaded Process".另请参阅名为“多线程进程中的信令”的pthread_sigmask示例。


Another POSIX option is that a SIGCHLD signal handler is installed before fork is called.另一个 POSIX 选项是在调用fork之前安装SIGCHLD信号处理程序。 Insider the signal handler only a small subset of async-signal-safe functions can be called.在信号处理程序内部,只能调用一小部分异步信号安全函数。 That is often too restrictive, so that self-pipe trick is used to delegate signal processing to a non-signal context.这通常过于严格,因此使用自管道技巧将信号处理委托给非信号上下文。 Some other thread read s that pipe from the signal handler and handles the signal in the "normal" non-signal context其他一些线程从信号处理程序read管道并在“正常”非信号上下文中处理信号


Linux provides signalfd syscall that essentially does the self-pipe trick for you, and this is the least tricky and most robust way to handle signals. Linux 提供了signalfd系统调用,它本质上为您完成了自管道技巧,这是处理信号的最不棘手和最健壮的方法。

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

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