簡體   English   中英

管道保證在孩子退出后關閉

[英]Pipe guarantee to close after the child has exited

在下面的代碼中,依靠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;
}

我應該在 while(1) 循環中添加一些東西來檢測子終止嗎? 什么特定情況可能會發生並破壞此應用程序?

下面是一些改進的想法。 但是,我會浪費 CPU 周期嗎?

  1. 使用帶有特殊參數 0 的 kill 不會終止進程,但只檢查它是否響應: if (kill(pid, 0)) { break; /* child exited */ }; if (kill(pid, 0)) { break; /* child exited */ }; /* 如果 sig 為 0,則不發送信號,但仍進行錯誤檢查; 這可用於檢查進程 ID 或進程組 ID 是否存在。 https://linux.die.net/man/2/kill */

  2. 在 while(1) 循環中使用 waitpid 非阻塞檢查子進程是否已退出。

  3. 使用 select() 檢查管道可讀性以防止 read() 可能掛起?

謝謝!

關於你的想法:

  • 如果孩子產生自己的孩子,則read()將不會返回 0,直到其所有后代死亡或關閉 stdout 和 stderr。 如果不是,或者如果孩子總是比它的所有后代都活得更長,那么只等待read()返回 0 就足夠了,永遠不會引起問題。
  • 如果孩子死了但父母還沒有wait(2) ed,那么kill(pid, 0)會成功,就好像孩子還活着一樣(至少在 Linux 上),所以這不是一個有效的檢查從您的父程序中。
  • 非阻塞waitpid()本身似乎可以解決孩子有自己孩子的問題,但實際上會引入微妙的競爭條件。 如果孩子在waitpid()但在read()之前退出,則read()將阻塞,直到其余后代退出。
  • 就其本身而言,如果您以阻塞方式使用select() ,它並不比僅調用read() 如果您以非阻塞方式使用select() ,您最終只會在循環中消耗 CPU 時間。

我會做什么:

  • 為 SIGCHLD 添加一個 no-op 信號處理函數,以便在它發生時引起 EINTR。
  • 在開始循環之前在父級中阻止 SIGCHLD。
  • 使用非阻塞read ,並使用pselect(2)阻塞以避免永遠旋轉 CPU。
  • pselect期間,傳入一個沒有阻止 SIGCHLD 的sigset_t ,這樣就可以保證在它最終被發送時導致一個 EINTR。
  • 在循環中的某個地方,執行一個非阻塞的waitpid(2) ,並適當地處理它的返回。 (確保在阻止 SIGCHLD 之后但在第一次調用select之前至少執行一次此操作,否則您將遇到競爭條件。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM