стандартный вывод влияет на SIGKILL?

У меня есть скрипт для ограничения времени выполнения команд.

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");
}

Затем я тестирую этот скрипт с некоторыми командами.

ТЕСТ А

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

Через 3 секунды мы можем обнаружить, что процессы были убиты, как мы и ожидали.

ТЕСТ Б

Удалите код вывода и запустите снова.

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

Почему процессы были уничтожены в ТЕСТЕ А, но не в ТЕСТЕ Б? Влияют ли выходные данные на SIGKILL?

Любое предложение?


person Alix    schedule 10.08.2012    source источник
comment
Гораздо лучше сначала отправить сигнал TERM (15) и дать процессу возможность корректно закрыться перед отправкой сигнала KILL (9).   -  person Ed Heal    schedule 10.08.2012


Ответы (2)


Между php -r 'while(1){sleep(1);echo PHP_OS;} (process C) и его родителем (process B) есть PIPE, posix_kill($pid, SIGKILL) отправляет сигнал KILL на process B, затем process B завершается, но process C ничего не знает о сигнале и продолжает работать и выводит что-то на broken pipe, когда process C получает сигнал SIGPIPE, но не знает, как с ним обращаться, поэтому выходит.

Вы можете проверить это с помощью 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");
 }
person Xupeng    schedule 10.08.2012
comment
Ах, мне было интересно, был ли это SIGPIPE, который они получали, когда пытались писать на терминал. - person EPB; 10.08.2012
comment
PS: отлично работает с новой версией ядра, например 2.6.34, но не с моей 2.6.18. - person Alix; 10.08.2012
comment
@Alix В предыдущем патче была ошибка, вместо этого новая группа процессов должна быть создана в дочернем процессе, я обновил ответ. - person Xupeng; 11.08.2012

Вы отправляете сигнал уничтожения своему разветвленному процессу, но он не распространяется на его детей или внуков. Таким образом, они остаются сиротами и продолжают бежать, пока что-то не остановит их. (В этом случае любые попытки записи в стандартный вывод должны вызывать ошибку, которая затем вынуждает их завершить работу. Перенаправление вывода также, вероятно, приведет к бесконечному запуску сирот.)

Вы хотите отправить сигнал уничтожения процессу и всем его дочерним элементам. К сожалению, мне не хватает знаний, чтобы рассказать вам хороший способ сделать это. Я не очень хорошо знаком с функциями управления процессами PHP. Можно проанализировать вывод ps.

Я нашел один простой способ, который работает, — отправить сигнал уничтожения всей группе процессов с помощью команды kill. Это грязно и добавляет дополнительное сообщение «Убито» для вывода на мою машину, но, похоже, это работает.

<?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.
}

Для получения дополнительной информации см.: Лучший способ убить все дочерние процессы

person EPB    schedule 10.08.2012
comment
Спасибо @EPB, да, основная причина - SIGPIPE. - person Alix; 10.08.2012