libevent源码分析(1)-事件循环

769 阅读4分钟

一直对鼎鼎大名的libevent很感兴趣,所以需要深入研究下源码。

libevent支持的功能

  • 异步dns
  • 简单的http server和http client
  • 简单的rpc框架
  • bufferevent IO
  • signal,定时器

前言

libevent的最新版本已经很复杂了,深入分析的难度很大,所以需要选择一个初期的版本(核心功能已经存在)去分析。

git clone https://github.com/libevent/libevent.git
git tag |head -5
release-1.1b
release-1.2
release-1.2a
release-1.3a

在此,我选择最老的一个 tag(release-1.1b) 进行分析,已经支持了

  • bufferevent IO
  • signal,定时器

初览

先确定需要分析的源码文件

# find ./ -name "*.c" |egrep -v test
./buffer.c
./devpoll.c
./epoll.c
./epoll_sub.c
./evbuffer.c
./event.c
./kqueue.c
./log.c
./poll.c
./rtsig.c
./select.c
./signal.c
./WIN32-Code/misc.c
./WIN32-Code/win32.c

除过测试代码有14个源码分析,如果只分析linux平台,可以在继续精简

./event.c
./signal.c
./evbuffer.c
./buffer.c
./epoll.c

只需要分析这5个文件就可以了,一共2161行代码,是不是压力就小了很多。

  811 event.c
  204 signal.c
  378 evbuffer.c
  423 buffer.c
  345 epoll.c
 2161 total

event.c : libevent的主要逻辑
evbuffer.cbuffer.c : proactor的实现,关于reactor和proactor,我觉得最简单的解释在这里。在此简单引用下:

reactor:能收了你跟俺说一声。
proactor: 你给我收十个字节,收好了跟俺说一声。

signal : 信号处理,本文暂不分析信号方面的代码
epoll.c : linux平台事件循环机制的后端实现

event.c文件分析

全局变量

struct event_list signalqueue;
struct event_base *current_base = NULL;
const struct eventop *eventops[] = {...}

event_base结构体

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 */

	/* active event management */
	struct event_list **activequeues;
	int nactivequeues;

	struct event_list eventqueue;
	struct timeval event_tv;

	RB_HEAD(event_tree, event) timetree;
};

evsel : 指向事件循环机制的后端实现
event_count : 当前的事件个数
event_count_active : 当前活跃的事件个数
activequeues : 二维数组,数组的每个元素是一个双向链表,双向链表中的每一个元素是struct event。使用二维数组是用来支持事件的优先级。数组的下标越小则优先级越高。形如: nactivequeues : 二维数组的大小
event_tv : 最后一次更新的时间戳
eventqueue : 保存文件描述符(fd)的读写事件
timetree : 保存超时事件,早期版本使用了红黑树,后期版本已经修改为最小堆了

事件主循环分析

int
event_base_loop(struct event_base *base, int flags)
{
    done = 0;
	while (!done) {        
		gettimeofday(&tv, NULL);
        
		if (timercmp(&tv, &base->event_tv, <)) {
            // 当前时间小于上一次记录的时间,则说明用户向前修改时间了
			struct timeval off;
            // 计算出用户向前修改的毫秒数
			timersub(&base->event_tv, &tv, &off);
            // 对所有的超时事件减去相应的毫秒数
			timeout_correct(base, &off);
		}
		base->event_tv = tv;

		if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK))
            // 当前没有活跃的事件,则计算出与最近的超时事件的间隔时间
			timeout_next(base, &tv);
		else
            // 当前存在活跃时间,则tv设置为0,dispatch(epoll_wait)会马上返回
			timerclear(&tv);
        
		res = evsel->dispatch(base, evbase, &tv);

		if (res == -1)
			return (-1);
        
        // 处理超时的时间
		timeout_process(base);

		if (base->event_count_active) {
            // 处理活跃事件
			event_process_active(base);
	}
	return (0);
}

精简后的主逻辑还是比较清晰的,此处需要注意两个点:

  1. libevent对于用户修改时间有可能会导致定时器失效的处理
  2. 当事件触发时都是先将事件保存在activequeues中,然后每次仅仅处理最高优先级的一组事件,之后再次进入主循环,这样的做的好处是考虑了保证高优先级的事件优先被处理。

超时事件的处理

超时事件的处理在event.c文件中的timeout_process函数,每次dispatch返回时都会去检查已经超时的事件,然后放入到**activequeues(活跃队列)**中

void
timeout_process(struct event_base *base)
{
	struct timeval now;
    struct event *ev, *next;
    
	gettimeofday(&now, NULL);
    
	for (ev = RB_MIN(event_tree, &base->timetree); ev; ev = next) {
		if (timercmp(&ev->ev_timeout, &now, >))
			break;
        // 当前now已经大于事件的超时事件
		next = RB_NEXT(event_tree, &base->timetree, ev);

        // 此处算是早期版本功能不足点,没有考虑到持续性(EV_PERSIST)的超时事件
		event_queue_remove(base, ev, EVLIST_TIMEOUT);        
		event_del(ev);
        
        // 放入到活跃事件链表中
        // 1表示调用事件回调的次数
		event_active(ev, EV_TIMEOUT, 1);
	}
}

读写事件的处理

读写事件的处理是在事件循环的dispatch函数中处理,在linux平台具体的函数是epoll_dispatch,先进行epoll_wait,当fd有事件发生时,将相应的事件放入 activequeues(活跃队列)

int
epoll_dispatch(struct event_base *base, void *arg, struct timeval *tv)
{
	struct epollop *epollop = arg;
	struct epoll_event *events = epollop->events;
	struct evepoll *evep;
	int i, res, timeout;
    
    // timeval向上取整转成毫秒
	timeout = tv->tv_sec * 1000 + (tv->tv_usec + 999) / 1000;
	res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);

	if (res == -1) {
		if (errno != EINTR) {
			event_warn("epoll_wait");
			return (-1);
		}
		return (0);
	}
    
    // 有res个fd有事件发生
	for (i = 0; i < res; i++) {
		int which = 0;
		int what = events[i].events;
		struct event *evread = NULL, *evwrite = NULL;

		evep = (struct evepoll *)events[i].data.ptr;
   
        if (what & EPOLLHUP)  // fd的读写被关闭
            what |= EPOLLIN | EPOLLOUT;
        else if (what & EPOLLERR) // fd出错
            what |= EPOLLIN | EPOLLOUT;

		if (what & EPOLLIN) {   // 可读事件发生
			evread = evep->evread;
			which |= EV_READ;
		}

		if (what & EPOLLOUT) {  // 可写事件发生
			evwrite = evep->evwrite;
			which |= EV_WRITE;
		}

		if (!which)
			continue;

		if (evread != NULL && !(evread->ev_events & EV_PERSIST))
        	// 没有设置持续性事件(EV_PERSIST),则删除读事件
			event_del(evread);
		if (evwrite != NULL && evwrite != evread && !(evwrite->ev_events & EV_PERSIST))
        	// 没有设置持续性事件(EV_PERSIST),则删除写事件
			event_del(evwrite);
        
		if (evread != NULL)
			event_active(evread, EV_READ, 1);   // 将读事件放入活跃队列
		if (evwrite != NULL)
			event_active(evwrite, EV_WRITE, 1); // 将写事件放入活跃队列
	}
	return (0);
}

活跃事件的处理

static void
event_process_active(struct event_base *base)
{
	struct event *ev;
	struct event_list *activeq = NULL;
	int i;
	short ncalls;
    
	for (i = 0; i < base->nactivequeues; ++i) {
		if (TAILQ_FIRST(base->activequeues[i]) != NULL) {
            // 找到优先级最高的一组事件
            // 每次仅仅处理一组事件,而不是全部的活跃事件
			activeq = base->activequeues[i];
			break;
		}
	}

    // 对优先级最高的一组事件进行处理
	for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {
        // 从活跃事件的队列中删除活跃事件
		event_queue_remove(base, ev, EVLIST_ACTIVE);
        
		ncalls = ev->ev_ncalls;
		ev->ev_pncalls = &ncalls;
		while (ncalls) {  // ncalls表示调用ev_callback的次数
			ncalls--;
			ev->ev_ncalls = ncalls;
			(*ev->ev_callback)((int)ev->ev_fd, ev->ev_res, ev->ev_arg);
		}
	}
}