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

不调用过程信号处理程序

东方俊杰
2023-03-14

我正在开发一个用PHP编写的预分叉TCP套接字服务器

守护进程(父进程)分叉一定数量的子进程,然后等待,直到它被告知退出并且子进程都走了或者它收到信号。

SIGINT和SIGTERM使其将SIGTERM发送给所有子级。

孩子们建立了他们自己的信号处理器:SIGTERM导致一个干净的退出。SIGUSR1导致它转储一些状态信息(只需在下面的示例代码中打印出它收到了信号)。

如果子异常退出,则父级启动一个新子级,除非SIGINT处理程序设置了退出标志。

在守护进程初始化期间分叉的初始子进程按照预期对信号做出反应。

新分叉的子进程替换了一个意外的子进程退出,不响应信号。< br >下面的代码可用于演示这一点:

<?php
$children = [];
$exiting = false;

pcntl_async_signals( true );
pcntl_signal( SIGCHLD, 'sigchldHandler' );
pcntl_signal( SIGINT, 'sigintHandler' );
pcntl_signal( SIGTERM, 'sigintHandler' );

// Fork our children.
for( $ii = 0; $ii < 1; $ii++ )
{
  startChild();
}

// Forks a single child.
function startChild()
{
  global $children;

  echo "Parent: starting child\n";
  $pid = pcntl_fork();
  switch( true )
  {
    case ( $pid > 0 ):
      $children[$pid] = $pid;
      break;

    case ( $pid === 0 ):
      child();
      exit( 0 );

    default:
      die( 'Parent: pcntl_fork() failed' );
      break;
  }
}

// As long as we have any children...
while( true )
{
  if( empty( $children ) ) break;
  sleep( 1 );
}

// The child process.
function child()
{
  $pid = posix_getpid();
  echo "Child $pid: started\n";
  sleep( 10 ); // Give us a chance to start strace (4/30/19 08:27)

  pcntl_sigprocmask( SIG_SETMASK, [] );           // Make sure nothing is blocked.
  pcntl_async_signals( true );                    // This may be inherited.
  pcntl_signal( SIGINT, SIG_IGN );                // Ignore SIGINT.
  pcntl_signal( SIGTERM, function() use ( $pid )  // Exit on SIGTERM.
  {
    echo "Child $pid: received SIGTERM\n";
    exit( 0 );
  }, false );
  pcntl_signal( SIGUSR1, function() use( $pid )   // Acknowledge SIGUSR1.
  {
    printf( "Child %d: Received SIGUSR1\n", $pid );
  });

  // Do "work" here.
  while( true )
  {
    sleep( 60 );
  }
}

// Handle SIGCHLD in the parent.
// Start a new child unless we're exiting.
function sigchldHandler()
{
  global $children, $exiting;

  echo "Parent: received SIGCHLD\n";
  while( true )
  {
    if( ( $pid = pcntl_wait( $status, WNOHANG ) ) < 1 )
    {
      break;
    }
    echo "Parent: child $pid exited\n";
    unset( $children[$pid] );
    if( !$exiting )
    {
      startChild();
    }
  }
}

// Handle SIGINT in the parent.
// Set exiting to true and send SIGTERM to all children.
function sigintHandler()
{
  global $children, $exiting;

  $exiting = true;
  echo PHP_EOL;
  foreach( $children as $pid )
  {
    echo "Parent: sending SIGTERM to $pid\n";
    posix_kill( $pid, SIGTERM );
  }
}

在终端会话中运行此脚本。初始输出与此类似,具有不同的PID:

父级:起始子级<br>子级65016:起始

从不同的终端会话发出终止命令:

# 杀死 -USR1 65016

子进程将在第一个终端会话中显示:

儿童 65016:已收到 SIGUSR1

孩子正在按预期接收和处理信号。现在终止第一个孩子:

# kill -TERM 65016 第一个终端会话的输出将如下所示(使用不同的 PIDS):

子项65016:接收到的SIGTERM
父项:接收到的SIGCHLD
母项:子项6506退出<br>父项:起始子项<br>子项65039:启动

此时,新的子进程将接收任何信号,但无法捕获的 SIGKILL 和 SIGSTOP 除外。

向父级发送SIGINT将导致它向新子级发送SIGTERM。孩子不会得到它,父级将等到孩子被强制杀死后才退出(是的,生产代码将包括超时和SIGKILL任何剩余的孩子)。

环境:
- Ubuntu 18.04.2
- macOS Mojave 10.14.3 (相同的行为)
- PHP 7.2.17 (cli)

我发现自己没有主意了。想法?

编辑 30-Apr-2019 08:27 PDT:
我有一点更多的信息。我在“回声”儿童$pid:开始\n“之后添加了一个睡眠(10);让我有机会对孩子跑。

根据strace输出,似乎正在传递信号,但未调用子信号处理程序。

# sudo strace - p 69710
strace: Process 69710 attached
restart_syscall(<... resuming interrupted nanosleep ...>) = 0
rt_sigprocmask( SIG_SETMASK, [], ~[ KILL STOP RTMIN RT_1], 8) = 0
rt_sigaction( SIGINT, {sa_handler = SIG_IGN, sa_mask = [], sa_flags = SA_RESTORER, sa_restorer = 0x7f6e8881cf20}, null, 8) = 0
rt_sigprocmask( SIG_UNBLOCK, [ INT ], null, 8 ) = 0
rt_sigaction( SIGTERM, {sa_handler = 0x55730bdaf2e0, sa_mask = ~[ ILL TRAP ABRT BUS FPE KILL SEGV CONT STOP TSTP TTIN TTOU SYS RTMIN RT_1], sa_flags = SA_RESTORER | SA_INTERRUPT | SA_SIGINFO, sa_restorer = 0x7f6e8881cf20}, null, 8) = 0
rt_sigprocmask( SIG_UNBLOCK, [ TERM ], null, 8 ) = 0
rt_sigaction( SIGUSR1, {sa_handler = 0x55730bdaf2e0, sa_mask = ~[ ILL TRAP ABRT BUS FPE KILL SEGV CONT STOP TSTP TTIN TTOU SYS RTMIN RT_1], sa_flags = SA_RESTORER | SA_RESTART | SA_SIGINFO, sa_restorer = 0x7f6e8881cf20}, null, 8) = 0
rt_sigprocmask( SIG_UNBLOCK, [ USR1 ], null, 8 ) = 0
nanosleep({tv_sec = 60, tv_nsec = 0}, 0x7ffe79859470) = 0
nanosleep({tv_sec = 60, tv_nsec = 0}, {tv_sec = 37, tv_nsec = 840636107}) = ? ERESTART_RESTARTBLOCK( Interrupted by signal)
--- SIGUSR1 {si_signo = SIGUSR1, si_code = SI_USER, si_pid = 69544, si_uid = 1000} ---
rt_sigreturn({mask = []})                 = -1 EINTR( Interrupted system call)
rt_sigprocmask( SIG_BLOCK, ~[ RTMIN RT_1], [], 8) = 0
rt_sigprocmask( SIG_SETMASK, [], null, 8 ) = 0
nanosleep({tv_sec = 60, tv_nsec = 0}, 0x7ffe79859470) = 0

共有1个答案

艾心远
2023-03-14

我认为问题在于,当在注册的信号处理函数中调用pcntl_ fork时,PHP信号处理可能无法正常工作。由于第二个子进程是在sigchldHandler内部创建的,因此它不会 接收 处理后续信号。

编辑:不幸的是,我没有这方面的参考资料。我自己也一直在用和OP类似的问题撞墙(因此有了新账户!)而我找不到任何关于这种行为的确定答案或解释,只有来自人工存根测试的证据。我也很想知道(:

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

  • 问题内容: 我想自己开发一个探查器,我想解释一下我所看到的。即使在最简单的程序中,也总是会出现一些默认线程: 销毁JavaVM 信号调度器 终结器 参考处理程序 尽管他们的名字很能说明问题,但我想获得更多信息。似乎这些线程没有记录在案,是否有人知道挖掘这些信息的来源,甚至确切地知道这些线程的作用? 问题答案: DestroyJavaVM是一个线程,该线程在程序退出时卸载Java VM。在大多数情况

  • 问题内容: 我有2个线程(线程1和线程2)。而且我有信号处理。每当发生线程2时,都应处理该信号。为此,我写了下面的程序 我编译并运行该程序。每1秒打印一次“ thread1 active”,每3秒打印一次“ thread2 active”。 现在我生成了。但是它会像上面那样显示“ thread1 active”和“ thread2 active”消息。再次生成了,现在每3秒仅打印一次“ threa

  • 我有以下CPP代码。我想做的是当我的本机端发生错误时,我会通知Java错误。我使用了如何捕获SIGSEGV(分段错误)并在Android上的JNI下获取堆栈跟踪?作为参考。 一切正常,但对 myClass.nativeCrashed() 的调用不起作用。我做错了什么?

  • 我正在编写一个Spring批处理应用程序: null

  • #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