不接收使用sudo执行的进程的SIGCHLD

BSc*_*ker 11 c shell sudo exec sigchld

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

一切都在工作 - 除非我执行升级权限的进程sudo.在这些情况下,我从来没有得到过SIGCHLD信号 - 所以我永远不知道这个过程已经完成了.

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

如果我在ps -auxshell执行后看一下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>
Run Code Online (Sandbox Code Playgroud)

因此,sudo在分配pid = 4795了孩子(ls)的情况下,跑步和分配4796.孩子已完成任务,现在正处于一个僵尸状态.sudo似乎并不想收获僵尸进程而只是坐在那里.

我想知道是什么原因造成这种现象-我已经尝试了不同的技术来清除这些僵尸进程,比如下运行我的壳sudo和等待直接sudoPIDsudo执行(在上面的例子中4796).这些技术都没有奏效.

一如既往,任何建议都表示赞赏.

Gil*_*ert 4

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

首先,我不知道您是否使用旧的 signal() 还是新的 POSIX sigaction() 信号例程来捕获信号。sigset() 是 GNU 中的一个有用的工具。

传统信号——signal()
在所有环境中使用原始信号处理器来保证信号处理器的密封性,即使不是不可能,也几乎是不可能的。

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

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

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

POSIX 信号——sigaction()
POSIX 信号可以清除旧信号的所有故障。

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

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

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

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

在过去,您需要在信号处理程序中进行绝对最少的处理...无需调用具有副作用的库代码,例如 printf。当必须使用旧的信号处理程序时,我仍然遵循这一点,并且始终在新的处理程序中使用多线程警告。

  • 我读到你的评论,建议一个进程可以为孙子获得一个 SIGCHLD(*“...他的自定义 shell 将获得两个 SIGCHLDS...”*),并且一个进程可以收获(a-`wait(2)`)一个孙子(*“如果处理者没有同时收获……”*)。两者都不正确——进程会收到其直接子进程的 SIGCHLD,并且只能收获其直接子进程。看起来,收割一个终止的孩子也会解雇僵尸孙子,但实际上是“init(1)”自动收养并收割新的“孤儿”孙子。 (3认同)