libevent高性能网络库源码分析——事件循环(五)

      • libevent事件循环的流程
      • 事件循环源码分析
      • 结束事件循环

libevent事件循环的流程

libevent将IO事件、信号事件和定时器事件很好的结合在一起,采用了统一的事件源方式,即把信号事件也转换成IO事件,然后采用同一套IO复用机制去监听。

libevent的事件循环通过event_base_loop完成,另外一个事件循环函数是event_base_dispatch,其功能上即为没有设置标志的 event_base_loop(base, 0)。即event_base_dispatch()将一直运行,直到没有已经注册的事件了,或者调用了event_base_loopbreak()或者event_base_loopexit()为止。整个事件的循环流程如下图所示:

libevent高性能网络库源码分析——事件循环(五)_第1张图片

说明:
1 . 时间校正

  • libevent中有个参数use_monotonic,该参数表示时间是否是单调增长的,该值为0则表示时间可能会往后调整,即往更早的时间去调。每一次循环时,需要判断时间是否需要调整,如果时间被往后调整至t1时刻,那么event_base中的所有事件都需要进行校正至t1时刻。
  • libevent维护了两套方案管理时间基于时间的小顶堆min-heap,还有基于通用超时构成的链表队列。 两种方式在不同的场景下时间复杂度各有差异。基于min-heap的获取最小时间的事件复杂度为O(1)。

2 . 事件等待
在循环前,libevent会检查signal标志位是否被设置。若设置要监听SIGINT这个信号,那么在实现的内部就对SIGINT这个信号设置捕抓函数。此外,在实现的内部还要建立一条管道(pipe,写管道ev_signal_pair[0]),并把这个管道加入到多路IO复用函数中。当SIGINT这个信号发生后,捕抓函数将会被调用。而这个捕抓函数的工作就是往管道写入一个字符(这个字符往往等于所捕抓到信号的信号值)。此时,这个管道ev_signal_pair[1]就变成是可读的了,多路IO复用函数能检测到这个管道变成可读的了。换句话说就是多路IO复用函数检测到这个SIGINT信号发生了,这也就完成了对信号的监听工作。

libevent高性能网络库源码分析——事件循环(五)_第2张图片

3 . 事件执行
对于激活的事件执行方式:按优先级从高到低。因而,低优先级的事件可能不能立即得到执行。

事件循环源码分析

int
event_base_loop(struct event_base *base, int flags)
{
    ....
    /* Grab the lock.  We will release it inside evsel.dispatch, and again as we invoke user callbacks. */
    EVBASE_ACQUIRE_LOCK(base, th_base_lock);

    if (base->running_loop) {
        event_warnx("%s: reentrant invocation.  Only one event_base_loop"
            " can run on each event_base at once.", __func__);
        EVBASE_RELEASE_LOCK(base, th_base_lock);
        return -1;
    }
    base->running_loop = 1;
    clear_time_cache(base);

    // 检查signal信号标记是否被设置
    if (base->sig.ev_signal_added && base->sig.ev_n_signals_added)
        // 设置evsignal_base=base,其为全局变量。
        // 处理signal时,用于指明signal所属的event_base实例
        evsig_set_base(base);

    done = 0;
    ....
    while (!done) {
        base->event_continue = 0;

        // 查看是否需要跳出循环,程序可以调用event_loopexit_cb()设置event_gotterm标记
        if (base->event_gotterm) {
            break;
        }

        // 调用event_base_loopbreak()设置event_break标记
        if (base->event_break) {
            break;
        }

        // 时间校正,若时间有问题,需要更新min-heap和common-timeout_list的超时时间
        timeout_correct(base, &tv);

        // 根据timer heap中事件的最小超时时间,计算系统I/O多路监听的最大等待时间
        tv_p = &tv;
        if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
            timeout_next(base, &tv_p);
        } else {
            /*
             * if we have active events, we just poll new events
             * without waiting.
             */
            evutil_timerclear(&tv);
        }

        // 如果当前没有注册事件,就退出
        if (!event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) {
            event_debug(("%s: no events registered.", __func__));
            retval = 1;
            goto done;
        }

        // 更新last wait time,并清空time cache
        gettime(base, &base->event_tv);
        clear_time_cache(base);

        // 调用系统IO多路复用机制监听IO事件,若设置了signal标志位,则signal事件也会被监听为IO事件。
        // 将signal event、IO event插入到激活链表event_io_map中
        res = evsel->dispatch(base, tv_p);

        if (res == -1) {
            event_debug(("%s: dispatch returned unsuccessfully.",
                __func__));
            retval = -1;
            goto done;
        }

        // 将event_base的time cache赋值为当前系统时间
        update_time_cache(base);

        // 检查heap中的timer events,将就绪的timer event从min-heap上删除,
并插入到激活链表中
        timeout_process(base);

        if (N_ACTIVE_CALLBACKS(base)) {
            // 按优先级处理链表中的所有激活事件
            int n = event_process_active(base);
            if ((flags & EVLOOP_ONCE)
                && N_ACTIVE_CALLBACKS(base) == 0
                && n != 0)
                done = 1;
        } else if (flags & EVLOOP_NONBLOCK)
            done = 1;
    }
    event_debug(("%s: asked to terminate loop.", __func__));

done:

    // 循环结束,清空时间缓存
    clear_time_cache(base);
    base->running_loop = 0;
    EVBASE_RELEASE_LOCK(base, th_base_lock);
    return (retval);
}

其中,signal的结构体为:

//evsignal-internal.h文件
struct evsig_info {
  //用于监听socketpair读端的event. ev_signal_pair[1]为读端
  struct event ev_signal;
  //socketpair
  evutil_socket_t ev_signal_pair[2];
  //用来标志是否已经将ev_signal这个event加入到event_base中了
  int ev_signal_added;
  //用户一共要监听多少个信号
  int ev_n_signals_added;

  //数组。用户可能已经设置过某个信号的信号捕抓函数。但
  //Libevent还是要为这个信号设置另外一个信号捕抓函数,
  //此时,就要保存用户之前设置的信号捕抓函数。当用户不要
  //监听这个信号时,就能够恢复用户之前的捕抓函数。
  //因为是有多个信号,所以得用一个数组保存。
#ifdef _EVENT_HAVE_SIGACTION
  struct sigaction **sh_old; 
#else//保存的是捕抓函数的函数指针,又因为是数组。所以是二级指针
  ev_sighandler_t **sh_old; 
#endif
  /* Size of sh_old. */
  int sh_old_max; //数组的长度
};

结束事件循环

在libevent中,使用event_base_loopexit函数结束一个事件循环,这个函数其实只是注册了一个只运行一次的定时器(event_loopexit_cb),而event_loopexit_cb只是简单地将标志位(event_gotterm)设置为1,如下:

int
event_base_loopexit(struct event_base *event_base, const struct timeval *tv)
{
    return (event_base_once(event_base, -1, EV_TIMEOUT, event_loopexit_cb,
            event_base, tv));
}
/** Callback: used to implement event_base_loopexit by telling the event_base
 * that it's time to exit its loop. */
static void
event_loopexit_cb(evutil_socket_t fd, short what, void *arg)
{
    struct event_base *base = arg;
    base->event_gotterm = 1;
}

本文参考:
1 . Libevent源码分析—–信号event的处理
2 . libevent源码深度剖析

你可能感兴趣的:(libevent详解)