前几天,同事提到商用系统上出现过使用kill
命令给进程发送SIGKILL
信号,进程无法退出的问题。尽管听起来有些不可思议,但是的确发生了。由于当时的环境没有保存下来,所以现在没法debug
。我考虑了一下,如果再发生,可以使用DTrace
去检查一下问题出现在哪里。
用kill
命令给进程发送信号可以分成3个步骤:
(1)kill
命令成功发送信号;
(2)进程收到信号;
(3)进程开始处理信号。
我们可以用DTrace
检查步骤(1)和(2),如果能证明进程的确收到信号,那么十之八九就是程序逻辑的问题了。
首先要做的是检查步骤(1),这个可以使用Brendan Gregg的DTraceToolkit里的kill.d
脚本来检查:
dtrace:::BEGIN
{
/* Print header */
printf("%5s %12s %5s %-6s %s\n",
"FROM", "COMMAND", "SIG", "TO", "RESULT");
}
syscall::kill:entry
{
/* Record target PID and signal */
self->target = arg0;
self->signal = arg1;
}
syscall::kill:return
{
/* Print source, target, and result */
printf("%5d %12s %5d %-6d %d\n",
pid, execname, self->signal, self->target, (int)arg0);
/* Cleanup memory */
self->target = 0;
self->signal = 0;
}
脚本比较简单,就是检查整个系统里所有运行进程发送的kill
命令是否成功。
接着检查步骤(2),可以用proc provider
的signal-handle probe
。这个probe
会在进程处理信号之前触发,所以可以用来检查进程是否收到了信号。我写了个简单的脚本catch.d
:
proc:::signal-handle
/pid == $target/
{
trace(args[0]);
}
args[0]
是收到的信号值。
接下来写个简单的测试程序,测试一下:
#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) {
// your code goes here
signal(SIGHUP, SIG_IGN);
signal(SIGINT, handler);
while (1)
{
sleep(1);
}
return 0;
}
编译运行,用psig
命令查看一下进程对信号的处理:
bash-3.2# psig 13297
13297: ./test
HUP ignored
INT caught handler RESETHAND,NODEFER
QUIT default
可以看到程序对SIGHUP
、SIGINT
和SIGQUIT
处理分别是ignore
、catch
和default
,这样就覆盖了UNIX
系统程序对信号处理的3种方式。
接下来分别在不同终端启动kill.d
和catch.d
。然后再在一个终端连续发送3个信号给测试程序:
bash-3.2# kill -HUP 13297
bash-3.2# kill -INT 13297
bash-3.2# kill -QUIT 13297
再检查kill.d
脚本输出:
bash-3.2# ./kill.d
FROM COMMAND SIG TO RESULT
18198 bash 1 13297 0
18198 bash 2 13297 0
18198 bash 3 13297 0
可以看到信号都已经成功发送。
最后检查catch.d
脚本输出:
bash-3.2# ./catch.d -p 13297
dtrace: script './catch.d' matched 1 probe
CPU ID FUNCTION:NAME
14 1163 psig:signal-handle 2
15 1163 psig:signal-handle 3
dtrace: pid 13297 has exited
可以看到进程也收到了信号。
所以如果下次商用系统再出现类似问题,就可以用这两个脚本分析了。
普通进程没法屏蔽SIGKILL,但是内核代码可以。有可能是程序的某个系统调用时,内核态代码屏蔽了信号处理,然后等待某些条件被满足(网络或者IO)。也许可以通过Dtrace脚本查看应用程序的相关代码路径或状态来定位问题。
Jinzhi:你提到的这一点我之前的确没有了解,下次出现时可以考虑一下。非常感谢你的指点!