为什么在一个从php进程运行的进程中不会触发一个报警处理程序?

我没有太多的PHP / mod_phppipe理经验,所以我很抱歉,如果这是一个非常简单的问题。

我的问题是 – 为什么不能通过exec()调用从PHP脚本产生的进程正确接收报警中断?

我的问题的长版:

今天早上,我在一个现有的PHP脚本中递交了一个bug。 经过一番调查,我已经把它追溯到php正在使用exec()来运行一个subprocess的事实,subprocess依靠一个SIGALRM来逃避一个循环,并且它从来没有收到警报。

我不认为这很重要,但具体的subprocess是/ bin / ping。 当ping一个不返回任何数据包的设备时(例如一个带有放弃ICMP回应请求而不是返回目标主机不可达的防火墙的设备),你必须使用-w选项来设置一个定时器来允许程序退出(因为-c计数返回数据包 – 如果目标永远不会返回数据包,并且不使用-w,则会陷入无限循环)。 当从PHP调用时, ping -w依赖的警报处理程序不会触发。

这里有一些有趣的使用strace来跟踪从命令行(警报处理程序工作)的ping调用:

 (snip) setitimer(ITIMER_REAL, {it_interval={0, 0}, it_value={1, 0}}, NULL) = 0 (snip) --- SIGALRM (Alarm clock) @ 0 (0) --- rt_sigreturn(0xe) = -1 EINTR (Interrupted system call) 

当我插入一个shell包装,允许我从网上调用时运行strace,我发现setitimer调用存在(并似乎运行成功),但SIGALRM行和rt_sigreturn()行不当下。 ping然后继续运行sendmsg()和recvmsg(),直到我手工杀死它。

试图减lessvariables,然后我把它切出来,写下面的Perl:

 [jj33@g3 t]# cat /tmp/toperl #!/usr/bin/perl $SIG{ALRM} = sub { print scalar(localtime()), " ALARM, leaving\n"; exit; }; alarm(5); print scalar(localtime()), " Starting sleep...\n"; sleep (10); print scalar(localtime()), " Exiting normally...\n"; 

从命令行运行时,按预期工作,报警处理程序成功触发:

 [jj33@g3 t]# /tmp/toperl Mon May 2 14:49:04 2011 Starting sleep... Mon May 2 14:49:09 2011 ALARM, leaving 

然后,我尝试通过相同的PHP页面(通过exec()和反引号)运行/ tmp / toperl,这是有问题的调用ping。 这里是我为testing写的php包装器:

 <? print "Running /tmp/toperl via PHP\n"; $v = `/tmp/toperl`; print "Output:\n$v\n"; ?> 

与ping一样,/ tmp / toperl没有收到其警报中断:

 Running /tmp/toperl via PHP Output: Mon May 2 14:52:19 2011 Starting sleep... Mon May 2 14:52:29 2011 Exiting normally... 

然后我在perl中写了一个快速的cgi包装器,在同一个Apache中执行,但在mod_cgi下而不是mod_php下执行。 以下是供参考的包装:

 [jj33@g3 t]# cat tt.cgi #!/usr/bin/perl print "Content-type: text/plain\n\n"; print "Running /tmp/toperl\n"; my $v = `/tmp/toperl`; print "Output:\n$v\n"; 

而且,你看,报警处理程序工作:

 Running /tmp/toperl Output: Mon May 2 14:55:34 2011 Starting sleep... Mon May 2 14:55:39 2011 ALARM, leaving 

所以,回到我原来的问题 – 为什么不能通过在mod_php控制的PHP脚本中的exec()产生的过程,当从命令行调用相同的派生进程时会接收到一个报警信号,而perl / mod_cgi ?

Apache 2.2.17,PHP 5.3.5。

感谢您的任何想法。

编辑 – DerfK是正确的,在调用subprocess之前,mod_php掩盖了SIGALRM。 我没有兴趣重新编译ping,所以我最终会写一个包装器。 由于我已经为这个问题写了这么多的文本,所以我想我也会在我的玩具程序/ tmp / toperl中join一个修订版本来testingSIGALRM是否被屏蔽掉,如果是这样的话。

 #!/usr/bin/perl use POSIX qw(:signal_h); my $sigset_new = POSIX::SigSet->new(); my $sigset_old = POSIX::SigSet->new(); sigprocmask(SIG_BLOCK, $sigset_new, $sigset_old); if ($sigset_old->ismember(SIGALRM)) { print "SIGALRM is being blocked!\n"; $sigset_new->addset(SIGALRM); sigprocmask(SIG_UNBLOCK, $sigset_new); } else { print "SIGALRM NOT being blocked\n"; } $SIG{ALRM} = sub { print scalar(localtime()), " ALARM, leaving\n"; sigprocmask(SIG_BLOCK, $sigset_new, $sigset_old); exit; }; alarm(5); print scalar(localtime()), " Starting sleep...\n"; sleep (10); print scalar(localtime()), " Exiting normally...\n"; 

现在这个testing在所有实例(perl /命令行,php /命令行,perl / mod_cgi,php / mod_php)中正常工作(意味着它在5秒钟后退出并且出现“ALARM,leaving”行)。 在前三个例子中,它打印'SIGALRM NOT blocked'行,在后者打印'SIGALRM被阻止! 并正确解锁它。

Mod_php可能会阻止这个(使用sigprocmask()我假设,掩码通过fork()execve() )来维护,以防止来自Apache的信号(因为mod_php在Apache的进程中运行PHP)。

如果是由于sigprocmask(),那么我认为你应该可以使用perl的POSIX模块在exec()的脚本中撤消它,但是我不确定它是如何工作的。 Perl Cookbook有一个阻止然后解除SIGINT的例子。 我觉得应该是这样的

 use POSIX qw(:signal_h); $sigset=POSIX::SigSet->new(SIGALRM); sigprocmask(SIG_UNBLOCK,$sigset); 

如果这样做不行,那么也许尝试安装php5-cgi,将其设置为Apache中的Handler,然后将其重命名为ping.phpc并更新链接。 由于CGI在自己的过程中执行,PHP的CGI版本可能不locking信号。