65

Python内核阅读(二十五):信号处理机制

 5 years ago
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中会再次注册信号处理函数)
  • 多次信号,可能会被合并处理一次
  • 按照信号值从小到大处理

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK