周末有时间,所以对 *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 身份运行它。
Enjoy:
$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 足够慢),我懒得将其添加到我的示例中。