当前位置: 首页 > 知识库问答 >
问题:

PHP中的进程重生和信号处理

华泽语
2023-03-14

规格

我在PHP中遇到了一个问题,当重新启动的进程没有处理信号时,而在重新启动之前,处理工作正常。我把我的代码缩减到最基本的部分:

declare(ticks=1);

register_shutdown_function(function() {
    if ($noRethrow = ob_get_contents()) {
        ob_end_clean();
        exit;
    }
    system('/usr/bin/nohup /usr/bin/php '.__FILE__. ' 1>/dev/null 2>/dev/null &');
});

function handler($signal)
{
    switch ($signal) {
        case SIGTERM:
            file_put_contents(__FILE__.'.log', sprintf('Terminated [ppid=%s] [pid=%s]'.PHP_EOL, posix_getppid(), posix_getpid()), FILE_APPEND);
            ob_start();
            echo($signal);
            exit;
        case SIGCONT:
            file_put_contents(__FILE__.'.log', sprintf('Restarted [ppid=%s] [pid=%s]'.PHP_EOL, posix_getppid(), posix_getpid()), FILE_APPEND);
            exit;
    }
}

pcntl_signal(SIGTERM, 'handler');
pcntl_signal(SIGCONT, 'handler');

while(1) {
    if (time() % 5 == 0) {
        file_put_contents(__FILE__.'.log', sprintf('Idle [ppid=%s] [pid=%s]'.PHP_EOL, posix_getppid(), posix_getpid()), FILE_APPEND);
    }
    sleep(1);
}

如您所见,它执行以下操作:

  • 注册关闭函数,其中使用nohup重新生成一个进程(因此,当父进程死亡时忽略SIGHUP
  • 通过pcntl_signal()SIGTERMSIGCONT注册处理程序。第一个将只记录进程被终止的消息,而第二个将导致进程的重生。它是通过ob_*函数实现的,所以要传递一个标志,在关闭函数中应该做什么-退出或重生。
  • 将脚本“活动”的一些信息记录到日志文件。

发生了什么

所以,我从以下内容开始编写脚本:

/usr/bin/nohup /usr/bin/php script.php 1>/dev/null 2>/dev/null &

然后,在日志文件中,有如下条目:

Idle [ppid=7171] [pid=8849]
Idle [ppid=7171] [pid=8849]

比方说,然后我做< code >杀死8849:

Terminated [ppid=7171] [pid=8849]

因此,它是成功处理SIGTERM的(并且脚本确实退出了)。现在,如果我杀死-18 8849,那么我看到(18是SIGCONT的数值):

Idle [ppid=7171] [pid=8849]
Restarted [ppid=7171] [pid=8849]
Idle [ppid=1] [pid=8875]
Idle [ppid=1] [pid=8875]

因此:首先,SIGCONT 也得到了正确的处理,并且,从下一个“空闲”消息来看,新生成的脚本实例运行良好。

更新#1:我在考虑使用ppid=1(因此,init全局进程)和孤立进程信号处理的东西,但事实并非如此。这是日志部分,它表明孤立(ppid=1)进程不是原因:当Worker通过控制应用程序启动时,它还使用system()命令调用它-就像Worker重新生成自己一样。但是,在控制应用程序调用Worker后,它具有ppid=1并正确响应信号,而如果Worker重新生成自己,则新副本不会响应它们,除了SIGKILL。因此,只有当Worker重新生成自己时才会出现问题。

更新#2:我试图分析< code>strace发生了什么。这里有两个街区。

>

  • 当工人尚未重生时 - 斯特拉斯输出。看看第4行和第5行,这是我发送SIGCONT的时候,因此杀死-18到一个进程。然后它触发所有链:写入文件,system()调用和退出当前进程。
  • 当工人已经自己重生时 - 斯特拉斯输出。在这里,看看第8行和第9行 - 它们在收到SIGCONT后出现。首先:看起来过程仍然以某种方式接收信号,其次,它忽略了信号。未执行任何操作,但系统通知进程 SIGCONT 已发送。为什么进程忽略它 - 是问题所在(因为,如果SIGCONT的用户处理程序安装失败,那么它应该结束执行,而进程没有结束)。至于 SIGKILL,那么已经重生的 worker 的输出是这样的:

    nanosleep({1, 0},  <unfinished ...>
    +++ killed by SIGKILL +++
    

    这表明,该信号已被接收并做了它应该做的事情。

    问题

    当该过程重生时,它既不对SIGTERM做出反应,也不对SIGCONT做出反应。但是,仍然有可能用SIGKILL结束它(因此,kill -9 PID确实结束了这个过程)。例如,对于上面的进程,kill 8875kill -18 8875 将不执行任何操作(进程将忽略信号并继续记录消息)。

    然而,我不会说注册信号是完全失败的——因为它至少重新定义了< code>SIGTERM(这通常会导致终止,而在这种情况下它被忽略)。我还怀疑< code>ppid = 1指向了一些错误的东西,但是我现在还不能确定。

    此外,我尝试了任何其他类型的信号(事实上,信号代码是什么并不重要,结果总是一样的)

    这个问题

    这种行为的原因是什么?我正在重塑一个过程的方式正确吗?如果没有,还有哪些其他选项允许新生成的进程正确使用用户定义的信号处理程序?

  • 共有2个答案

    何安宜
    2023-03-14

    这是因为您通过执行system(foo)生成一个子进程,然后继续终止当前进程。因此,该进程成为孤儿,其父进程成为PID 1(init)。

    您可以使用pstree命令查看更改。

    之前:

    init─┬─cron
    (...)
         └─screen─┬─zsh───pstree
                  ├─3*[zsh]
                  ├─zsh───php
                  └─zsh───vim
    

    之后:

    init─┬─cron
    (...)
         └─php
    

    维基百科是这样说的:

    孤立进程是一种与僵尸进程相反的情况,因为它指的是父进程在其子进程之前终止的情况,在这种情况下,这些子进程被称为“孤立”。

    与子进程终止时发生的异步子进程到父进程通知(通过SIGCHLD信号)不同,当父进程结束时,子进程不会立即得到通知。相反,系统只是将子进程数据中的“parent-pid”字段重新定义为系统中所有其他进程的“祖先”进程,其pid值通常为1(一),其名称通常为“init”。因此,有人说“init”采用“系统上的每个孤立进程”。

    对于您的情况,我建议两个选项:

    • 使用两个脚本:一个用于管理孩子,另一个“工人”,实际执行工作。
    • 或者,使用一个脚本,该脚本将包括两个部分:外部部分将管理,内部部分,从外部分叉,将完成工作
    陆昕
    2023-03-14

    解决方案:最终,strace帮助理解了问题。具体如下:

    nanosleep({1, 0}, {0, 294396497})       = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
    restart_syscall(<... resuming interrupted call ...>) = 0
    

    因此,它显示信号已被接收,但被忽略。为了完全回答这个问题,我需要弄清楚,为什么处理添加的信号来忽略列表,但是用pcntl_sigprocmask()强行取消阻止它们正在做这件事:

    pcntl_sigprocmask(SIG_UNBLOCK, [SIGTERM, SIGCONT]);
    

    然后一切顺利,重生进程按预期接收/处理信号。例如,我尝试只添加<code>SIGCONT</code>来解除阻塞,然后它被正确处理,而<code>SIGTERM</code>被阻塞,这表明这正是无法发送信号的原因。

    解决方案:由于某些原因,当进程在安装了信号处理程序的情况下自行生成时,新实例会屏蔽这些信号以供忽略。揭开它们的面纱有力地解决了这个问题,但为什么信号会在新的实例中被掩盖呢?这是一个尚未解决的问题。

     类似资料:
    • 上一篇尬聊了通篇的 pcntl_wait() 和 pcntl_waitpid(),就是为了解决僵尸进程的问题,但最后看起来还是有一些遗留问题,而且因为嘴欠在上篇文章的结尾出也给了解决方案:信号。 信号是一种软件中断,也是一种非常典型的异步事件处理方式。在 nix 系统诞生的混沌之初,信号的定义是比较混乱的,而且最关键是不可靠,这是一个很严重的问题。所以在后来的POSIX标准中,对信号做了标准化同时

    • #include <stdio.h> #include <signal.h> void handler(int sig); void handler(int sig) { signal(sig, handler); printf("Receive signal: %d\n", sig); } int main(void) { signal(SI

    • 问题内容: 这应该非常简单,并且令我感到惊讶的是,我还没找到关于stackoverflow的答案。 我有一个类似程序的守护程序,该程序需要响应SIGTERM和SIGINT信号才能与新贵一起正常工作。我读到最好的方法是在与主线程不同的线程中运行程序的主循环,并让主线程处理信号。然后,当接收到信号时,信号处理程序应通过设置通常在主循环中检查的哨兵标志来告诉主循环退出。 我已经尝试过这样做,但是它没有按

    • 在Linux中,当一个程序(可能有多个线程)收到信号(如SIGTERM或SIGHUP)时会发生什么? 哪个线程拦截信号?多个线程可以获得相同的信号吗?是否有专门处理信号的特殊线程?如果没有,那么处理信号的线程内部会发生什么?信号处理程序例程完成后,执行如何继续?

    • 问题内容: 我需要在接收到任何终止命令(如SIGTERM和SIGKILL)时写入日志文件。 我可以注册SIGTERM,但是如何处理SIGKILL信号? 问题答案: 您不能,至少不是因为进程被杀死。 您 可以 做的是安排父进程监视子进程的死亡,并采取相应的措施。任何体面的过程监控系统(例如daemontools)都内置了这样的工具。

    • 本文向大家介绍Android Init进程对信号的处理流程详细介绍,包括了Android Init进程对信号的处理流程详细介绍的使用技巧和注意事项,需要的朋友参考一下 Android  Init进程对信号的处理流程 在Android中,当一个进程退出(exit())时,会向它的父进程发送一个SIGCHLD信号。父进程收到该信号后,会释放分配给该子进程的系统资源;并且父进程需要调用wait()或wa