【问题标题】:cleaning child processes with handler with waitpid pselect fork sigaction使用带有 waitpid pselect fork sigaction 的处理程序清理子进程
【发布时间】:2014-08-16 10:29:09
【问题描述】:

我有一个将消息接收到套接字的服务器,并且对于接收到的每条消息,执行一个 fork exec。这部分似乎工作正常。

但我需要在非阻塞模式下执行此操作,因此我创建了一个处理程序以使用 waitpid() 正确清理所有已终止的子进程(如论坛中的许多主题中所述)。

问题是这个处理程序对我的pselect 命令生成了一个中断的系统调用,它会停止程序并显示以下消息:
"select(): Interrupted system call"

我在“防止竞争条件”之类的论坛上找到了一些关于这个问题的解释,所以我尝试使用sigprocmask() 来阻止几个信号,但没有成功。

我确信这是一个微不足道的问题,但这是我第一次使用这种程序。

我需要一些帮助。 提前致谢。

这是程序:

void
clean_up_child_process (int signal_number)
{

  pid_t p;
  int status;

  while (1)
    {
      p = waitpid (-1, &status, WNOHANG);

      if (p == -1)
        {
          if (errno == EINTR)
            {
              continue;
            }
          break;
        }
      else if (p == 0)
        {
          break;
        }
    }


}

static void
app (void)
{
  SOCKET sock;
  char commande[BUF_SIZE];
  char res_cmd[BUF_SIZE];
  int max;
  int n;

  sock = init_connection ();
  max = sock;
  fd_set rdfs;

  sigemptyset (&sigmask);
  sigaddset (&sigmask, SIGCHLD);
  sigaddset (&sigmask, SIGINT);
  sigaddset (&sigmask, SIGTSTP);
  sigaddset (&sigmask, SIGTERM);
  sigprocmask (SIG_BLOCK, &sigmask, NULL);

  struct sigaction sigchld_action;
  memset (&sigchld_action, 0, sizeof (sigchld_action));
  sigchld_action.sa_handler = &clean_up_child_process;
  sigaction (SIGCHLD, &sigchld_action, NULL);

  while (1)
    {
      int i = 0;
      FD_ZERO (&rdfs);

      /* add STDIN_FILENO */
      FD_SET (STDIN_FILENO, &rdfs);

      /* add the connection socket */
      FD_SET (sock, &rdfs);

      sigemptyset (&empty_mask);
      if (pselect (max + 1, &rdfs, NULL, NULL, NULL, &empty_mask) == -1)
        if (errno != EINTR)
          {
            perror ("select()");
            exit (errno);
          }

      if (FD_ISSET (STDIN_FILENO, &rdfs))
        {
          /* stop process when type on keyboard */
          // break; must be disable to avoid bad exits
        }
      else if (FD_ISSET (sock, &rdfs))
        {
          /* new client */
          SOCKADDR_IN csin = { 0 };
          size_t sinsize = sizeof csin;
          int csock = accept (sock, (SOCKADDR *) & csin, &sinsize);
          if (csock == SOCKET_ERROR)
            {
              perror ("accept()");
              continue;
            }

          if ((n = recv (csock, commande, BUF_SIZE - 1, 0)) < 0)
            {
              perror ("recv(commande)");
              n = 0;
              continue;
            }
          commande[n] = 0;
          if ((n = fork ()) == -1)
            perror ("fork()");
          else if (n == 0)
            {
              close (STDOUT_FILENO);
              dup (csock);
              close (STDERR_FILENO);
              dup (csock);
              execlp (commande, commande, 0);
            }
          else
            {
              closesocket (csock);
            }
        }
    }
  end_connection (sock);
}

【问题讨论】:

  • signal_number 似乎没有在您的 clean_up_child_process 方法中使用?
  • 我的理解是:Signal is given by sigaction : sigaction (SIGCHLD, &sigchld_action, NULL);你在这里设置句柄函数:sigchld_action.sa_handler = &clean_up_child_process;在你使用 sigaction 之后。
  • pselect() 返回-1 时,如果errno == EINTR 则不要exit() - 而是循环返回并重新启动pselect() 调用。这是很正常的行为。
  • 您好,Paul,非常感谢您的建议,它现在可以正常工作了。我只需要更改处理函数(直接更新到帖子中)。我不知道为什么第一次连接后旧的挂起。现在好啦。我不知道为什么如果我用 FD_ISSET(STDIN_FILENO, &rdfs) 管理键盘,如果我不按键盘,程序会在第一个连接事件后直接退出。这并不重要,因为我不会在真正的服务器中使用它。这很奇怪(对我来说:-))

标签: c select fork zombie-process waitpid


【解决方案1】:

您需要了解更多关于 POSIX 信号处理的知识。

在可中断的系统调用(在本例中为pselect)期间接收到信号时,信号调用将退出回用户空间并调用信号处理程序。信号处理程序完成后,正常的行为是信号调用返回EINTR。在某些系统上,可以通过发出信号操作SA_RESTART 来避免这种情况,在这种情况下,内核将自动重新启动系统调用。这听起来是一个不错的选择,直到您意识到您经常想要捕获像SIGINT 这样的信号并让它们设置一个全局变量(例如退出程序)并对其进行测试。因此,如下结构(适用于您的程序)很常见:

volatile sig_atomic_t rxsig_quit = 0;

void
handlesignal (int sig)
{
  /* Only do signal safe things here; remember mutexes may be held */
  switch (sig)
    {
    case SIGINT:
    case SIGTERM:
      rxsig_quit++;
      break;
    case SIGCHLD:
      /* do all our waiting here */
      while (1)
        {
          int status;
          waitpid (WAIT_ANY, &status, WNOHANG);
        }
      break;
    }
}

static void
app (void)
{

  /* ... */

  while (!rxsig_quit)
    {
      /* ... */

      do
        {
          int ret;
          ret = pselect (max + 1, &rdfs, NULL, NULL, NULL, &empty_mask);
        }
      while ((ret < 0) && (errno == EINTR) && !rxsig_quit);

      /* ... */
    }

  /* ... */
}

您可以使用man -s7 signal 获取更多信息。这还列出了异步安全函数,即您可以在信号处理程序中安全调用的函数。

但是,您假设您根本需要执行wait。在现代 POSIX 系统上,情况并非如此。您可以将SIGCHLD 设置为SIG_IGN,在这种情况下,操作系统将完成工作,根据wait(2) 手册页中的这一段:

POSIX.1-2001 指定如果SIGCHLD 的处置设置为SIG_IGNSA_NOCLDWAIT 标志为SIGCHLD 设置(请参阅sigaction(2)),则终止的子级不会成为僵尸并且对wait()waitpid() 的调用将阻塞,直到所有子进程都终止,然后失败,errno 设置为ECHILD。 (原始 POSIX 标准未指定将 SIGCHLD 设置为 SIG_IGN 的行为。请注意,即使 SIGCHLD 的默认处置是“忽略”,但将处置显式设置为 SIG_IGN 会导致对僵尸进程的不同处理孩子。)Linux 2.6 符合此规范。但是,Linux 2.4(及更早版本)不会:如果在忽略 SIGCHLD 时进行了 wait() 或 waitpid() 调用,则调用的行为就像 SIGCHLD 未被忽略一样,即调用阻塞直到下一个子进程终止,然后返回该子进程的进程 ID 和状态。

显然这不太便携。

【讨论】:

  • 嗨,Abligh,非常感谢您的所有建议。我花了很长时间来分析您的所有解释和代码,现在一切正常并在控制之下。有了这个解释,我节省了大量的工作时间,而且我的代码更加安全。
  • @thorgal99 :没问题,你提醒我我错过了一些可能让你的生活更轻松的东西(刚刚添加的最后一段)。
猜你喜欢
  • 2021-04-26
  • 1970-01-01
  • 1970-01-01
  • 2013-06-03
  • 2019-04-11
  • 2020-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多