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存储结构

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]这两个成员。

/* 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。函数定义如下(删除了跨平台代码):

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的待执行回调函数中。函数定义如下:

/* 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事件的响应。

 

posted @ 2019-08-16 17:59  yangzd~  阅读(464)  评论(0编辑  收藏  举报