[英]Pipe guarantee to close after the child has exited
In the code below, is it safe to rely on read() failure to detect termination of child?在下面的代码中,依靠read() 失败来检测孩子的终止是否安全?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
int pipefd[2];
pipefd[0] = 0;
pipefd[1] = 0;
pipe(pipefd);
pid_t pid = fork();
if (pid == 0)
{
// child
close(pipefd[0]); // close unused read end
while ((dup2(pipefd[1], STDOUT_FILENO) == -1) && (errno == EINTR)) {} // send stdout to the pipe
while ((dup2(pipefd[1], STDERR_FILENO) == -1) && (errno == EINTR)) {} // send stderr to the pipe
close(pipefd[1]); // close unused write end
char *argv[3];
argv[0] = "worker-app";
argv[1] = NULL;
argv[2] = NULL;
execvp("./worker-app", argv);
printf("failed to execvp, errno %d\n", errno);
exit(EXIT_FAILURE);
}
else if (pid == -1)
{
}
else
{
// parent
close(pipefd[1]); // close the write end of the pipe in the parent
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
while (1) // <= here is it safe to rely on read below to break from this loop ?
{
ssize_t count = read(pipefd[0], buffer, sizeof(buffer)-1);
printf("pipe read return %d\n", (int)count);
if (count > 0)
{
printf("child: %s\n", buffer);
}
else if (count == 0)
{
printf("end read child pipe\n", buffer);
break;
}
else if (count == -1)
{
if (errno == EINTR)
{ continue;
}
printf("error read child pipe\n", buffer);
break;
}
}
close(pipefd[0]); // close read end, prevent descriptor leak
int waitStatus = 0;
waitpid(pid, &waitStatus, 0);
}
fprintf(stdout, "All work completed :-)\n");
return EXIT_SUCCESS;
}
Should I add something in the while(1) loop to detect child termination?我应该在 while(1) 循环中添加一些东西来检测子终止吗? What specific scenario could happen and break this app ?
什么特定情况可能会发生并破坏此应用程序?
Some thoughts of improvements below.下面是一些改进的想法。 However would I just waste CPU cycles?
但是,我会浪费 CPU 周期吗?
Use kill with special argument 0 that won't kill the process but just check if it is responsive: if (kill(pid, 0)) { break; /* child exited */ };
使用带有特殊参数 0 的 kill 不会终止进程,但只检查它是否响应:
if (kill(pid, 0)) { break; /* child exited */ };
if (kill(pid, 0)) { break; /* child exited */ };
/* If sig is 0, then no signal is sent, but error checking is still performed; /* 如果 sig 为 0,则不发送信号,但仍进行错误检查; this can be used to check for the existence of a process ID or process group ID.
这可用于检查进程 ID 或进程组 ID 是否存在。 https://linux.die.net/man/2/kill */
https://linux.die.net/man/2/kill */
Use waitpid non-blocking in the while(1) loop to check if child has exited.在 while(1) 循环中使用 waitpid 非阻塞检查子进程是否已退出。
Use select() to check for pipe readability to prevent read() from possibly hanging?使用 select() 检查管道可读性以防止 read() 可能挂起?
Thanks!谢谢!
Regarding your ideas:关于你的想法:
read()
won't return 0 until all of its descendants either die or close stdout and stderr.read()
将不会返回 0,直到其所有后代死亡或关闭 stdout 和 stderr。 If it doesn't, or if the child always outlives all of its descendants, then just waiting for read()
to return 0 is good enough and won't ever cause a problem.read()
返回 0 就足够了,永远不会引起问题。wait(2)
ed on it, then kill(pid, 0)
will succeed as if the child were still alive (at least on Linux), so this isn't an effective check from within your parent program.wait(2)
ed,那么kill(pid, 0)
会成功,就好像孩子还活着一样(至少在 Linux 上),所以这不是一个有效的检查从您的父程序中。waitpid()
on its own would appear to fix the problem with the child having children of its own, but would actually introduce a subtle race condition.waitpid()
本身似乎可以解决孩子有自己孩子的问题,但实际上会引入微妙的竞争条件。 If the child exited right after the waitpid()
but before the read()
, then the read()
would block until the rest of the descendants exited.waitpid()
但在read()
之前退出,则read()
将阻塞,直到其余后代退出。select()
in a blocking way, it's no better than just calling read()
.select()
,它并不比仅调用read()
。 If you used select()
in a non-blocking way, you'd just end up burning CPU time in a loop.select()
,您最终只会在循环中消耗 CPU 时间。 What I'd do:我会做什么:
read
s, and use pselect(2)
to block to avoid spinning the CPU forever.read
,并使用pselect(2)
阻塞以避免永远旋转 CPU。pselect
, pass in a sigset_t
that doesn't have SIGCHLD blocked, so that it's guaranteed to cause an EINTR for it when it eventually gets sent.pselect
期间,传入一个没有阻止 SIGCHLD 的sigset_t
,这样就可以保证在它最终被发送时导致一个 EINTR。waitpid(2)
, and handle its return appropriately.waitpid(2)
,并适当地处理它的返回。 (Make sure you do this at least once after blocking SIGCHLD but before calling select
for the first time, or you'll have a race condition.) select
之前至少执行一次此操作,否则您将遇到竞争条件。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.