为什么cron产生的进程最终不存在?

时间:2009-10-01 22:45:19

标签: bash background cron zombie-process defunct

我在<defunct>(和top)中有一些显示为ps的流程。我从真实的脚本和程序中汲取了一些东西。

在我的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)都可以更容易地修改。

6 个答案:

答案 0 :(得分:11)

因为它们不是wait(2)系统调用的主题。

由于某人可能会在将来等待这些进程,因此内核无法完全摆脱它们,或者它无法执行wait系统调用,因为它不具有退出状态或其存在的证据。

当你从shell启动一个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

所以,发生了什么事,我想:

  • cron forks和cron child start shell
  • shell(1636)启动sid和pgid 1636并开始睡眠
  • shell退出,SIGCHLD发送到cron 3562
  • 信号被忽略或处理不当
  • shell变成了僵尸。请注意,睡眠被重新设置为init,因此当睡眠退出init时,将获得信号并进行清理。我还在试图找出僵尸收获的时间。可能没有活跃的孩子,cron 1629指出它可以退出,此时僵尸将被重新分配给初始化并获得收获。所以现在我们想知道cron应该处理的丢失的SIGCHLD。
    • 这不一定是vixie cron的错。正如您在daemon_fork()期间所见libdaemon installs a SIGCHLD handler,这可能会干扰1629中间快速退出的信号传递

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

答案 1 :(得分: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会将其置于后台。

答案 2 :(得分:4)

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

因此,CROND正在等待EOF,直到用户命令及其生成的所有子进程已关闭管道。如果这样做,CROND继续使用wait语句,然后defunct user命令消失。

所以我认为你必须明确地将脚本中的每个衍生子进程从管道中断开(例如通过将其重定向到文件或/ dev / null。

所以以下行应该在crontab中起作用:

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

答案 3 :(得分:3)

我建议您通过简单地没有两个单独的流程来解决问题:让launcher.sh在最后一行执行此操作:

exec "$@"

这将消除多余的过程。

答案 4 :(得分:1)

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

杀死已解决的进程不是一个选项,因为您需要查找并终止其父进程。我最终以下列方式杀死了已解散的进程:

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

在“grep''”中,您可以将搜索范围缩小到您所追求的特定已解散的过程。

答案 5 :(得分:-2)

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

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