跳转至

libevent 源码深度剖析 06

初见事件处理框架

前面已经对 libevent 的事件处理框架和 event 结构体做了描述,现在是时候 剖析 libevent 对事件的详细处理流程 了,本节将分析 libevent 的事件处理框架 event_base 和 libevent 注册、删除事件的具体流程,可结合前一节 libevent 对 event 的管理。

1. 事件处理框架 -event_base

回想 Reactor 模式的几个基本组件,本节讲解的部分对应于 Reactor 框架组件。在 libevent 中,这就表现为 event_base 结构体,结构体声明如下,它 位于 event-internal.h 文件 中:

struct event_base {
    const struct eventop *evsel;
    void *evbase; 
    int event_count;  /* counts number of total events */
    int event_count_active; /* counts number of active events */
    int event_gotterm;  /* Set to terminate loop */
    int event_break;  /* Set to terminate loop immediately */
    /* active event management */
    struct event_list **activequeues;
    int nactivequeues;
    /* signal handling info */
    struct evsignal_info sig;
    struct event_list eventqueue;
    struct timeval event_tv;
    struct min_heap timeheap;
    struct timeval tv_cache;
};

下面详细解释一下结构体中各字段的含义。

  1. evsel 和 evbase 这两个字段的设置可能会让人有些迷惑,这里你可以把 evsel 和 evbase 看作是类和静态函数的关系,比如添加事件时的调用行为:evsel->add(evbase, ev),实际执行操作的是 evbase;这相当于 class::add(instance, ev),instance 就是 class 的一个对象实例。 evsel 指向了全局变量 static const struct eventop *eventops[] 中的一个; 前面也说过,libevent 将系统提供的 I/O demultiplex 机制统一封装成了 eventop 结构;因此 eventops[] 包含了 select、poll、kequeue 和 epoll 等等其中的若干个全局实例对象。 evbase 实际上是一个 eventop 实例对象; 先来看看 eventop 结构体,它的成员是一系列的函数指针, 在 event-internal.h 文件中:

    struct eventop {
        const char *name;
        void *(*init)(struct event_base *); // 初始化
        int (*add)(void *, struct event *); // 注册事件
        int (*del)(void *, struct event *); // 删除事件
        int (*dispatch)(struct event_base *, void *, struct timeval *); // 事件分发
        void (*dealloc)(struct event_base *, void *); // 注销,释放资源
        /* set if we need to reinitialize the event base */
        int need_reinit;
    };
    

    也就是说,在 libevent 中,每种 I/O demultiplex 机制的实现都必须提供这五个函数接口,来完成自身的初始化、销毁释放;对事件的注册、注销和分发。 比如对于 epoll,libevent 实现了 5 个对应的接口函数,并在初始化时并将 eventop 的 5 个函数指针指向这 5 个函数,那么程序就可以使用 epoll 作为 I/O demultiplex 机制了,这个在后面会再次提到。

  2. activequeues 是一个二级指针,前面讲过 libevent 支持事件优先级,因此你可以把它看作是数组,其中的元素 activequeues[priority] 是一个链表,链表的每个节点指向一个优先级为 priority 的就绪事件 event。

  3. eventqueue,链表,保存了所有的注册事件 event 的指针。
  4. sig 是由来管理信号的结构体,将在后面信号处理时专门讲解;
  5. timeheap 是管理定时事件的小根堆,将在后面定时事件处理时专门讲解;
  6. event_tv 和 tv_cache 是 libevent 用于时间管理的变量,将在后面讲到; 其它各个变量都能因名知意,就不再啰嗦了。

2. 创建和初始化 event_base

创建一个 event_base 对象也既是创建了一个新的 libevent 实例,程序需要通过调用 event_init()(内部调用 event_base_new 函数执行具体操作)函数来创建,该函数同时还对新生成的 libevent 实例进行了初始化。

  • 该函数首先为 event_base 实例申请空间,
  • 然后初始化 timer mini-heap,选择并初始化合适的系统 I/O 的 demultiplexer 机制,初始化各事件链表;

函数还检测了系统的时间设置,为后面的时间管理打下基础。

3. 接口函数

前面提到 Reactor 框架的作用就是提供事件的注册、注销接口;根据系统提供的事件多路分发机制执行事件循环,当有事件进入“就绪”状态时,调用注册事件的回调函数来处理事件。 Libevent 中对应的接口函数主要就是:

int  event_add(struct event *ev, const struct timeval *timeout);
int  event_del(struct event *ev);
int  event_base_loop(struct event_base *base, int loops);
void event_active(struct event *event, int res, short events);
void event_process_active(struct event_base *base); 

本节将按介绍事件注册和删除的代码流程,libevent 的事件循环框架将在下一节再具体描述。

  • 对于 定时事件,这些函数将调用 timer heap 管理接口执行插入和删除操作;
  • 对于 I/O 和 Signal 事件 将调用 eventopadd 和 delete 接口函数执行插入和删除操作(eventop 会对 Signal 事件调用 Signal 处理接口执行操作);

这些组件将在后面的内容描述。

1)注册事件 函数原型:

int event_add(struct event *ev, const struct timeval *tv)

参数:ev:指向要注册的事件; tv:超时时间;

e 函数将 ev 注册到 ev->ev_base 上,事件类型由 ev->ev_events 指明,

  • 如果注册成功,v 将被插入到已注册链表中;
  • 如果 tv 不是 NULL,则会同时注册定时事件,将 ev 添加到 timer 堆上;

如果其中有一步操作失败,那么函数保证没有事件会被注册,可以讲这相当于一个原子操作。这个函数也体现了 libevent 细节之处的巧妙设计,且仔细看程序代码,部分有省略,注释直接附在代码中。

int event_add(struct event *ev, const struct timeval *tv) {
    struct event_base *base = ev->ev_base;
    // 要注册到的event_base
    const struct eventop *evsel = base->evsel;
    void *evbase = base->evbase;
    // base使用的系统I/O策略
    // 新的timer事件,调用timer heap接口在堆上预留一个位置
    // 注:这样能保证该操作的原子性:
    // 向系统I/O机制注册可能会失败,而当在堆上预留成功后,
    // 定时事件的添加将肯定不会失败;
    // 而预留位置的可能结果是堆扩充,但是内部元素并不会改变
    if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
        if (min_heap_reserve(&base->timeheap, 1 + min_heap_size(&base->timeheap)) == -1)
                    return (-1);
        /* ENOMEM == errno */
    }
    // 如果事件ev不在已注册或者激活链表中,则调用evbase注册事件
    if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) && !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
        res = evsel->add(evbase, ev);
        if (res != -1) // 注册成功,插入event到已注册链表中
        event_queue_insert(base, ev, EVLIST_INSERTED);
    }
    // 准备添加定时事件
    if (res != -1 && tv != NULL) {
        struct timeval now;
        // EVLIST_TIMEOUT表明event已经在定时器堆中了,删除旧的
        if (ev->ev_flags & EVLIST_TIMEOUT)
                    event_queue_remove(base, ev, EVLIST_TIMEOUT);
        // 如果事件已经是就绪状态则从激活链表中删除
        if ((ev->ev_flags & EVLIST_ACTIVE) &&
                (ev->ev_res & EV_TIMEOUT)) {
            // 将ev_callback调用次数设置为0
            if (ev->ev_ncalls && ev->ev_pncalls) {
                *ev->ev_pncalls = 0;
            }
            event_queue_remove(base, ev, EVLIST_ACTIVE);
        }
        // 计算时间,并插入到timer小根堆中
        gettime(base, &now);
        evutil_timeradd(&now, tv, &ev->ev_timeout);
        event_queue_insert(base, ev, EVLIST_TIMEOUT);
    }
    return (res);
}
  • event_queue_insert() 负责将事件插入到对应的链表中,下面是程序代码;
  • event_queue_remove() 负责将事件从对应的链表中删除,这里就不再重复贴代码了;
void event_queue_insert(struct event_base *base, struct event *ev, int queue) {
    // ev可能已经在激活列表中了,避免重复插入
    if (ev->ev_flags & queue) {
        if (queue & EVLIST_ACTIVE)
           return;
    }
    // ...
    ev->ev_flags |= queue;
    // 记录queue标记
    switch (queue) {
        case EVLIST_INSERTED: // I/O或Signal事件,加入已注册事件链表
        TAILQ_INSERT_TAIL(&base->eventqueue, ev, ev_next);
        break;
        case EVLIST_ACTIVE: // 就绪事件,加入激活链表
        base->event_count_active++;
        TAILQ_INSERT_TAIL(base->activequeues[ev->ev_pri], ev, ev_active_next);
        break;
        case EVLIST_TIMEOUT: // 定时事件,加入堆
        min_heap_push(&base->timeheap, ev);
        break;
    }
}

2)删除事件: 函数原型为:

int event_del(struct event *ev);

该函数将删除事件 ev

  • 对于 I/O 事件,从 I/O 的 demultiplexer 上将事件注销;
  • 对于 Signal 事件,将从 Signal 事件链表中删除;
  • 对于定时事件,将从堆上删除;

同样删除事件的操作则不一定是原子的,比如删除时间事件之后,有可能从系统 I/O 机制中注销会失败。

int event_del(struct event *ev) {
    struct event_base *base;
    const struct eventop *evsel;
    void *evbase;
    // ev_base为NULL,表明ev没有被注册
    if (ev->ev_base == NULL)
      return (-1);
    // 取得ev注册的event_base和eventop指针
    base = ev->ev_base;
    evsel = base->evsel;
    evbase = base->evbase;
    // 将ev_callback调用次数设置为
    if (ev->ev_ncalls && ev->ev_pncalls) {
        *ev->ev_pncalls = 0;
    }
    // 从对应的链表中删除
    if (ev->ev_flags & EVLIST_TIMEOUT)
      event_queue_remove(base, ev, EVLIST_TIMEOUT);
    if (ev->ev_flags & EVLIST_ACTIVE)
      event_queue_remove(base, ev, EVLIST_ACTIVE);
    if (ev->ev_flags & EVLIST_INSERTED) {
        event_queue_remove(base, ev, EVLIST_INSERTED);
        // EVLIST_INSERTED表明是I/O或者Signal事件,
        // 需要调用I/O demultiplexer注销事件
        return (evsel->del(evbase, ev));
    }
    return (0);
}

4. 小结

分析了 event_base 这一重要结构体,初步看到了 libevent 对系统的 I/O demultiplex 机制的封装 event_op 结构,并结合源代码分析了事件的注册和删除处理,下面将会接着分析事件管理框架中的主事件循环部分。