PHP命令行下的信号处理
作者:James Zhu ([email protected])
创建日期:2018-03-16
引言
我们有一个demo.php
的程序源代码如下:
for($i=0; $i < 60; $i++) {
echo $i;
sleep(1);
}
当在命令行运行php demo.php
时候,会每秒输出一个数字,直到60秒后,程序结束。
当然,这是一个会结束的循环,在实际开发过程中,往往由于考虑欠妥或代码不熟悉,造成死循环。遇到死循环如何解决呢?——是的,Ctrl-C
即可立即终止该程序。
/ # php demo.php
01234567^C
那么,你有没有想过,当你按下Ctrl-C
后,程序是如何处理的呢?
Unix信号
在计算机科学中,信号(英语:Signals)是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。
发送信号
在一个运行的程序的控制终端键入特定的组合键可以向它发送某些信号:
Ctrl-C
发送INT信号(SIGINT);默认情况下,这会导致进程终止。Ctrl-Z
发送TSTP信号(SIGTSTP);默认情况下,这会导致进程挂起。Ctrl-\
发送QUIT信号(SIGQUIT);默认情况下,这会导致进程终止并且将内存中的信息转储到硬盘(核心转储)。(这些组合键可以通过stty命令来修改。)
kill()系统调用会在权限允许的情况下向进程发送特定的信号,类似地,kill命令允许用户向进程发送信号。raise(3)库函数可以将特定信号发送给当前进程。
像除数为零、段错误这些异常也会产生信号(这里分别是SIGFPE和SIGSEGV,默认都会导致进程终止和核心转储)。
内核可以向进程发送信号以告知它一个事件发生了。例如当进程将数据写入一个已经被关闭的管道是将会收到SIGPIPE信号,默认情况下会使进程关闭。
处理信号
PHP的进程控制支持实现了Unix方式的进程创建、程序执行、 信号处理以及进程的中断。进程控制不能被应用在Web服务器环境,当其被用于Web服务环境时可能会带来意外的结果。
这里使用PCNTL为demo.php
安装信号处理器,源代码如下:
// Ctrl-C handler
pcntl_signal(SIGINT, function() {
echo PHP_EOL.'Ctrl-C Pressed'.PHP_EOL;
exit();
});
// Ctrl-\ handler
pcntl_signal(SIGQUIT, function() {
echo PHP_EOL.'Ctrl-\\ Pressed'.PHP_EOL;
exit();
});
for($i=0; $i < 60; $i++) {
pcntl_signal_dispatch();
echo $i;
sleep(1);
}
示例中安装了2个信号处理器,监测Ctrl-C
和Ctrl-\
,运行结果如下:
/ # php demo.php
01234567^C
Ctrl-C Pressed
/ # php demo.php
01234567^\
Ctrl-\ Pressed
以上示例实现了非守护进程的信号处理,但对于后台守护进程,显然键盘输入已经无效,这是需要通过其它信号(如SIGTERM)来处理。
我们为demo.php
再安装一个SIGTERM的信号处理器,源代码如下:
pcntl_signal(SIGTERM, function() {
echo PHP_EOL.'Process has been killed'.PHP_EOL;
exit();
});
for($i=0; $i < 60; $i++) {
pcntl_signal_dispatch();
echo $i;
sleep(1);
}
运行结果如下:
/ # php demo.php 1>out 2>&1 &
/ # ps
PID USER TIME COMMAND
44 root 0:00 php demo.php
45 root 0:00 ps
/ # kill -15 44
/ # cat out
012345678910
Process has been killed
[1]+ Done php demo.php 1>out 2>&1
至此,最简单的信号处理已经介绍完毕。wuxing下的WuxingProcess类就是基于此实现的,当后台程序一直在运行时,如果需要更新代码,就不得不重启该守护进程。程序内通过SIGTERM处理器,判断需要终止脚本时,程序内业务处理完后安全地退出。具体实现可自行阅读WuxingProcess的方法。
常见信号种类
信号 | 值 | 发出信号的原因 |
---|---|---|
SIGHUP | 1 | 终端挂起或者控制进程终止 |
SIGINT | 2 | 键盘中断(如break键被按下) |
SIGQUIT | 3 | 键盘的退出键被按下 |
SIGILL | 4 | 非法指令 |
SIGTRAP | 5 | 跟踪/断点捕获 |
SIGABRT | 6 | 由abort(3)发出的退出指令 |
SIGBUS | 7 | 总线错误(错误的内存访问) |
SIGFPE | 8 | 浮点异常 |
SIGKILL | 9 | Kill信号 |
SIGUSR1 | 10 | 用户自定义信号1 |
SIGSEGV | 11 | 无效的内存引用 |
SIGUSR2 | 12 | 用户自定义信号2 |
SIGPIPE | 13 | 管道破裂: 写一个没有读端口的管道 |
SIGALRM | 14 | 由alarm(2)发出的信号 |
SIGTERM | 15 | 终止信号 |
信号的值可能因为系统差异有一些差别,使用常量保证正常工作。