周末有空,所以对 *nix 系统上的 proc_open() 做了一点研究。
虽然 proc_open() 不会阻止 PHP 的脚本执行,即使 shell 脚本没有在后台运行,但如果您不自己调用 PHP 的脚本完全执行后,PHP 会自动调用 proc_close()。所以,我们可以想象在脚本的末尾总是有一行 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() 会在 shell 脚本执行结束之前等待,但我们的脚本很快就被终止了。之所以会发生,是因为我们关闭了管道(如果我们忘记了,PHP 似乎会默默地执行此操作),因此一旦该脚本尝试向已经不存在的管道写入内容 - 它就会出错并终止。
现在,让我们尝试不使用管道(嗯,会有,但他们将使用当前的 tty,没有任何 PHP 链接):
$proc = proc_open("top -b -n 10000", array(), $pipes);
proc_close($proc);
现在,我们的 PHP 脚本正在等待我们的 shell 脚本结束。我们能避免吗?幸运的是 PHP 使用
生成 shell 脚本
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);
编辑:我忘了提到 PHP 不会立即运行你的 shell 脚本,所以你必须稍等片刻才能执行其他 shell 命令,但通常它足够快(或者 PHP 足够慢)而且我懒得将检查添加到我的示例中。