![](/style/images/good.png)
![](/style/images/bad.png)
Python内核阅读(二十五):信号处理机制
source link: http://www.hongweipeng.com/index.php/archives/1607/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
起步
Python处理信号是在 signal
模块中,这个模块其实是纯python代码对 _signal
的封装。要想知道Python解释器本身如何处理信号以及如何实现的,还需要去了解 signalmodule.c
。其中,比较需要了解的是python解释器与操作系统有关信号的交互。
大体上,Python解释器不太可能会操作系统发出的信号立即做回调。因为Python的Opcode操作是原子操作,不允许被中断。所以Python解释器对信号做一层封装,并做好标记,待时机得当的时候来检查并触发相关的回调函数。
信号机制的初始化
信号机制的初始化是在Python初始化整个解释器时开始的,Python在初始化函数中调用 initsigs()
来进行整个系统以及 singal
模块的初始化。
[Python/pylifecycle.c] _PyInitError _Py_InitializeMainInterpreter(PyInterpreterState *interp, const _PyMainInterpreterConfig *config) { ... if (interp->config.install_signal_handlers) { err = initsigs(); /* Signal handling stuff, including initintr() */ if (_Py_INIT_FAILED(err)) { return err; } } ... }
而在 initsigs(void)
函数中,则是直接对系统调用的封装:
[Python/pylifecycle.c] static _PyInitError initsigs(void) { #ifdef SIGPIPE PyOS_setsig(SIGPIPE, SIG_IGN); // 忽略SIGPIPE #endif #ifdef SIGXFZ PyOS_setsig(SIGXFZ, SIG_IGN); // 忽略SIGXFZ #endif #ifdef SIGXFSZ PyOS_setsig(SIGXFSZ, SIG_IGN); // 忽略SIGXFSZ file size exceeded #endif PyOS_InitInterrupts(); /* May imply initsignal() */ if (PyErr_Occurred()) { return _Py_INIT_ERR("can't import signal"); } return _Py_INIT_OK(); }
暂时不知道忽略了那几个信号的原因。而 PyOS_InitInterrupts(void)
函数中其实就是 import _signal
:
[Modules/signalmodule.c] void PyOS_InitInterrupts(void) { PyObject *m = PyImport_ImportModule("_signal"); if (m) { Py_DECREF(m); } }
在 _signal
模块的初始化中:
[Modules/signalmodule.c] PyMODINIT_FUNC PyInit__signal(void) { PyObject *m, *d, *x; int i; main_thread = PyThread_get_thread_ident(); main_pid = getpid(); // 创建signal模块 m = PyModule_Create(&signalmodule); if (m == NULL) return NULL; ... /* Add some symbolic constants to the module */ d = PyModule_GetDict(m); // 将SIG_DFL、SIGIGN 转化成Python整数对象 x = DefaultHandler = PyLong_FromVoidPtr((void *)SIG_DFL); if (!x || PyDict_SetItemString(d, "SIG_DFL", x) < 0) goto finally; x = IgnoreHandler = PyLong_FromVoidPtr((void *)SIG_IGN); if (!x || PyDict_SetItemString(d, "SIG_IGN", x) < 0) goto finally; x = PyLong_FromLong((long)NSIG); if (!x || PyDict_SetItemString(d, "NSIG", x) < 0) goto finally; Py_DECREF(x); ... /* * 获取signal模块中的默认中断处理函数, * 实际就是 signal_default_int_handler */ x = IntHandler = PyDict_GetItemString(d, "default_int_handler"); if (!x) goto finally; Py_INCREF(IntHandler); /* * 初始化Python解释器中的Handler, * 这个数组存储每个用户自定义的信号处理函数 * 以及标志是否发生该信号的标志。 */ _Py_atomic_store_relaxed(&Handlers[0].tripped, 0); for (i = 1; i < NSIG; i++) { void (*t)(int); t = PyOS_getsig(i); _Py_atomic_store_relaxed(&Handlers[i].tripped, 0); if (t == SIG_DFL) Handlers[i].func = DefaultHandler; else if (t == SIG_IGN) Handlers[i].func = IgnoreHandler; else Handlers[i].func = Py_None; /* None of our business */ Py_INCREF(Handlers[i].func); } //为 SIGINT 设置默认的信号处理函数signal_handler if (Handlers[SIGINT].func == DefaultHandler) { /* Install default int handler */ Py_INCREF(IntHandler); Py_SETREF(Handlers[SIGINT].func, IntHandler); PyOS_setsig(SIGINT, signal_handler); } // 实现signal模块中的各个 SIGXXX 信号值和名称 #ifdef SIGHUP if (PyModule_AddIntMacro(m, SIGHUP)) goto finally; #endif .... if (PyErr_Occurred()) { Py_DECREF(m); m = NULL; } finally: return m; }
可以看到,用户自定义的处理函数将会保存在 Handler
数组中,而实际上向操作系统注册 signal_signal_impl
函数。这个函数将作为Python解释器和用户自定义处理函数的桥梁:
[Modules/signalmodule.c] static PyObject * signal_signal_impl(PyObject *module, int signalnum, PyObject *handler) /*[clinic end generated code: output=b44cfda43780f3a1 input=deee84af5fa0432c]*/ { PyObject *old_handler; void (*func)(int); #ifdef MS_WINDOWS /* Validate that signalnum is one of the allowable signals */ switch (signalnum) { case SIGABRT: break; case SIGTERM: break; ... default: PyErr_SetString(PyExc_ValueError, "invalid signal value"); return NULL; } #endif // 只有主线程才能设置信号处理函数 if (PyThread_get_thread_ident() != main_thread) { PyErr_SetString(PyExc_ValueError, "signal only works in main thread"); return NULL; } if (signalnum < 1 || signalnum >= NSIG) { PyErr_SetString(PyExc_ValueError, "signal number out of range"); return NULL; } if (handler == IgnoreHandler) func = SIG_IGN; else if (handler == DefaultHandler) func = SIG_DFL; else if (!PyCallable_Check(handler)) { PyErr_SetString(PyExc_TypeError, "signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object"); return NULL; } else func = signal_handler; // Python解释器向系统注册的都是signal_handler函数 /* Check for pending signals before changing signal handler */ if (PyErr_CheckSignals()) { return NULL; } if (PyOS_setsig(signalnum, func) == SIG_ERR) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } // 把实际的用户自定义信号处理函数,放入对应的Handler数组中进行替换 old_handler = Handlers[signalnum].func; Py_INCREF(handler); Handlers[signalnum].func = handler; if (old_handler != NULL) return old_handler; else Py_RETURN_NONE; }
函数 signal_handler
是直接由C信号机制进行的回调。如果用户注册了信号处理函数,那么会取代旧的处理函数。
信号产生时
当C层发出信号并进行回调 signal_handler(int sig_num)
:
[Modules/signalmodule.c] static void signal_handler(int sig_num) { int save_errno = errno; /* See NOTES section above */ if (getpid() == main_pid) { trip_signal(sig_num); } #ifndef HAVE_SIGACTION #ifdef SIGCHLD /* To avoid infinite recursion, this signal remains reset until explicit re-instated. Don't clear the 'func' field as it is our pointer to the Python handler... */ if (sig_num != SIGCHLD) #endif /* If the handler was not set up with sigaction, reinstall it. See * Python/pylifecycle.c for the implementation of PyOS_setsig which * makes this true. See also issue8354. */ PyOS_setsig(sig_num, signal_handler); #endif /* Issue #10311: asynchronously executing signal handlers should not mutate errno under the feet of unsuspecting C code. */ errno = save_errno; #ifdef MS_WINDOWS if (sig_num == SIGINT) SetEvent(sigint_event); #endif } static void trip_signal(int sig_num) { unsigned char byte; int fd; Py_ssize_t rc; // 标记位设为1 ,表示信号产生了 _Py_atomic_store_relaxed(&Handlers[sig_num].tripped, 1); /* Set is_tripped after setting .tripped, as it gets cleared in PyErr_CheckSignals() before .tripped. */ // 如果正在处理信号,则不再向Python虚拟机提交 _Py_atomic_store(&is_tripped, 1); /* Notify ceval.c */ _PyEval_SignalReceived(); #ifdef MS_WINDOWS fd = Py_SAFE_DOWNCAST(wakeup.fd, SOCKET_T, int); #else fd = wakeup.fd; #endif if (fd != INVALID_FD) { byte = (unsigned char)sig_num; #ifdef MS_WINDOWS ... #endif { /* _Py_write_noraise() retries write() if write() is interrupted by a signal (fails with EINTR). */ rc = _Py_write_noraise(fd, &byte, 1); if (rc < 0) { if (wakeup.warn_on_full_buffer || (errno != EWOULDBLOCK && errno != EAGAIN)) { // 向Python虚拟机提交pending_call,纳入到整个虚拟机的执行过程中 Py_AddPendingCall(report_wakeup_write_error, (void *)(intptr_t)errno); } } } } }
当C语言触发回调后,该回调函数会进行设置标记位并将 report_wakeup_write_error
加入到虚拟机的执行过程中,通过跟踪会调用 PyErr_CheckSignals()
进行信号的检查:
[Modules/signalmodule.c] int PyErr_CheckSignals(void) { int i; PyObject *f; if (!_Py_atomic_load(&is_tripped)) return 0; if (PyThread_get_thread_ident() != main_thread) return 0; // 在处理信号了,将标志位设为0 _Py_atomic_store(&is_tripped, 0); if (!(f = (PyObject *)PyEval_GetFrame())) f = Py_None; // 按照信号值从小到大依次调用对应的信号处理函数 for (i = 1; i < NSIG; i++) { if (_Py_atomic_load_relaxed(&Handlers[i].tripped)) { PyObject *result = NULL; PyObject *arglist = Py_BuildValue("(iO)", i, f); _Py_atomic_store_relaxed(&Handlers[i].tripped, 0); if (arglist) { // 调用回调函数 result = PyEval_CallObject(Handlers[i].func, arglist); Py_DECREF(arglist); } if (!result) { _Py_atomic_store(&is_tripped, 1); return -1; } Py_DECREF(result); } } return 0; }
这里面的 PyErr_CheckSignals
函数允许被其他模块调用直接信号的处理。
总结
可以看到整个信号处理的流程:
- 初始化signal模块,将对应的操作系统信号值、函数转化成Python对象
- 用户设置信号就向操作系统注册函数signal_handler,并将用户自定义信号处理函数设置到对应的Handler数组中
- 当信号发生时,操作系统调用signal_handler设置tripped=1,然后调用trip_signal将统一处理函数checksignals_witharg作为pendingcall注册到Python虚拟机的执行栈中。
- Python虚拟机在处理pendingcall时调用checksignals_withargs,从而信号处理函数得以执行。
- 另外,Python其他模块可以直接调用PyErr_CheckSignals进行信号处理。
对于书写python代码的开发这而言:
- 只有主线程能够设置、捕获和处理信号
- 信号设置一直有效(signal_handler中会再次注册信号处理函数)
- 多次信号,可能会被合并处理一次
- 按照信号值从小到大处理
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK