Libevent源代码分析笔记二,初始化

时间:2022-02-22 16:53:46
本次分析的版本是libevent-2.0.21-stable
event_base是libevent的核心对象,它代表了libevent的事件处理框架。

在libevent的官方网站上,它号称支持/dev/pollkqueue(2)event portsPOSIX select(2)Windows select()poll(2), 跟 epoll(4) 。之所以有这么大的支持力度,因为libevent的事件处理框架并不直接调用系统API,而是为支持的操作系统封装了一系列的回调函数。

从它生成的Makefile可以看出来:

build_triplet = armv7l-unknown-linux-gnueabihf
host_triplet = armv7l-unknown-linux-gnueabihf
am__append_1 = libevent_pthreads.la
am__append_2 = libevent_pthreads.pc
#am__append_3 = libevent_openssl.la
#am__append_4 = libevent_openssl.pc
am__append_5 = select.c
am__append_6 = poll.c
#am__append_7 = devpoll.c
#am__append_8 = kqueue.c
am__append_9 = epoll.c
#am__append_10 = evport.c
am__append_11 = signal.c
#am__append_12 = $(EVENT1_HDRS)
.....
SYS_SRC = $(am__append_5) $(am__append_6) \
	$(am__append_7) $(am__append_8) \
	$(am__append_9) $(am__append_10) \
	$(am__append_11)

由此可见它根据操作系统的支持程度不同,编入select.c, poll.c等。


而在event.c 中又有以下定义

static const struct eventop *eventops[] = {
#ifdef _EVENT_HAVE_EVENT_PORTS
	evportops,
#endif
#ifdef _EVENT_HAVE_WORKING_KQUEUE
	kqops,
#endif
#ifdef _EVENT_HAVE_EPOLL
	epollops,
#endif
#ifdef _EVENT_HAVE_DEVPOLL
	devpollops,
#endif
#ifdef _EVENT_HAVE_POLL
	pollops,
#endif
#ifdef _EVENT_HAVE_SELECT
	selectops,
#endif
#ifdef WIN32
	win32ops,
#endif
	NULL
};
由此可见,libevent支持哪些异步i/o调用是在编译期指定的。

现在开始进入代码。为了使用libevent, 创建一个libevent对象是必须的。我们一般调用的是:

struct event_base *base;
base = event_base_new();

往里跟event_base_new函数,可得:

struct event_base *
event_base_new(void)
{
	struct event_base *base = NULL;
	struct event_config *cfg = event_config_new();
	if (cfg) {
		base = event_base_new_with_config(cfg);
		event_config_free(cfg);
	}
	return base;
}


由此可见,我们可以在分配event_base_new之前完成一些配置信息,从而改变事件处理框架的默认行为。
具体的函数有
event_config_avoid_method(),不使用某些事件处理框架,如select等
event_config_require_features(),//指定使用某些feature,如EV_FEATURE_ET
event_config_set_flag(),//指定flag,如EVENT_BASE_FLAG_NOLOCK
event_config_set_num_cpus_hint()在windows下IOCP时,试图使用的CPU数量

继续往里跟,可得以下代码:

event_base_new_with_config(const struct event_config *cfg)
{
	...
	mm_calloc(1, sizeof(struct event_base)))//分配内存
	detect_monotonic();//检测是否支持monotonic
	gettime(base, base->event_tv);

	//一系列的初始化操作。清空time_heap,event_queue等等,不一一列举。
	min_heap_ctor(base->timeheap);
	TAILQ_INIT(base->eventqueue);
	base->sig.ev_signal_pair[0] = -1;
	base->sig.ev_signal_pair[1] = -1;
	base->th_notify_fd[0] = -1;
	base->th_notify_fd[1] = -1;

	event_deferred_cb_queue_init(base->defer_queue);//
	base->defer_queue.notify_fn = notify_base_cbq_callback;
	base->defer_queue.notify_arg = base;
	if (cfg)
		base->flags = cfg->flags;

	evmap_io_initmap(base->io);
	evmap_signal_initmap(base->sigmap);
	event_changelist_init(base->changelist);

	base->evbase = NULL;

	should_check_environment =
	    !(cfg  (cfg->flags  EVENT_BASE_FLAG_IGNORE_ENV));
	
	//eventops是一个静态数组,存放着系统支持的异步框架(select,poll,epoll...)
	for (i = 0; eventops[i]  !base->evbase; i++) {
		if (cfg != NULL) {
			/* determine if this backend should be avoided */
			if (event_config_is_avoided_method(cfg,
				eventops[i]->name))
				continue;
			if ((eventops[i]->features  cfg->require_features)
			    != cfg->require_features)
				continue;
		}

		/* also obey the environment variables */
		if (should_check_environment 
		    event_is_method_disabled(eventops[i]->name))
			continue;
		//选中一个事件处理框架,在这行代码可以看到虽然系统可能支持多种异步处理框架,但libevent只会选用一种。
		//默认选中的是eventops数组里的最后一条,但是可以通过event_config_avoid_method,event_config_require_features
		//来修改,选中我们中意的事件处理框架
		base->evsel = eventops[i];
		
		//调用该框架下的处理函数,例如epoll框架需要调用epoll_create来创建队列。
		//返回一个框架操作对象,里面存放着相关的句柄信息。如epoll需要存放epoll_create返回的fd等等。
		base->evbase = base->evsel->init(base);
	}
	//经过event_config_avoid_method,event_config_require_features的过滤后,有可能找不到中意的事件处理框架。
	//这时返回NULL
	if (base->evbase == NULL) {
		event_warnx("%s: no event mechanism available",
		    __func__);
		base->evsel = NULL;
		event_base_free(base);
		return NULL;
	}

	if (evutil_getenv("EVENT_SHOW_METHOD"))
		event_msgx("libevent using: %s", base->evsel->name);

	/* allocate a single active event queue */
	if (event_base_priority_init(base, 1) < 0) {
		event_base_free(base);
		return NULL;
	}

	/* prepare for threading */
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
	if (EVTHREAD_LOCKING_ENABLED() &&
	    (!cfg || !(cfg->flags & EVENT_BASE_FLAG_NOLOCK))) {
		int r;
		EVTHREAD_ALLOC_LOCK(base->th_base_lock,
		    EVTHREAD_LOCKTYPE_RECURSIVE);
		base->defer_queue.lock = base->th_base_lock;
		EVTHREAD_ALLOC_COND(base->current_event_cond);
		r = evthread_make_base_notifiable(base);
		if (r<0 event_warnx="" s:="" unable="" to="" make="" base="" notifiable="" __func__="" event_base_free="" base="" return="" null="" endif="" ifdef="" win32="" if="" cfg="" cfg-="">flags & EVENT_BASE_FLAG_STARTUP_IOCP))
		event_base_start_iocp(base, cfg->n_cpus_hint);
#endif
	return (base);
}0>

至此,libevent初始化完毕。看完了初始化,再来理解event_base就会相对容易一些。

它首先会选中一种事件处理框架,将之存放在evsel中。然后需要存放与该事件处理框架相关的对象,存入evbase中。
evbase在eventinternal.h 中定义。我就不贴代码了,注释挺详细的。
另外在这里可以看到libevent用到了tail queue(使用TAILQ_XXX来操作),哈希表(使用HT_XXX来操作 ),最小堆等数据结构。没见过tail queue,之前的算法书也没提过哈希表的实现。所以应该在下篇分析一下。