为什么由cron生成的进程最终会成为“僵尸”进程?

15

我在top(和ps)上看到一些进程显示为<defunct>。我已经从真实的脚本和程序中将问题简化了。

在我的crontab中:

* * * * * /tmp/launcher.sh /tmp/tester.sh

launcher.sh的内容(当然已经标记为可执行文件):

#!/bin/bash
# the real script does a little argument processing here
"$@"

tester.sh的内容(当然它是标记为可执行文件的):

#!/bin/bash
sleep 27 & # the real script launches a compiled C program in the background

ps 显示如下:

user       24257 24256  0 18:32 ?        00:00:00 [launcher.sh] <defunct>
user       24259     1  0 18:32 ?        00:00:00 sleep 27

请注意,tester.sh 没有出现 - 它在启动后退出了后台作业。

为什么 launcher.sh 会保持在状态 <defunct>?只有在通过 cron 启动时才会这样,而我自己运行它时没有出现这个问题。

另外需要注意的是:launcher.sh 是该系统中常见的脚本,不容易修改。其他东西(如 crontabtester.sh,甚至我运行的程序替代 sleep)可以更容易地进行修改。


1
顺便提一下,标记为“<defunct>”的进程被称为“僵尸进程”。 - Teddy
3
在这个帖子中提供了一种可能的解决方案:http://stackoverflow.com/questions/3748432/insane-crond-behavior-keeps-making-defunct-bash-processes - user1077227
6个回答

15
因为它们还没有被wait(2)系统调用所涉及。 由于未来可能会有人等待这些进程,因此内核无法完全摆脱它们,否则它将无法执行wait系统调用,因为它将不再具有退出状态或其存在的证据。 当您从shell启动一个进程时,您的shell会捕获SIGCHLD并执行各种等待操作,因此没有什么能够一直保持僵尸状态。 但是cron不处于等待状态,而是处于睡眠状态,因此僵尸子进程可能会在cron唤醒之前一直存在。
更新:响应评论... 嗯,我确实复制了这个问题...
 PPID   PID  PGID  SESS COMMAND
    1  3562  3562  3562 cron
 3562  1629  3562  3562  \_ cron
 1629  1636  1636  1636      \_ sh <defunct>
    1  1639  1636  1636 sleep

所以,我想发生的是:

  • 定时任务分叉并启动子进程
  • 子进程(1636)启动sid和pgid 1636,然后启动睡眠
  • 子进程退出,SIGCHLD发送给cron 3562
  • 信号被忽略或处理不当
  • 子进程变成僵尸进程。请注意,睡眠进程已重新分配到init下运行,因此当睡眠进程退出时,init将收到信号并进行清理。我仍在试图找出何时会清除僵尸进程。可能是没有活动子进程后,cron 1629发现可以退出了,在那个时候,僵尸进程将被重新分配给init并得到清理。现在我们想知道cron应该处理的缺失的SIGCHLD。
    • 这不一定是vixie cron的问题。正如您在此处所见,libdaemon在 daemon_fork()期间安装了一个SIGCHLD处理程序,并且这可能会干扰中间进程1629的快速退出的信号传递。

      现在,我甚至不知道我的Ubuntu系统上的vixie cron是否使用libdaemon构建,但至少我有一个新理论。:-)


它实际上会一直保持运行状态,而不仅仅是在cron唤醒之前。您能对此发表评论吗?我运行的真正程序(不是sleep)运行了数小时。 - John Zwinck
3
有合适的解决方案吗?脚本能否做些什么来确保在完成后不会变成僵尸进程? - Superole
你好,你能告诉我如何重现这个问题吗? - Vince.Wu
也许会产生输出的命令会导致“cron”成为僵尸进程?只是猜测。 - weaming

9
据我看,这是由进程CROND(由crond为每个任务生成)在等待标准输入上引起的,该标准输入被管道传输到crontab中的命令的stdout/stderr。这是因为cron能够通过邮件将输出结果发送给用户。

因此,CROND正在等待EOF,直到用户命令及其所有生成的子进程关闭管道。如果完成此操作,则CROND继续等待语句,然后不良的用户命令消失。

因此,我认为您必须明确断开脚本中每个生成的子进程与管道的连接(例如,通过将其重定向到文件或/dev/null)。

因此,以下行应在crontab中起作用:

* * * * * ( /tmp/launcher.sh /tmp/tester.sh &>/dev/null & ) 

2
谢谢,这篇帖子让我在深夜感到快乐。 - Ricardo Cristian Ramirez

4

我猜测cron在等待会话中的所有子进程终止。与负pid参数相关的内容,请参见wait(2)。您可以使用以下命令查看SESS:

ps faxo stat,euid,ruid,tty,tpgid,sess,pgrp,ppid,pid,pcpu,comm

这是我看到的(已编辑):

STAT  EUID  RUID TT       TPGID  SESS  PGRP  PPID   PID %CPU COMMAND
Ss       0     0 ?           -1  3197  3197     1  3197  0.0 cron
S        0     0 ?           -1  3197  3197  3197 18825  0.0  \_ cron
Zs    1000  1000 ?           -1 18832 18832 18825 18832  0.0      \_ sh <defunct>
S     1000  1000 ?           -1 18832 18832     1 18836  0.0 sleep

请注意,sh和sleep在同一个SESS中。
使用命令setsid(1)。以下是tester.sh:
#!/bin/bash
setsid sleep 27 # the real script launches a compiled C program in the background

注意,你不需要使用&,setsid会将它放到后台运行。


这样做会导致launcher.shtester.sh继续运行。我希望它们都能终止(至少在我的原始情况下,tester.sh会终止--使用setsid则不会,而我不想要这种情况)。 - John Zwinck
很奇怪,我在这里运行它时,启动器和测试器都会终止。(几乎立即——我还没有拍到它们运行的ps快照。) - bstpierre
我正在使用Ubuntu Hardy 64位系统。你呢? - John Zwinck
哦,我在我的crontab顶部有SHELL=/bin/bash - John Zwinck
Ubuntu Jaunty 32,我的crontab中没有bash。cron 3.0pl1-105ubuntu1.1。 - bstpierre

3

我建议你通过不再拥有两个独立的进程来解决这个问题: 在launcher.sh的最后一行执行以下操作:

exec "$@"

这将消除多余的流程。


我认为你是对的,但我不能轻易地这样做,因为launcher.sh被许多东西使用,其中一些如果我进行此更改将会中断。我可能考虑创建一个新的启动脚本来执行并保留其他版本不变,但这相当令人不快。 - John Zwinck
@John Zwinck:我无法想象在什么情况下,如果您进行此更改,事情会出现问题。这实际上是相同的事情,只是少了一个进程。 - Teddy
@Teddy:可能会出问题的是,有些人在交互式 shell 中这样做:. launcher.sh foo bar。如果启动器使用 exec,则用户的 shell 将在启动程序完成后终止。我知道这是一个奇怪的用例,但这就是现有系统的情况。 - John Zwinck
@John Zwinck:这个脚本可以重写一下,检测它是被启动还是被引用,然后做出相应的操作。 - Teddy

2

我在寻找类似问题的解决方案时发现了这个问题。不幸的是,这个问题中的答案没有解决我的问题。

杀死僵尸进程不是一个选项,因为你需要找到并杀死它的父进程。我最终以以下方式杀死了僵尸进程:

ps -ef | grep '<defunct>' | grep -v grep | awk '{print "kill -9 ",$3}' | sh

在“grep''”中,您可以将搜索范围缩小到您想要的特定已死进程。

-3

我已经测试了同样的问题很多次。 最终我找到了解决方案。 只需在bash脚本之前指定'/bin/bash',如下所示。

* * * * * /bin/bash /tmp/launcher.sh /tmp/tester.sh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接