Труба гарантированно закрывается после выхода ребенка - PullRequest
0 голосов
/ 17 октября 2018

В приведенном ниже коде безопасно ли полагаться на сбой 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) для обнаружения дочернего завершения?Какой конкретный сценарий может произойти и сломать это приложение?

Некоторые мысли об улучшениях ниже.Однако я бы просто тратить циклы процессора?

  1. Используйте команду kill со специальным аргументом 0, который не завершит процесс, а просто проверит, реагирует ли он: if (kill(pid, 0)) { break; /* child exited */ }; / * Если sig равен 0, то сигнал не отправляется, нопроверка ошибок все еще выполняется;это можно использовать для проверки существования идентификатора процесса или идентификатора группы процессов.https://linux.die.net/man/2/kill * /

  2. Используйте неблокирование waitpid в цикле while (1), чтобы проверить, вышел ли ребенок.

  3. Используйте select (), чтобы проверить читаемость канала, чтобы предотвратить возможное зависание read ()?

Спасибо!

1 Ответ

0 голосов
/ 19 октября 2018

Относительно ваших идей:

  • Если ребенок порождает своих собственных детей, read() не вернет 0, пока все его потомки не умрут или не закроют stdout и stderr.Если это не так, или если ребенок всегда переживает всех своих потомков, то достаточно просто дождаться, пока read() вернет 0, и не вызовет проблем.
  • Если ребенок умрет, нородитель еще не wait(2) редактировал его, тогда kill(pid, 0) будет успешным, как если бы ребенок был еще жив (по крайней мере, в Linux), так что это не эффективная проверка из вашей родительской программы.
  • Сам по себе неблокирующий waitpid() мог бы решить проблему с ребенком, имеющим собственных детей, но фактически привел бы к тонкому состоянию расы.Если ребенок вышел сразу после waitpid(), но до read(), тогда read() будет блокироваться до тех пор, пока не выйдут остальные потомки.
  • Сам по себе, если вы использовали select() вблокировка, это не лучше, чем просто позвонить read().Если бы вы использовали select() неблокирующим образом, вы бы просто сожгли цикл ЦП в цикле.

Что бы я сделал:

  • Добавьте функцию-обработчик сигнала no-op для SIGCHLD, чтобы он вызывал EINTR при его возникновении.
  • Блокируйте SIGCHLD в родительском элементе перед началом цикла.
  • Используйте неблокирующую reads, и используйте pselect(2), чтобы заблокировать, чтобы не вращать процессор вечно.
  • В течение pselect, передайте sigset_t, который не заблокирован SIGCHLD, так что гарантированно вызовет EINTRдля этого, когда он в конечном итоге будет отправлен.
  • Где-нибудь в цикле, сделайте неблокирующую waitpid(2) и обработайте ее возврат соответствующим образом.(Убедитесь, что вы делаете это по крайней мере один раз после блокировки SIGCHLD, но перед первым вызовом select, иначе у вас будет состояние гонки.)
...