libevent source code parsing - Event Loop

Libevent recently read the source code, write an article to summarize their knowledge to learn. Libevent should be preferred to use the latest stable version, and read the source code in order to reduce the difficulty, I chose version 1.4, that is, patches-1.4 branch. This article needs to read basic Unix network programming, knowing reactor model, who still have doubts you can see my article typical server model theory and its practice

libevent file structure

Libevent file structure on this article The Libevent Reference Manual: Preliminaries explained more clearly, a brief description here.

event 和 event_base

event_base event and is the core libevent, but also we want to explore the core, mainly around two types of event structures and event_base unfold, event defines the structure of the event, the event loop is event_base framework, these two structures are defined in event.h and event-internal.h file. Defines an event initialization, event registration, event delete API in event.c, the event also includes a loop frame event base related API.

evbuffer 和 bufferevent

evbuffer bufferevent and the process in question libevent the write buffer, the two structures are also defined in the header file event.h, and the relevant API API are defined in the relevant documents and evbuffer.c buffer.c file. bufferevent buffer management is a structure, which contains two in evbuffer pointers, a read buffer, a write buffer. evbuffer is dealing with the underlying IO. Also I must mention is bufferevent for read and write cache buffer zone have set a high and low water, high water to avoid consuming too much of a single cache memory, the low water level is to reduce the callback function calls times and improve efficiency.

IO multiplexing system

libevent is a cross-platform network library that implements multi-channel IO in different platforms in a different way, it may be implemented in many ways even in the same platform, libevent supports select, poll, epoll, kqueue other ways.

useful

util module is that some of the common methods, such as logging function, a function of processing time, etc.

event

libevent further abstracted events, in addition to read and write events, further comprising a timed event, or even be converted into a signal event to process. First look at the structure of the event.

  1. libevent to preserve Chain event registration and activation events, ev_next is a list of all registered events, ev_active_next is activated chain of events, ev_signal_next signal is the list of events. Time event heap to manage with the smallest, with a minimum heap is a very efficient way, only you need to determine the top of the heap every event, time event if the top of the heap are not ready, then behind the times certainly not ready.

  2. Each event will have a circulation event_base, to schedule events, ev_base point to the event loop of the event is located.

  3. ev_events indicates the type of event the event of interest, may be the following situations:

// 时间事件
#define EV_TIMEOUT  0x01
// 可读事件
#define EV_READ     0x02
// 可写事件
#define EV_WRITE    0x04
// 信号
#define EV_SIGNAL   0x08
// 标识是否为永久事件。非永久事件激活一次后,就会从注册队列中删除,如果想继续监听该事件,需要再次加入事件队列。而永久事件则激活后不会从注册事件中删除,除非自己手动删除。
#define EV_PERSIST  0x10    /* Persistant event */
复制代码
  1. If the event is a time event, it is a long timeout ev_timeout this event.

  2. libevent event can use a priority, a high priority event is always the first response, ev_pri is the priority of the event.

  3. ev_callback is a callback function corresponding to the event, when the event is triggered calls the callback function for processing.

struct event {
    /*
    ** libevent 用双向链表来保存注册的所有事件,包括IO事件,信号事件。
    ** ev_next 存储了该事件在事件链表中的位置
    ** 另外,libevent 还用另一个链表来存储激活的事件,通过遍历激活的事件链表来分发任务
    ** ev_active_next 存储了该事件在激活事件链表中的位置
    ** 类似,ev_signal_next 就是该事件在信号事件链表中的位置
    */
	TAILQ_ENTRY (event) ev_next;
	TAILQ_ENTRY (event) ev_active_next;
	TAILQ_ENTRY (event) ev_signal_next;
    /* libevent 用最小堆来管理超时时间,min_heap_idx 保存堆顶的 index */
	unsigned int min_heap_idx;	/* for managing timeouts */

    /* event_base 是整个事件循环的核心,每个 event 都处在一个 event_base 中,ev_base 保存这个结构体的指针 */
	struct event_base *ev_base;
    /* 对于 IO 事件,ev_fd 是绑定的文件描述符,对于 signal 事件,ev_fd 是绑定的信号 */
	int ev_fd;
    /* 要处理的事件类型, */
	short ev_events;
    /* 事件就绪执行时,调用ev_callback的次数,通常为1 */
	short ev_ncalls;
	short *ev_pncalls;	/* Allows deletes in callback */
    /* 事件超时的时间长度 */
	struct timeval ev_timeout;
    /* 优先级 */
	int ev_pri;		/* smaller numbers are higher priority */
    /* 响应事件时调用的callback函数 */
	void (*ev_callback)(int, short, void *arg);
	void *ev_arg;

	int ev_res;		/* result passed to event callback */
    /* 表示事件所处的状态 */
	int ev_flags;
};
复制代码

For handling the event there are three main API: event_set, event_add, event_del

event_set event_set event object is used to initialize a

void event_set(struct event *ev, int fd, short events,
      void (*callback)(int, short, void *), void *arg)
{
    /* Take the current base - caller needs to set the real base later */
    /* current_base 是一个全局变量,ev_base 会默认指向这个变量,
    ** 之后 ev_base 也可以通过 event_base_set 设置指向指定的 event_base 
    ** 特别是对于一个进程中有多个 event_base 的情况下,需要绑定到指定的 event_base 上*/
    ev->ev_base = current_base;

    ev->ev_callback = callback;
    ev->ev_arg = arg;
    ev->ev_fd = fd;
    ev->ev_events = events;
    ev->ev_res = 0;
    ev->ev_flags = EVLIST_INIT;
    ev->ev_ncalls = 0;
    ev->ev_pncalls = NULL;

    min_heap_elem_init(ev);

    /* by default, we put new events into the middle priority */
    /* 设定默认优先级为最大优先级的一半 */
    if(current_base)
        ev->ev_pri = current_base->nactivequeues/2;
}
复制代码

event_add event_add is added as an event registration event queue, if the event listener in reading events, event or write on the signal, it will add it to the first IO multiplexing system, and then added to the list of registered events . If the tv argument is not NULL, it will register the event to the time of the event the minimum heap.

int event_add(struct event *ev, const struct timeval *tv)
{
    // 要注册的evbase
    struct event_base *base = ev->ev_base;
    const struct eventop *evsel = base->evsel;
    void *evbase = base->evbase;
    int res = 0;

    event_debug((
         "event_add: event: %p, %s%s%scall %p",
         ev,
         ev->ev_events & EV_READ ? "EV_READ " : " ",
         ev->ev_events & EV_WRITE ? "EV_WRITE " : " ",
         tv ? "EV_TIMEOUT " : " ",
         ev->ev_callback));

    // 校验没有设其他的标志位
    assert(!(ev->ev_flags & ~EVLIST_ALL));

    /*
     * prepare for timeout insertion further below, if we get a
     * failure on any step, we should not change any state.
     */
    // 分配最小堆插入一个元素的内存,先分配内存是为了保证时间事件
    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_events 监听的事件类型为读写或者信号 而且 该事件没有被注册过,也不在激活队列里 */
    if ((ev->ev_events & (EV_READ|EV_WRITE|EV_SIGNAL)) &&
        !(ev->ev_flags & (EVLIST_INSERTED|EVLIST_ACTIVE))) {
        /* 将事件注册到 IO 多路复用中 */
        res = evsel->add(evbase, ev);
        if (res != -1)
            /* 注册成功后将事件加入到 event_base 的事件链表中 */
            event_queue_insert(base, ev, EVLIST_INSERTED);
    }

    /* 
     * we should change the timout state only if the previous event
     * addition succeeded.
     */
    if (res != -1 && tv != NULL) {
        struct timeval now;

        /* 
         * we already reserved memory above for the case where we
         * are not replacing an exisiting timeout.
         */
        /* 如果事件已经在定时事件中了,则从时间事件链表中删除该事件 */
        if (ev->ev_flags & EVLIST_TIMEOUT)
            event_queue_remove(base, ev, EVLIST_TIMEOUT);

        /* Check if it is active due to a timeout.  Rescheduling
         * this timeout before the callback can be executed
         * removes it from the active list. */
        /* 如果事件已经在激活队列中,则从激活队列中删除该事件 */
        if ((ev->ev_flags & EVLIST_ACTIVE) &&
            (ev->ev_res & EV_TIMEOUT)) {
            /* See if we are just active executing this
             * event in a loop
             */
            if (ev->ev_ncalls && ev->ev_pncalls) {
                /* Abort loop */
                *ev->ev_pncalls = 0;
            }
            
            event_queue_remove(base, ev, EVLIST_ACTIVE);
        }
        // 获取当前时间
        gettime(base, &now);
        // 计算超时时间
        evutil_timeradd(&now, tv, &ev->ev_timeout);

        event_debug((
             "event_add: timeout in %ld seconds, call %p",
             tv->tv_sec, ev->ev_callback));
        // 插入到定时时间事件队列中
        event_queue_insert(base, ev, EVLIST_TIMEOUT);
    }

    return (res);
}
复制代码

Event Loop

Introduction to the event to introduce the core event_base event scheduling, event_base definition header file in the event-internal.h.

First, there is a member in event_base evsel, the function pointer member holds the IO multiplexing resources, eventop following structure:

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;
};
复制代码

For each IO multiplexing have achieved init, add, del, dispatch several methods, init is initialized, add is to add an event, del is to delete the event, diapatch is activated to wait for the event, and were treated activated event. There is also a member of the event_base evbase, this member holds the IO multiplexing resources. For example, the first parameter add function is void *, void * this is to pass the evbase. In fact this approach is to achieve a multi-state through a function pointer C, if it is object-oriented languages ​​do not have to get so complicated. However, this method is implemented in C polymorphism that we can learn.

event_base there is a member of the activequeues need to explain that this is a pointer to a pointer. Said before activating the queue is prioritized, the same priority activation event in a linked list, then the number of different priority queues head node activation will form a queue. Thus, there is a pointer to a pointer.

struct event_base {
    /* eventop 对象指针,决定了使用哪种IO多路复用资源 
    ** 但是 eventop 实际上只保存了函数指针,最后资源的句柄是保存在 evbase 中。
    ** 比如要使用 epoll,那么就应该有一个 epoll 的文件描述符,eventop 中只保存了epoll相关的add,del等函数
    ** epoll 的文件描述符是保存在 evbase 中的,因此调用的形式就是 evsel->add(evbase, ev);
    */
    const struct eventop *evsel;
    void *evbase;
    /* event base 上所有事件的数量包括注册事件和激活事件
    ** 在 event_queue_insert 函数中加 1 */
    int event_count;        /* counts number of total events */
    /* event base 上被激活的事件的数量 */
    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 */
    /* libevent 支持事件的优先级,对于激活的事件,不同优先级的事件存储在不同的链表中 
    ** 然后再用一个链表把这些链表串起来
    */
    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;
};
复制代码

About event_base key stakeholders are the following functions: event_base_new, event_base_free, event_base_loop

event_base_new and event_base_free respectively, is allocated event_base resources and release event_base resources, it is better understood. First created by event_base_new event_base, and then create different events and register event_base in the last cycle by event_base_loop start event. If you want to exit the event loop, you can call event_base_loopbreak or event_loopexit_cb.

The core event loop is the dispatch function call IO multiplexing should be noted that prior to calling dispatch function will first calculate the most recent time event from now, how long, then this time difference as the dispatch blocking time, this time the event will timely response may be, will not be blocked on IO multiplexing too long to wait for too much time. This is a common practice, redis event loop is implemented as follows.

int
event_base_loop(struct event_base *base, int flags)
{
    const struct eventop *evsel = base->evsel;
    void *evbase = base->evbase;
    struct timeval tv;
    struct timeval *tv_p;
    int res, done;

    /* clear time cache */
    base->tv_cache.tv_sec = 0;

    if (base->sig.ev_signal_added)
        evsignal_base = base;
    done = 0;
    while (!done) {
        /* Terminate the loop if we have been asked to */
        /* 调用 event_loopexit_cb 跳出循环,为什么搞了两个函数? */
        if (base->event_gotterm) {
            base->event_gotterm = 0;
            break;
        }

        /* 调用 event_base_loopbreak 函数跳出循环 */
        if (base->event_break) {
            base->event_break = 0;
            break;
        }

        /* You cannot use this interface for multi-threaded apps */
        while (event_gotsig) {
            event_gotsig = 0;
            if (event_sigcb) {
                res = (*event_sigcb)();
                if (res == -1) {
                    errno = EINTR;
                    return (-1);
                }
            }
        }

        /* 矫正时间 */
        timeout_correct(base, &tv);

        tv_p = &tv;
        /* 如果没有激活事件,且等待方式不是非阻塞,计算当前时间距离最小堆堆顶时间事件的时间差,作为阻塞的时间 */
        if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) {
            timeout_next(base, &tv_p);
        } else {
            /* 
             * if we have active events, we just poll new events
             * without waiting.
             */
            /* 如果有激活事件,将阻塞时间设置为 0 */
            evutil_timerclear(&tv);
        }
        
        /* If we have no events, we just exit */
        /* 如果已经没有事件了,则退出循环 */
        if (!event_haveevents(base)) {
            event_debug(("%s: no events registered.", __func__));
            return (1);
        }

        /* update last old time */
        /* 更新事件循环的时间 */
        gettime(base, &base->event_tv);

        /* clear time cache */
        /* 清空时间缓存 */
        base->tv_cache.tv_sec = 0;
        /* 调用 IO 多路复用函数等待事件就绪,就绪的信号事件和IO事件会被插入到激活链表中 */
        res = evsel->dispatch(base, evbase, tv_p);

        if (res == -1)
            return (-1);
        /* 写时间缓存 */
        gettime(base, &base->tv_cache);
        /* 检查heap中的时间事件,将就绪的事件从heap中删除并插入到激活队列中 */
        timeout_process(base);
        /* 如果有激活的信号事件和IO时间,则处理 */
        if (base->event_count_active) {
            event_process_active(base);
            if (!base->event_count_active && (flags & EVLOOP_ONCE))
                done = 1;
        } else if (flags & EVLOOP_NONBLOCK)
            /* 如果采用非阻塞的方式 */
            done = 1;
    }

    /* clear time cache */
    base->tv_cache.tv_sec = 0;

    event_debug(("%s: asked to terminate loop.", __func__));
    return (0);
}
复制代码

to sum up

The core libevent event loop is described above them, and for the implementation details of the place gives the corresponding Chinese comments on my github. In addition, there are very intuitive diagram describing a libevent event loop, and I drew it back again, slightly modified, posted and to share on the network.

libevent event loop

Guess you like

Origin juejin.im/post/5d8cc14af265da5b9f7c52d5