[英]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 并errno
( ECHILD
如果子进程不存在(从未创建或已经被收割)或者不是这个进程的子进程, 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==0
, waitpid()
应该只返回子 PID 或 -1( errno
)。 但是,由于检查非常便宜,我更愿意将其他所有内容都视为 ECHILD 错误,因为这样可以提供最可靠的结果。
调用者可以随意忽略返回值。 但是,如果他们想知道,如果成功,返回值为 0,否则返回 -1 并errno
(并且strerror(errno)
提供文本原因)。
我想以最健壮和通用的方式编写等待子进程完成的代码。
子进程由fork
系统调用创建。 最坏的情况是SIGCHLD
在fork
返回之前传递给父进程。 SIGCHLD
的默认信号操作是忽略该信号,以便后续的waitpid
调用无限期挂起。
在任何/多线程程序中处理子进程终止的健壮 POSIX 方法是:
sigprocmask/pthread_sigmask
阻止SIGCHLD
。 子线程继承父线程的信号掩码。 换句话说, main
函数应该最早阻塞信号。 (除非您的全局 C++ 对象构造函数或平台特定的构造函数在进入main
之前产生新线程,但据我所知,这超出了 C++ 标准或任何平台特定标准的范围/要求glibc
甚至可能永远挂起,如果在进入main
之前创建新线程,这是glibc
一个长期存在的错误)。sigwait
或sigwaitinfo
来接收已挂起的SIGCHLD
(如果有)或等待它。 在这种情况下,信号不会丢失。 有关此处提到的问题和解决方案的完整说明,请参阅sigwaitinfo
。
另请参阅名为“多线程进程中的信令”的pthread_sigmask
示例。
另一个 POSIX 选项是在调用fork
之前安装SIGCHLD
信号处理程序。 在信号处理程序内部,只能调用一小部分异步信号安全函数。 这通常过于严格,因此使用自管道技巧将信号处理委托给非信号上下文。 其他一些线程从信号处理程序read
管道并在“正常”非信号上下文中处理信号
Linux 提供了signalfd
系统调用,它本质上为您完成了自管道技巧,这是处理信号的最不棘手和最健壮的方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.