【问题标题】:standard output impact SIGKILL?标准输出影响 SIGKILL?
【发布时间】:2012-08-07 08:18:46
【问题描述】:

我有一个脚本来限制命令的执行时间长度。

limit.php

<?php
declare(ticks = 1);

if ($argc<2) die("Wrong parameter\n");
$cmd = $argv[1];
$tl = isset($argv[2]) ? intval($argv[2]) : 3;

$pid = pcntl_fork();
if (-1 == $pid) {
    die('FORK_FAILED');
} elseif ($pid == 0) {
    exec($cmd);
    posix_kill(posix_getppid(), SIGALRM);
} else {
    pcntl_signal(SIGALRM, create_function('$signo',"die('EXECUTE_ENDED');"));
    sleep($tl);
    posix_kill($pid, SIGKILL);
    die("TIMEOUT_KILLED : $pid");
}

然后我用一些命令测试这个脚本。

测试A

php limit.php "php -r 'while(1){sleep(1);echo PHP_OS;}'" 3

3s后,我们可以发现进程按预期被杀死了。

测试 B

删除输出代码并再次运行。

php limit.php "php -r 'while(1){sleep(1);}'" 3

结果看起来不太好,函数“exec”创建的进程没有像 TEST A 那样被杀死。

[alix@s4 tmp]$ ps aux | grep whil[e]
alix      4433  0.0  0.1 139644  6860 pts/0    S    10:32   0:00 php -r while(1){sleep(1);}

系统信息

[alix@s4 tmp]$ uname -a
Linux s4 2.6.18-308.1.1.el5 #1 SMP Wed Mar 7 04:16:51 EST 2012 x86_64 x86_64 x86_64 GNU/Linux
[alix@s4 tmp]$ php -v
PHP 5.3.9 (cli) (built: Feb 15 2012 11:54:46) 
Copyright (c) 1997-2012 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2012 Zend Technologies

为什么进程在 TEST A 中被杀死但在 TEST B 中没有?输出会影响 SIGKILL 吗?

有什么建议吗?

【问题讨论】:

  • 最好先发送一个 TERM 信号 (15) 并给进程一些机会在发送 KILL (9) 信号之前优雅地关闭。

标签: php linux bash fork sigkill


【解决方案1】:

php -r 'while(1){sleep(1);echo PHP_OS;} (process C) 和它的父级 (process B) 之间有一个PIPEposix_kill($pid, SIGKILL)process B 发送KILL 信号,然后process B 被终止,但@987654329当process C 接收到SIGPIPE 信号但不知道如何处理它所以它退出时,@ 对信号一无所知并继续运行并向broken pipe 输出一些东西。

你可以用 strace 验证(运行php limit.php "strace php -r 'while(1){sleep(1); echo PHP_OS;};'" 1),你会看到这样的:

14:43:49.254809 write(1, "Linux", 5)    = -1 EPIPE (Broken pipe)
14:43:49.254952 --- SIGPIPE (Broken pipe) @ 0 (0) ---
14:43:49.255110 close(2)                = 0
14:43:49.255212 close(1)                = 0
14:43:49.255307 close(0)                = 0
14:43:49.255402 munmap(0x7fb0762f2000, 4096) = 0
14:43:49.257781 munmap(0x7fb076342000, 1052672) = 0
14:43:49.258100 munmap(0x7fb076443000, 266240) = 0
14:43:49.258268 munmap(0x7fb0762f3000, 323584) = 0
14:43:49.258555 exit_group(0)           = ?

至于php -r 'while(1){sleep(1);},因为在它的父节点死后没有出现broken pipe,所以它继续按预期运行。

一般来说,如果你想杀死它的孩子,你应该杀死整个进程组,而不仅仅是进程本身,PHP你可以将process B添加到它自己的进程组中,然后杀死整个组,这是与您的代码的差异:

--- limit.php   2012-08-11 20:50:22.000000000 +0800
+++ limit-new.php   2012-08-11 20:50:39.000000000 +0800
@@ -9,11 +9,13 @@
 if (-1 == $pid) {
     die('FORK_FAILED');
 } elseif ($pid == 0) {
+    $_pid = posix_getpid();
+    posix_setpgid($_pid, $_pid);
     exec($cmd);
     posix_kill(posix_getppid(), SIGALRM);
 } else {
     pcntl_signal(SIGALRM, create_function('$signo',"die('EXECUTE_ENDED');"));
     sleep($tl);
-    posix_kill($pid, SIGKILL);
+    posix_kill(-$pid, SIGKILL);
     die("TIMEOUT_KILLED : $pid");
 }

【讨论】:

  • 啊,我想知道是不是他们尝试写入终端时得到的 SIGPIPE。
  • PS:在新版本的内核(如 2.6.34)上运行良好,但在我的 2.6.18 上运行良好
  • @Alix 上个补丁有个bug,应该在子进程中创建新的进程组,我已经更新了答案。
【解决方案2】:

您将 kill 信号发送到您的分叉进程,但这不会传播给它的子或孙。因此,他们成为孤儿并继续运行,直到有什么东西阻止他们这样做。 (在这种情况下,任何写入标准输出的尝试都会导致错误,然后强制它们退出。重定向输出也可能导致无限期运行的孤儿。)

您想向进程及其所有子进程发送终止信号。不幸的是,我缺乏知识来告诉你这样做的好方法。我对 PHP 的过程控制功能不是很熟悉。可以解析ps的输出。

我发现一种可行的简单方法是使用 kill 命令向整个进程组发送终止信号。它很乱,并且它在我的机器上添加了一个额外的“Killed”消息输出,但它似乎工作。

<?php
declare(ticks = 1);

if ($argc<2) die("Wrong parameter\n");
$cmd = $argv[1];
$tl = isset($argv[2]) ? intval($argv[2]) : 3;

$pid = pcntl_fork();
if (-1 == $pid) {
    die('FORK_FAILED');
} elseif ($pid == 0) {
    exec($cmd);
    posix_kill(posix_getppid(), SIGALRM);
} else {
    pcntl_signal(SIGALRM, create_function('$signo',"die('EXECUTE_ENDED');"));
    sleep($tl);
    $gpid = posix_getpgid($pid);

    echo("TIMEOUT_KILLED : $pid");
    exec("kill -KILL -{$gpid}"); //This will also cause the script to kill itself.
}

欲了解更多信息,请参阅:Best way to kill all child processes

【讨论】:

  • 感谢@EPB,是的,根本原因是 SIGPIPE。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-18
  • 1970-01-01
  • 2021-10-20
  • 1970-01-01
  • 1970-01-01
  • 2013-05-01
  • 2021-08-06
相关资源
最近更新 更多