【问题标题】:Really daemonize a command when using exec()?使用 exec() 时真的守护一个命令吗?
【发布时间】:2013-04-05 05:47:51
【问题描述】:

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

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

你可以看到参数here的解释。

但我不明白一些事情:如果我重新启动或停止我的 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 的内容,但是在我的命令行中使用 &,我认为我真的将我的子进程与他的父进程分离了。

我将 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 重新启动时死亡。

【问题讨论】:

    标签: php process exec parent-child daemon


    【解决方案1】:

    首先请注意,您的示例中的“&”只是一个布尔 AND,它连接了 commandecho。如果你想在 background 中启动命令,这意味着 exec 将立即返回,请在命令行的最后使用&amp;

    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。这有助于守护脚本。

    【讨论】:

    • 不,不是。通过exec启动的进程仍然属于apache进程组
    • 前几天我也有同样的要求,使用pcntl_fork() 成功了。我不知道为什么,但是没有明确创建新进程组的事件
    • 此解决方案不起作用。我试图将pcntl_fork 放在 apache 中,但(幸运的是)它没有用。我也在执行的命令本身内部进行了尝试,但问题仍然存在。
    • pcntl_fork 是否启用?添加ini_set('display_errors', 1);but fortunately it didn't worked是什么意思?
    • 嘿,谢谢你的消息!!!我预料到了这一点,但在 cli 环境中它不是必需的。 (我不知道为什么)。我有另一个想法:fork 然后 posix_exec(),这将从内存中替换 apache 进程
    【解决方案2】:

    “at”方法

    我找到了一个可行的解决方案,但如果我们谈到性能和安全性,我不知道这是否可以。它使用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 将是 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 的 pid。
    • 您需要删除www-data,它默认在/etc/at.deny 中(可能是有原因的,所以要小心)
    • 我对性能有严重的怀疑:我认为at写在atd读取的文件上进行通信

    fork/setsid 方法

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

    不应在 Web 服务器环境中启用进程控制 如果有任何过程控制功能,则可能会出现意想不到的结果 在网络服务器环境中使用。

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

    所以这里有一个中间命令会守护你的执行的场景:

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

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

    2) 中间命令 fork 并使用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
    

    【讨论】:

      猜你喜欢
      • 2012-06-29
      • 2017-11-13
      • 1970-01-01
      • 1970-01-01
      • 2015-11-09
      • 1970-01-01
      • 1970-01-01
      • 2011-07-04
      • 1970-01-01
      相关资源
      最近更新 更多