简体   繁体   English

管道保证在孩子退出后关闭

[英]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 周期吗?

  1. 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 */

  2. Use waitpid non-blocking in the while(1) loop to check if child has exited.在 while(1) 循环中使用 waitpid 非阻塞检查子进程是否已退出。

  3. Use select() to check for pipe readability to prevent read() from possibly hanging?使用 select() 检查管道可读性以防止 read() 可能挂起?

Thanks!谢谢!

Regarding your ideas:关于你的想法:

  • If the child spawns children of its own, the 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 就足够了,永远不会引起问题。
  • If the child dies but the parent hasn't yet 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 上),所以这不是一个有效的检查从您的父程序中。
  • A non-blocking 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()将阻塞,直到其余后代退出。
  • On its own, if you used 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:我会做什么:

  • Add a no-op signal handler function for SIGCHLD, just so that it causes EINTR when it occurs.为 SIGCHLD 添加一个 no-op 信号处理函数,以便在它发生时引起 EINTR。
  • Block SIGCHLD in the parent before you start looping.在开始循环之前在父级中阻止 SIGCHLD。
  • Use non-blocking read s, and use pselect(2) to block to avoid spinning the CPU forever.使用非阻塞read ,并使用pselect(2)阻塞以避免永远旋转 CPU。
  • During the 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。
  • Somewhere in the loop, do a non-blocking 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.) (确保在阻止 SIGCHLD 之后但在第一次调用select之前至少执行一次此操作,否则您将遇到竞争条件。)

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

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