11

我目前正在编写一个shell。我执行进程并SIGCHLD在它们完成时使用信号处理程序来清理(等待它们)。

一切正常——除非我执行使用sudo. 在这些情况下,我永远不会收到SIGCHLD信号——所以我永远不知道进程已经完成执行。

当我收到诸如 之类的命令时sudo ls,我执行程序sudo,然后ls作为参数提供。我用execvp.

如果我ps -aux在我的 shell 执行后查看一下sudo ls,我会看到以下内容:

root      4795  0.0  0.0   4496  1160 pts/29   S+   16:51   0:00 sudo ls
root      4796  0.0  0.0      0     0 pts/29   Z+   16:51   0:00 [ls] <defunct>

所以,sudo跑了,被指派pid = 4795了,孩子(ls)被指派了4796。孩子已经完成了任务,现在正处于僵尸状态。sudo似乎不想收获僵尸进程,只是坐在那里。

我想知道是什么导致了这种行为——我尝试了不同的技术来清理这些僵尸进程,例如在下面运行我的 shellsudo并直接等待,然后sudo执行(上面示例中的 4796)。这些技术都没有奏效。PIDsudo

与往常一样,我们将不胜感激任何建议。

4

1 回答 1

4

我的第一个想法是不正确的信号处理,但是您的帖子中没有足够的信息来编写测试代码来复制您的失败。但我可以给你一些地方看看。如果我为未来的读者介绍一些您已经知道的信号基础知识,请原谅我。

首先,我不知道您是使用旧的 signal() 还是新的 POSIX sigaction() 信号例程来捕获信号。sigset() 在 GNU 之间很有用。

Legacy Signals -- signal()
保证在所有环境中使用原始信号处理器的气密信号处理器几乎是不可能的,如果不是不可能的话。

  • 在某些 UNIX 系统上,输入信号处理程序可以将处理程序重置为默认条件。除非处理程序显式重置信号,否则后续信号肯定会丢失。
  • signal() 处理程序不能假定它们为每个信号调用一次。
    • 处理程序必须执行一个while( ( pid = waitpid( -1, &signal, WNOHANG ) ) > 0 )循环,直到找不到更多信号,因为遗留信号设置了一个布尔条件,表明至少有一个信号未完成。实际数量未知。
    • 如果先前的 while() 循环处理了信号,则处理程序必须允许找不到信号。
  • 允许来自未知进程的信号......如果您启动的程序还启动了一个孙进程,如果您的孩子快速退出,您可以继承该进程。

建议,捏住鼻子,逃离遗留信号。

遗留处理程序和多个 SIGCHILD 中缺少一个 while() 循环,一个来自您的 sudo,一个或多个来自 sudo 触发的意外孙辈。如果孙信号首先进入时只处理一个 SIGCHILD,则预期程序的信号将不会被捕获。

POSIX 信号 -- sigaction()
POSIX 信号可以清除遗留信号的所有故障。

  • 设置一个处理程序,而不进行恢复(恢复不是 POSIX 信号的一部分,并且至少在我看来,当您可能以相同的方式处理多个信号时,它通常是邪恶的)。
  • sigaction() 信号是粘性的……它们一直存在,直到明确改变(太棒了!)。不必在处理程序中再次重置信号处理程序的这种麻烦要求都没有。
  • 设置一个掩码,在处理信号时屏蔽掉当前信号。偏执狂也会屏蔽传递给同一处理程序的任何其他信号。

如果您在 SIGCHILD 处理程序中获得 SIGCHILD,则缺少掩码会导致奇怪的事情,例如丢失信号跟踪。

GNU -- sigset()
GNU 提供了一个有用的中间函数,它与 signal() 具有相同的调用签名,但消除了大部分问题。一些额外的控制功能也可用。使用 sigset() 可以轻松解决许多信号问题。

提醒
将信号处理程序视为程序中的线程,即使您没有在代码中使用线程。

在过去的日子里,您需要在信号处理程序中进行绝对最少的处理......没有调用具有副作用的库代码,例如 printf。当必须使用旧的信号处理程序时,我仍然遵循这一点,并且总是在较新的处理程序中使用多线程注意事项。

于 2011-11-25T19:41:19.037 回答