event_base是libevent里另外一个核心的结构,它是存放各种事件集的容器,同时也是libevent事件循环(event loop)的主要结构。和event一样,它也是不透明结构。
每个libevent程序都必须创建至少一个event_base结构。如果需要多个线程监听I/O的话,需要为每个线程分配一个event_base(推荐的做法是one loop per thread )。
本文可能不会详尽event_base每个字段含义,libevent盘根错节,先抽取出一条主线,逐渐深入每一处细节。
event_base结构 尽管event_base结构libevent的源码里其实已经为每个字段都作了注释,这里我还是再解释一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 struct event_base { const struct eventop *evsel ; void *evbase; struct event_changelist changelist ; const struct eventop *evsigsel ; struct evsig_info sig ; int virtual_event_count; int virtual_event_count_max; int event_count; int event_count_max; int event_count_active; int event_count_active_max; int event_gotterm; int event_break; int event_continue; int event_running_priority; int running_loop; int n_deferreds_queued; struct evcallback_list *activequeues ; int nactivequeues; struct evcallback_list active_later_queue ; struct common_timeout_list **common_timeout_queues ; int n_common_timeouts; int n_common_timeouts_allocated; struct event_io_map io ; struct event_signal_map sigmap ; struct min_heap timeheap ; struct timeval tv_cache ; struct evutil_monotonic_timer monotonic_timer ; struct timeval tv_clock_diff ; time_t last_updated_clock_diff; #ifndef EVENT__DISABLE_THREAD_SUPPORT unsigned long th_owner_id; void *th_base_lock; void *current_event_cond; int current_event_waiters; #endif struct event_callback *current_event ; #ifdef _WIN32 struct event_iocp_port *iocp ; #endif enum event_base_config_flag flags ; struct timeval max_dispatch_time ; int max_dispatch_callbacks; int limit_callbacks_after_prio; int is_notify_pending; evutil_socket_t th_notify_fd[2 ]; struct event th_notify ; int (*th_notify_fn)(struct event_base *base); struct evutil_weakrand_state weakrand_seed ; LIST_HEAD(once_event_list, event_once) once_events; };
event_base初始化流程 和event
初始化过程类似,通过event_base_new()
或者event_base_new_with_config()
可以初始化一个event_base。相对地,通过event_base_free()
函数释放event_base。
从命名就可以看出来event_base_new()和event_base_new_with_config()的区别是后者允许你初始化的时候给event_base配置参数。
为了节约篇幅,初始化的过程就不贴出来了,主要干了这5件事情:
分配event_base的空间
选择后端的多路复用方法
初始化管理事件集以及事件回调函数的各种容器
根据config标志,设置event_base
初始化多线程相关的数据结构
分配内存和设置event_base没什么好说的,多线程这块后边的章节再单独细说。下面重点分析2-4的流程。
选用后端多路复用方法 在通用编程技法 一节,提到了event_base选择后端多路复用方法的过程。通常linux都提供了epoll,poll,select多路复用接口,因而全局变量eventops
(event.c文件里)数组的取值是这样的:
1 2 3 4 5 6 7 static const struct eventop *eventops [] = { &epollops, &pollops, &selectops, NULL };
根据event_base_new_with_config的逻辑,数组的下标越小,优先级越高:
1 2 3 4 5 6 7 8 9 10 11 struct event_base* event_base_new_with_config (const struct event_config *cfg) { ... for (i = 0 ; eventops[i] && !base->evbase; i++) { ... base->evsel = eventops[i]; base->evbase = base->evsel->init(base); } ... }
以Linux平台为例,最终base->evsel初始化的值是epollops,也就是说默认情况下使用epoll作为后端:
1 2 3 4 5 6 7 8 9 10 11 const struct eventop epollops = { "epoll" , epoll_init, epoll_nochangelist_add, epoll_nochangelist_del, epoll_dispatch, epoll_dealloc, 1 , EV_FEATURE_ET|EV_FEATURE_O1|EV_FEATURE_EARLY_CLOSE, 0 };
初始化后端的多路复用方法 确定了后端方法后,紧接着会调用base->evsel->init(epoll_init)对其初始化,并将其所需要的信息交给event_base保存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 static void * epoll_init (struct event_base *base) { int epfd = -1 ; struct epollop *epollop ; #ifdef EVENT__HAVE_EPOLL_CREATE1 epfd = epoll_create1(EPOLL_CLOEXEC); #endif if (epfd == -1 ) { if ((epfd = epoll_create(32000 )) == -1 ) { if (errno != ENOSYS) event_warn("epoll_create" ); return (NULL ); } evutil_make_socket_closeonexec(epfd); } ... epollop->epfd = epfd; if ((base->flags & EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST) != 0 || ((base->flags & EVENT_BASE_FLAG_IGNORE_ENV) == 0 && evutil_getenv_("EVENT_EPOLL_USE_CHANGELIST" ) != NULL )) { base->evsel = &epollops_changelist; } ... return (epollop); }
epoll_init主要干了两件事儿:
封装epoll_create(1)函数,创建了epoll实例
如果配置了changlist,就将后端的方法替换为epollops_changelist
与epollops相比epollops_changelist,只有2个函数指针不同,分别是event_changelist_add_和event_changelist_del_:
1 2 3 4 5 6 7 8 9 10 11 static const struct eventop epollops_changelist = { "epoll (with changelist)" , epoll_init, event_changelist_add_, event_changelist_del_, epoll_dispatch, epoll_dealloc, 1 , EV_FEATURE_ET|EV_FEATURE_O1| EARLY_CLOSE_IF_HAVE_RDHUP, EVENT_CHANGELIST_FDINFO_SIZE };
changelist是libevent2重要的一项feature,主要用于提高性能减少系统调用,后边会单独用一章详细介绍。
初始化事件集容器 event_base初始化的另外一项工作是对保存事件以及事件回调函数函数的容器的初始化。我这里将这些容器用图表示了出来:
下面的表描述了这些容器的类型以及作用,
字段
类型
作用
once_events
list
保存一次性事件
timeheap
minheap
保存超时时间
sigmap
hashmap
保存信号事件(数组实现)
io
hashmap
保存I/O事件 (Linux的fd是连续的数字,和sigmap用了数组实现,widnows的文件句柄不是连续的数字,没用数组实现)
activequeues
array
数组的每个元素是回到函数的链表(struct evcallback_list
),数组的作用是为了体现优先级,数组的下标越小,回调函数链表的优先级越高,也就越早会被调用
active_later_queue
tail queue
保存被延迟回调(deferred callbacks)的事件回调函数
总结 不知不觉篇幅已经这么长了,才整理了event_base初始化过程,到下一章将详细介绍它是如何发挥作用的。