现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

libevent事件循环的流程

2018-04-08 23:04 工业·编程 ⁄ 共 4585字 ⁄ 字号 暂无评论

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 . 时间校正

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信号发生了,这也就完成了对信号的监听工作。

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;

}

给我留言

留言无头像?