formats

test

test

 
formats

Python 信号机制深入学习

Published on 十二月 6th, 2011 by in python
  1. 前世

python的信号机制是由signal模块实现,如下代码:

import signal

 

def signalHandler(num , frame):

pass

 

signal.signal(signal.SIGTERM,signalHandler)

 

则是向系统(Python 虚拟机)注册一个处理SIGTERM 信号的函数,当程序收到系统SIGTERM 信号时,signalHandler将被执行。

 

那么,这整个过程在Python是怎么实现的呢?下面我将从回调函数(signalHandler)的注册,到其被调用这一过程来深入分析其信号机制。

  1. 今生

欲追本溯源,则需先找到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中的信号机制总结如下:

  1. Python接受到信号,并没有立刻执行响应函数,只是加到队列。
  2. 如果在C扩展中,收到信号,Python会在C扩展执行完后,才能响应信号,如果此时有长时间的计算或堵塞操作,信号的处理会被不定时间延迟。
 
formats

重新开博

坚持很难,断断续续写了一些,VPS换了又换,以前的文章就干脆放弃了,重新开写,希望可以坚持,但不强求~

 
 
© magic codes
credit