内容简介:Python处理信号是在大体上,Python解释器不太可能会操作系统发出的信号立即做回调。因为Python的Opcode操作是原子操作,不允许被中断。所以Python解释器对信号做一层封装,并做好标记,待时机得当的时候来检查并触发相关的回调函数。信号机制的初始化是在Python初始化整个解释器时开始的,Python在初始化函数中调用
起步
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中会再次注册信号处理函数)
- 多次信号,可能会被合并处理一次
- 按照信号值从小到大处理
以上所述就是小编给大家介绍的《Python内核阅读(二十五):信号处理机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- xenomai内核解析之信号signal(二)---xenomai信号处理机制
- Python信号处理
- Linux信号处理机制
- 写给 PHP 程序员的信号处理教程
- 音频可视化中的信号处理方案
- 开放下载!《阿里语音与信号处理技术》精选集
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。