У меня было некоторое время на выходных, поэтому я провел небольшое исследование proc_open () в * nix системах.
Хотя proc_open () не блокирует выполнение скрипта PHP, даже если скрипт оболочки не запущенв фоновом режиме PHP автоматически вызывает proc_close () после полного выполнения скрипта PHP, если вы сами его не вызываете.Итак, мы можем представить, что у нас всегда есть строка с proc_close () в конце скрипта.
Проблема заключается в неочевидном, но логическом поведении proc_close ().Давайте представим, что у нас есть такой скрипт:
$proc = proc_open('top -b -n 10000',
array(
array('pipe', 'r'),
array('pipe', 'w')),
$pipes);
//Process some data outputted by our script, but not all data
echo fread($pipes[1],100);
//Don't wait till scipt execution ended - exit
//close pipes
array_map('fclose',$pipes);
//close process
proc_close($proc);
Странно, proc_close () будет ждать, пока не закончится выполнение сценария оболочки, но наш сценарий вскоре будет прерван.Это происходит потому, что мы закрыли каналы (кажется, что PHP делает это молча, если мы забыли), поэтому, как только этот сценарий пытается записать что-то в уже несуществующий канал - он получает ошибку и завершает свою работу.
Теперь,давайте попробуем без каналов (ну, будут, но они будут использовать текущий tty без какой-либо ссылки на PHP):
$proc = proc_open("top -b -n 10000", array(), $pipes);
proc_close($proc);
Теперь наш PHP-скрипт ожидает завершения нашего сценария оболочки.Можем ли мы избежать этого?К счастью, PHP порождает сценарии оболочки с
sh -c 'shell_script'
, поэтому мы можем просто завершить процесс sh и оставить наш скрипт запущенным:
$proc = proc_open("top -b -n 10000", array(), $pipes);
$proc_status=proc_get_status($proc);
exec('kill -9 '.$proc_status['pid']);
proc_close($proc);
Конечно, мы могли бы просто запустить процесс в фоновом режименапример:
$proc = proc_open("top -b -n 10000 &", array(), $pipes);
proc_close($proc);
и никаких проблем не возникает, но эта функция приводит нас к самому сложному вопросу: можем ли мы запустить процесс с помощью proc_open (), прочитав какой-нибудь вывод, а затем принудительно перевести процесс в фоновый режим?Ну, в некотором смысле - да.
Основная проблема здесь - каналы: мы не можем закрыть их, или наш процесс умрет, но нам нужны они, чтобы прочитать некоторые полезные данные из этого процесса.Оказывается, мы можем использовать здесь магический трюк - gdb.
Сначала создайте где-нибудь файл (в моем примере / usr / share / gdb_null_descr) со следующим содержимым:
p dup2(open("/dev/null",0),1)
p dup2(open("/dev/null",0),2)
Он скажет GDB изменить дескрипторы 1 и 2 (ну, обычно это stdout и stderr) на новые обработчики файлов (в этом примере / dev / null, но вы можете изменить его).
Теперь, последнее: убедитесь, что GDB может подключаться к другим запущенным процессам - это по умолчанию в некоторых системах, но, например, в Ubuntu 10.10 вы должны установить / proc / sys / kernel / yama / ptrace_scope в 0, если вы не запускаете его как root.
Наслаждайтесь:
$proc = proc_open('top -b -n 10000',
array(
array('pipe', 'r'),
array('pipe', 'w'),
array('pipe', 'w')),
$pipes);
//Process some data outputted by our script, but not all data
echo fread($pipes[1],100);
$proc_status=proc_get_status($proc);
//Find real pid of our process(we need to go down one step in process tree)
$pid=trim(exec('ps h -o pid --ppid '.$proc_status['pid']));
//Kill parent sh process
exec('kill -s 9 '.$proc_status['pid']);
//Change stdin/stdout handlers in our process
exec('gdb -p '.$pid.' --batch -x /usr/share/gdb_null_descr');
array_map('fclose',$pipes);
proc_close($proc);
edit: я забыл упомянуть, что PHP не запускает ваш скрипт оболочки сразу, поэтому вам придется немного подождать, прежде чем выполнять другие команды оболочки, но обычноэто достаточно быстро (или PHP достаточно медленно), и я ленив, чтобы добавить эти проверки в мои примеры.