libevent源碼分析三--signal事件響應

  libevent支持io事件,timeout事件,signal事件,這篇文件將分析libevent是如何組織signal事件,以及如何實現signal事件響應的。後端

1.  sigmap

相似於io事件,event_base有另一個hash表sigmap用於存儲signal事件,hash表使用signal number作數組索引,同一個signal number的不一樣事件使用雙向鏈表鏈接(使用struct event結構中的ev_. ev_signal. ev_signal_next成員來構造這個鏈表)。使用到的數據結構有struct event_signal_map與struct evmap_signal,定義以下:數組

/* Used to map signal numbers to a list of events.  If EVMAP_USE_HT is not
   defined, this structure is also used as event_io_map, which maps fds to a
   list of events.
*/
struct event_signal_map {
    /* An array of evmap_io * or of evmap_signal *; empty entries are
     * set to NULL. */
    void **entries;
    /* The number of entries available in entries */
    int nentries;
};
/* An entry for an evmap_signal list: notes all the events that want to know
   when a signal triggers. */
struct evmap_signal {
    struct event_dlist events;
};

event_base中的成員sigmap便是struct event_signal_map結構的實例。entries指向一個動態分配的指針數組,數組長度爲nentries;數組成員是一個指向動態分配的evmap_sinal結構的指針。sigmap的結構大體能夠用圖1-1表示,數據結構

圖1-1 sigmap存儲結構socket

2.  signal後端

  signal事件的後端與io事件的後端不一樣,它們的操做基本不相同,libevent在源文件signal.c中實現signal事件的後端操做,並在event_base中使用成員struct eventop *evsigsel專門指向signal事件的後端操做,未複用指向io後端的evsel指針,signal後端使用到的信息保存在struct evsig_info sig成員中。函數

  struct evsig_info的定義以下,這裏主要介紹ev_signal與ev_signal_pair[2]這兩個成員。oop

/* Data structure for the default signal-handling implementation in signal.c
 */
struct evsig_info {
    /* Event watching ev_signal_pair[1] */
    struct event ev_signal;
    /* Socketpair used to send notifications from the signal handler */
    evutil_socket_t ev_signal_pair[2];
    /* True iff we've added the ev_signal event yet. */
    int ev_signal_added;
    /* Count of the number of signals we're currently watching. */
    int ev_n_signals_added;

    /* Array of previous signal handler objects before Libevent started
     * messing with them.  Used to restore old signal handlers. */
#ifdef EVENT__HAVE_SIGACTION
    struct sigaction **sh_old;
#else
    ev_sighandler_t **sh_old;
#endif
    /* Size of sh_old. */
    int sh_old_max;
};
  • ev_signal_pair[2]是兩個文件描述符,一般使用pipe函數建立。
  • ev_signal是一個專職事件(內部事件),監聽ev_signal_pair[0]上的讀事件,它做爲一個io事件被加入到event_base中,並將優先級設置爲0,以便一旦該事件發生,它的回調函數能被優先執行。

  在signal後端中,每個被註冊了事件的signal都會使用sigaction函數將signal handler修改成統一的函數static void __cdecl evsig_handler(int sig),這個函數的做用就是向ev_signal_pair[1]中寫觸發的signal_number,激活事件ev_signal。函數定義以下(刪除了跨平臺代碼):ui

static void __cdecl
evsig_handler(int sig)
{
    int save_errno = errno;
    ev_uint8_t msg;

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

    /* Wake up our notification mechanism */
    msg = sig;
    {
        int r = write(evsig_base_fd, (char*)&msg, 1);
        (void)r; /* Suppress 'unused return value' and 'unused var' */
    }

    errno = save_errno;
}

  ev_signal的回調函數也是在signal.c源文件中定義,static void evsig_cb(evutil_socket_t fd, short what, void *arg),它的做用就是從ev_signal_pair[0]上讀回被觸發的signal number,並將sigmap上對應signal_number的全部event加入到event_base的待執行回調函數中。函數定義以下:this

/* Callback for when the signal handler write a byte to our signaling socket */
static void
evsig_cb(evutil_socket_t fd, short what, void *arg)
{
    static char signals[1024];
    ev_ssize_t n;
    int i;
    int ncaught[NSIG];
    struct event_base *base;

    base = arg;
    memset(&ncaught, 0, sizeof(ncaught));

    while (1) {

        n = read(fd, signals, sizeof(signals));

        if (n == -1) {
            int err = evutil_socket_geterror(fd);
            if (! EVUTIL_ERR_RW_RETRIABLE(err))
                event_sock_err(1, fd, "%s: recv", __func__);
            break;
        } else if (n == 0) {
            /* XXX warn? */
            break;
        }
        for (i = 0; i < n; ++i) {
            ev_uint8_t sig = signals[i];
            if (sig < NSIG)
                ncaught[sig]++;
        }
    }

    EVBASE_ACQUIRE_LOCK(base, th_base_lock);
    for (i = 0; i < NSIG; ++i) {
        if (ncaught[i])
            evmap_signal_active_(base, i, ncaught[i]);//將signal number i 對應的事件加入到待執行回調函數鏈表中
    }
    EVBASE_RELEASE_LOCK(base, th_base_lock);
}

  當signal發生時,evsig_handler會向ev_signal_pair[1]寫數據(寫入singal number),激活ev_signal事件,ev_signal事件的回調函數evsig_cb會從ev_signal_pair[0]上讀到被觸發的signal number,而後據此從event_base的sigmap中將對應的全部event的回調函數加入到待執行回調函數鏈表,這些回調函數將在event_base_loop中被執行,完成對signal事件的響應。spa

相關文章
相關標籤/搜索