test
Python 信号机制深入学习
- 前世
python的信号机制是由signal模块实现,如下代码:
“
import signal
def signalHandler(num , frame):
pass
signal.signal(signal.SIGTERM,signalHandler)
“
则是向系统(Python 虚拟机)注册一个处理SIGTERM 信号的函数,当程序收到系统SIGTERM 信号时,signalHandler将被执行。
那么,这整个过程在Python是怎么实现的呢?下面我将从回调函数(signalHandler)的注册,到其被调用这一过程来深入分析其信号机制。
- 今生
欲追本溯源,则需先找到signal.signal()函数是怎么实现。在文件$src/Modules/signalmodule.c ,可以找到static PyObject signal_signal(PyObject *self, PyObject *args),这就是函数的实现。
函数主要做了两个操作 1. 向操作系统注册信号处理函数signal_handler 。 2. 将python代码的注册函数保存到一个Handlers数组中,数组的下标就是信号的代码(这里是SIGTERM)。
来看一下Handlers:
“
static struct {
int tripped;
PyObject *func;
} Handlers[NSIG];
”
可见,这是一个结构的数组,结构中有两个成员,func 很明显是用来用户向python注册的函数,整形的tripped , 0 表示未触发信号 , 1 表示触发了信号,需要执行 。 Python会在某个时刻遍历
Handlers数组,执行tripped 标志为1 的函数。
至此,信号处理函数的注册过程就完成了。从上面分析可以看到,Python并没有向系统注册用户自定义的函数(signalHandler), 而是注册了自己的signal_handler函数。可以大胆的猜测,当信号触发的时候,signal_handler函数执行,他必然通过一定的机制,获取到Handlers中保存的相应的函数,并执行。
来看一下signal_handler($src/Modules/signalmodule.c)函数。它为用户注册的函数设置标记 Handlers[sig_num].tripped = 1;然后调用Py_AddPendingCall(checksignals_witharg, NULL); 从函数名字来看, Py_AddPendingCall是保存了一个函数,稍后执行(pending的意思是 “有待”、“听侯”、“未决定”, 来自谷歌翻译),姑且这么理解,稍后在做探究。参数 checksignals_witharg 是一个函数,调用了PyErr_CheckSignals
“
static int
checksignals_witharg(void * unused)
{
return PyErr_CheckSignals();
}
”
PyErr_CheckSignals做的事情正如我们猜测,遍历Handlers数组,调用结构中stripped 值为1 对应的函数。
再来看一下Py_AddPendingCall ,$src/Python/ceval.c有它的实现。其流程如下:
“
if (busy)
return -1;
busy = 1;
”
给函数加锁
“
i = pendinglast;
j = (i + 1) % NPENDINGCALLS;
if (j == pendingfirst) {
busy = 0;
return -1; /* Queue full */
}
”
检查pendingcalls 数组是否已满,如果满了,则忽略该信号。
“
pendingcalls[i].func = func;
pendingcalls[i].arg = arg;
pendinglast = j;
”
将checksignals_witharg 函数加入到pendingcalls数组,pendingcalls 定义如下:
#define NPENDINGCALLS 32
static struct {
int (*func)(void *);
void *arg;
} pendingcalls[NPENDINGCALLS];
可见,checksignals_witharg 函数只是被缓存起来,并没有执行,那它时候时候执行呢?
Py_MakePendingCalls函数负责调用 pendingcalls 中的函数。而Py_MakePendingCalls 则在Python的主循环中被调用。函数为:PyEval_EvalFrameEx
流程如下:
for(;;)
{
….
Py_MakePendingCalls(…)
….
执行python字节码
}
由此可见 Py_MakePendingCalls 在python处理字节码的过程中被调用。
因此,Python中的信号机制总结如下:
- Python接受到信号,并没有立刻执行响应函数,只是加到队列。
- 如果在C扩展中,收到信号,Python会在C扩展执行完后,才能响应信号,如果此时有长时间的计算或堵塞操作,信号的处理会被不定时间延迟。





No Comments » 