libevent源码学习(14):IO复用模型之epoll的封装

目录

Libevent提供的epoll后端结构体

初始化epoll_init

何时调用epoll_init

事件添加epoll_nochangelist_add

何时调用epoll_nochangelist_add

事件删除epoll_nochangelist_del

何时调用epoll_nochangelist_del

事件监听epoll_dispatch

为什么evmap_io_active的第三个参数需要或上一个EV_ET?

何时调用epoll_dispatch

epoll销毁epoll_dealloc

何时调用epoll_dealloc

 

以下源码均基于libevent-2.0.21-stable。

       在event_base配置中提到过,libevent中封装了多种IO复用模型,当创建一个event_base的时候,libevent就会自动选定一种支持的IO复用模型作为该event_base的后端(back-end),后续真正实现事件添加、监听、删除等操作也是通过所选后端来实现的。因此,本文就主要以epoll为例,分析libevent是如何封装epoll这一IO复用模型的。

Libevent提供的epoll后端结构体

      在libevent中,提供了多种IO复用模型,可以从全局数组eventops中看到,如下所示:

static const struct eventop *eventops[] = { //包含了各种可使用的backups的函数结构体
#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
};

       在linux下,如果创建event_base的时候没有什么特殊的cfg配置要求,那么libevent就会自动分配epoll作为event_base的后端,其对应于这里的epollops,这也是一个结构体,其中包含了相关的函数,如下所示:

const struct eventop epollops = {
	"epoll",       //后端名称
	epoll_init,    //初始化函数
	epoll_nochangelist_add,    //事件添加监听函数
	epoll_nochangelist_del,    //事件删除监听函数
	epoll_dispatch,    //事件监听函数
	epoll_dealloc,     //事件销毁函数
	1, /* need reinit */ 
	EV_FEATURE_ET|EV_FEATURE_O1,    //后端的特征
	0
};

       对于epoll的使用,主要分为3步:1.通过epoll_create创建一个epoll实例;2.通过epoll_ctl向前面创建的epoll实例中添加、修改或删除需要监听的事件;3.通过epoll_wait来返回epoll的就绪事件链表。libevent中的epoll后端主要通过5个函数,来实现对epoll的封装,下面就来分析一下这5个函数:

初始化epoll_init

       epoll_init函数定义如下:

static void *
epoll_init(struct event_base *base)
{
	int epfd;
	struct epollop *epollop;

	/* Initialize the kernel queue.  (The size field is ignored since
	 * 2.6.8.) */
	if ((epfd = epoll_create(32000)) == -1) {  //创建一个可以监听32000个文件描述符的epoll,其中包含1个epoll_create返回的文件描述符
		if (errno != ENOSYS)
			event_warn("epoll_create");
		return (NULL);
	}

	evutil_make_socket_closeonexec(epfd);  //在调用exec时关闭epfd

	if (!(epollop = mm_calloc(1, sizeof(struct epollop)))) {
		close(epfd);
		return (NULL);
	}

	epollop->epfd = epfd;  //epoll base的文件描述符

	/* Initialize fields */
	epollop->events = mm_calloc(INITIAL_NEVENT, sizeof(struct epoll_event));//这里实际上开辟的是epoll_event的数组,数组元素为epoll_event类型,元素个数初始化为32
	if (epollop->events == NULL) {
		mm_free(epollop);
		close(epfd);
		return (NULL);
	}
	epollop->nevents = INITIAL_NEVENT;  //events数组大小

	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;

	evsig_init(base);

	return (epollop);
}

       在epoll_init函数中,创建了一个可监听30000个文件描述符的epoll对象,并且把epoll对象的文件描述符保存在了epfd中。然后创建了一个epollop结构体,该结构体定义如下:

struct epollop {
	struct epoll_event *events; //epoll_event数组,epoll_wait监听激活的epoll_event都会放到这里
	int nevents; //events数组长度,通过限定events数组的长度来限制epoll_wait最多可以接受多少激活的事件
	int epfd; //epoll的文件描述符
};

       也就是说,每一个epoll对象都会对应这样一个epollop结构体,其中含有一个events成员,通过epoll_init对于该成员的内存分配可以知道,events实际上是一个epoll_event数组,由此大概也能猜到,这个events应当就是用来存储最终epoll激活的那些epoll_event。而nevents则是这个数组的长度。

        另一个还需注意的地方是,epoll_init函数的返回值就是这个epollop,从上面的分析也可以知道,epollop结构体记录了epoll的文件描述符,以及未来用来存放就绪epoll_event的数组及其大小,那么epoll_init是在什么时候被调用的呢?

何时调用epoll_init

        在创建event_base的时候,event_base_new_with_config内部会调用epoll_init函数,如下所示:

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); // 用选定的backup的初始化函数来初始化base中的evbase
	}
	......
}

      在event_base_new_with_config函数中,当选定了一个符合条件的后端IO模型后,就会直接将这个后端的函数结构体保存到event_base的evsel中,并且调用所选后端的init函数(比如说这里的epoll_init函数),返回一个epollop结构体保存到event_base的evbase成员中。这样,当我们创建一个event_base之后,实际上就是创建了一个epoll对象,这个epoll对象的必要信息和相关函数就都放在了event_base的evsel和evbase成员中。

事件添加epoll_nochangelist_add

       参考epoll使用的流程,大概也能想到,这个函数的作用会与epoll_ctl函数相关了。该函数定义如下:

static int
epoll_nochangelist_add(struct event_base *base, 
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值