(十一)信號事件的管理

前言

在上一小節中我們主要講解了信號事件是如何合併到多路I/O複用機制中的以及信號事件的初始化。在本小節中,我們將看到有關信號事件的主要操作。

信號事件的註冊

前面我們看到了信號事件是在何時何地如何被初始化的,一個事件無非就是初始化、註冊、激活、回調、註銷這幾個重要的操作。接下來我們看看信號事件註冊相關的,即evsignal_add函數,它在signal.c文件中定義。

int
evsignal_add(struct event *ev)
{
    int evsignal;
    struct event_base *base = ev->ev_base;
    struct evsignal_info *sig = &ev->ev_base->sig;
    //讀/寫事件不屬於這兒
    if (ev->ev_events & (EV_READ|EV_WRITE))
        event_errx(1, "%s: EV_SIGNAL incompatible use", __func__);
    /*
     * 這裏是個宏函數,在event.h中定義如下
     * #define EVENT_SIGNAL(ev)   (int)(ev)->ev_fd
     * 作用就是取得信號值
     */
    evsignal = EVENT_SIGNAL(ev);
    assert(evsignal >= 0 && evsignal < NSIG);
    //沒有事件註冊到該evsignal信號上
    if (TAILQ_EMPTY(&sig->evsigevents[evsignal])) {
        event_debug(("%s: %p: changing signal handler", __func__, ev));
        /* 如有必要,擴大給sh_old分配的內存
         * 給該信號設置新的信號捕捉函數
         */
        if (_evsignal_set_handler(
                base, evsignal, evsignal_handler) == -1)
            return (-1);

        /* catch signals if they happen quickly */
        evsignal_base = base;
        //該信號事件未註冊過
        if (!sig->ev_signal_added) {
            //註冊該事件
            if (event_add(&sig->ev_signal, NULL))
                return (-1);
            sig->ev_signal_added = 1;
        }
    }

    /* multiple events may listen to the same signal */
    //將信號事件添加到信號evsignal對應的事件鏈表中
    TAILQ_INSERT_TAIL(&sig->evsigevents[evsignal], ev, ev_signal_next);

    return (0);
}

該函數主要邏輯就是:

  • 判斷信號有無對應的註冊事件鏈表
    1. 無,判斷是否需要重新分配內存,接着便註冊該事件
    2. 有,直接將該事件添加到信號對應的事件鏈表中

這裏初看的話可能你會覺得有點迷糊,因爲有兩個註冊事件的操作,而且不一樣。
- event_add註冊的事件,是到時候信號事件被激活了,真正執行的操作。
- TAILQ_INSERT_TAIL這個執行的操作雖然也是將信號事件加入鏈表中,但是是不同的鏈表。它是收到信號之後,會執行的操作,可以看到_evsignal_set_handler設置的都是evsignal_handler捕捉函數,不管是哪個信號。等下我們會講這個函數。

總的來說,evsignal_handler,主要是執行寫socket這邊寫數據觸發讀socket事件,然後多路I/O機制就可以成功的知道信號發生了,從而將event_add註冊的信號事件激活。
evsignal_handler代碼如下:

static void
evsignal_handler(int sig)
{
    int save_errno = errno;

    if (evsignal_base == NULL) {
        event_warn(
            "%s: received signal %d, but have no base configured",
            __func__, sig);
        return;
    }

    evsignal_base->sig.evsigcaught[sig]++;
    evsignal_base->sig.evsignal_caught = 1;

#ifndef HAVE_SIGACTION
    signal(sig, evsignal_handler);
#endif

    /* Wake up our notification mechanism */
    send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0);
    errno = save_errno;
}

裏面完成的就主要是註冊信號捕捉函數以及給讀socket發送數據。這樣就可以理解前面那個可能導致你迷惑的知識了。

激活信號事件

我們最後再來看看是如何激活一個信號事件的:

void
evsignal_process(struct event_base *base)
{
    struct evsignal_info *sig = &base->sig;
    struct event *ev, *next_ev;
    sig_atomic_t ncalls;
    int i;

    base->sig.evsignal_caught = 0;
    /*
     * NSIG是個宏定義,代碼支持的信號最大值
     * 遍歷該信號的事件鏈表
     */
    for (i = 1; i < NSIG; ++i) {
        ncalls = sig->evsigcaught[i];
        //ncalls代表信號接收到的次數,爲0則不需要進行激活了
        if (ncalls == 0)
            continue;
        sig->evsigcaught[i] -= ncalls;

        //處理該信號對應事件鏈表上的每個事件
        for (ev = TAILQ_FIRST(&sig->evsigevents[i]);
            ev != NULL; ev = next_ev) {
            next_ev = TAILQ_NEXT(ev, ev_signal_next);
            //如果不是永久事件,則激活了將其刪除
            if (!(ev->ev_events & EV_PERSIST))
                event_del(ev);
            //進行激活操作
            event_active(ev, EV_SIGNAL, ncalls);
        }

    }
}

小結

關於信號事件管理部分,還有註銷等操作,這些和註冊大致相同,就不一一講解了。主要是要理解socket pair以及它們各自對應的事件是怎樣的。關於寫socket這邊,信號發生之後,會調用send函數給讀socket發數據從而觸發讀事件,導致該事件被激活,最後被調度。
接下來,我們會取看一看關於定時事件方面的管理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章