1

从我的 apache 服务器的 PHP 页面中,我使用如下行运行一些命令:

exec("{$command} >> /tmp/test.log 2>&1 & echo -n \$!");

您可以在此处查看参数的解释。

但我不明白一些事情:如果我重新启动或停止我的 apache 服务器,我的命令也会死掉。

root@web2:/sx/temp# ps ax | grep 0ff | grep -v grep
15957 ?        S      0:38 /usr/bin/php /sx/site_web_php/fr_FR/app/console task:exec /sx/temp/task_inventaire/ 0ff79bf690dcfdf788fff26c259882e2d07426df 10800
root@web2:/sx/temp#  /etc/init.d/apache2 restart
Restarting web server: apache2 ... waiting ..
root@web2:/sx/temp# ps ax | grep 0ff | grep -v grep
root@web2:/sx/temp# 

经过一些研究,我阅读了一些关于父 pid 的内容,但是&在我的命令行中使用 a 时,我认为我真的将我的子进程与他的父进程分离了。

我将 apache2 与 libapache2-mod-php5 和 apache2-mpm-prefork 一起使用。

我如何才能真正将我的子程序与 apache 分离?

编辑

您可以通过这种方式在 Linux/Mac 上重现它:

a) 创建一个 execute_script.php 文件,其中包含:

<?php
sleep(10);

b) 创建一个 execute_from_http.php 文件,其中包含:

<?php
exec("php executed_script.php > /tmp/test.log 2>&1 & echo -n \$!");

c) 运行http://localhost/path/execute_from_http.php

d) 在终端上,运行命令:

ps axjf | grep execute | grep -v grep ; sudo /etc/init.d/apache2 restart ; ps axjf | grep execute | grep -v grep

如果您在 execute_from_http.php 脚本的 10 秒内运行该命令,您将获得以下输出:

php@beast:/var/www/xxx/$ ps axjf | grep execute | grep -v grep ; sudo /etc/init.d/apache2 restart ; ps axjf | grep execute | grep -v grep
    1  5257  5245  5245 ?           -1 S       33   0:00 php executed_script.php
 * Restarting web server apache2
 ... waiting    ...done.
php@beast:/var/www/xxx/$

如您所见,该ps命令仅输出一次,这告诉您执行的脚本在 apache 重新启动时死亡。

4

2 回答 2

2

“在”方法

我找到了一个可行的解决方案,但如果我们谈到性能和安全性,我不知道这是否可以。它使用at命令,一种只工作一次的 cron。

代替 :

exec("php executed_script.php > /dev/null 2>&1 & echo -n \$!");

利用 :

exec("echo 'php executed_script.php > /dev/null 2>&1' | at now -M");

关键是executed_script.php它将由外部守护程序 (atd) 运行,因此executed_script.php将是 apache 的子进程atd而不是 apache 的子进程。

php@beast:/var/www/xxx$ ps axjf | grep execute | grep -v grep ; sudo /etc/init.d/apache2 restart ; ps axjf | grep execute | grep -v grep
 7032  7033   973   973 ?           -1 SN      33   0:00          \_ php executed_script.php
 * Restarting web server apache2
 ... waiting    ...done.
 7032  7033   973   973 ?           -1 SN      33   0:00          \_ php executed_script.php
php@beast:/var/www/xxx$ ps ax | grep 973
 973 ?        Ss     0:00 atd

注意几件事:

  • 您无法访问已运行应用程序的 pid,如果您$!像我之前的代码一样,您将获得at.
  • 您需要删除www-data默认情况下存在的/etc/at.deny内容(可能是有原因的,所以要小心)
  • 我对性能有严重的怀疑:我认为at写在一个读取的文件上以atd进行通信

fork/setsid 方法

正如@hek2mgl 在其自己的答案中所写,我们可以使用 a pcntl_fork(),但这并不那么简单。首先,你不能pcntl_fork()在 apache 后面运行,因为如果我们查看 PHP Manual, Introduction of the Process Control,我们可以看到:

不应在 Web 服务器环境中启用流程控制,如果在 Web 服务器环境中使用任何流程控制功能,可能会出现意外结果。

进行分叉时,您会在内存中获得父进程的两个精确副本。并且因为 apache 后面的 PHP 作为模块运行,所以在 PHP 执行结束时(甚至在 a 之后die()),你回到 apache 的模块包装器,你无法控制正在发生的事情。

所以这里是一个中间命令的场景,它将守护你的执行:

1)从 Apache,您运行将创建您的守护程序命令的中间命令:

$command = escapeshellarg("php executed_script.php");
exec("php run_as_daemon.php {$command} >> /dev/null 2>&1 &");

2)中间命令分叉并用于posix_setsid真正分离您的命令。

<?php

if (!isset($argv[1]))
{
    exit;
}
$command = $argv[1];

$pid = pcntl_fork();
if ($pid < 0) // error
    exit;
else if ($pid) // parent
    exit;
else // child
{
    $sid = posix_setsid(); // creates a daemon

    if ($sid < 0)
        exit;

    exec("{$command} >> /dev/null 2>&1 &");
}

3)您执行的命令当然不会改变:

<?php
sleep(10);

结果 :

php@beast:/var/www/xxx/$ wget -qO- http://localhost/xxx/execute_from_http.php && sleep 1 && ps axjf | grep execute | grep -v grep ; sudo /etc/init.d/apache2 restart ; ps axjf | grep execute | grep -v grep
    1 19958 19956 19956 ?           -1 S       33   0:00 php executed_script.php
 * Restarting web server apache2 ......done.
    1 19958 19956 19956 ?           -1 S       33   0:00 php executed_script.php
于 2013-04-12T19:03:40.000 回答
1

首先请注意,您示例中的 '&' 只是一个布尔值 AND,它连接了commandecho。如果要在后台启动命令,这意味着 exec 将立即返回,请&在命令行的最末尾使用:

exec("{$command} >> /tmp/test.log 2>&1 & echo -n \$! &");

如果您希望进程在 apache 完成后运行,您必须使用pcntl_fork()

这里有一个例子:

$pid = pcntl_fork();

switch($pid) {
    case -1 : die ('Error while forking');

    case 0:  // daemon code
         posix_setsid(); // create new process group
         exec("{$command} >> /tmp/test.log 2>&1 & echo -n \$!");
         break;
    default: 
         echo 'daemon started';
         break;

}

现在在开始的 PHP 脚本中没有任何代码来处理它的返回值exec和它的输出。所以当前进程可以在完成之前exec完成。此后,工作进程将归其所有init


你也可以看看 PEAR 包System_Daemon。这可以帮助守护脚本。

于 2013-04-12T15:59:28.560 回答