[libevent]集成信号处理和集成定时器事件

本文详细介绍了libevent如何将信号(Signal)事件集成到事件主循环,通过socket pair实现通知机制,并注册、注销signal事件。同时,文章也探讨了libevent集成定时器事件的方法,利用小根堆管理Timer事件,确保事件的高效处理。
摘要由CSDN通过智能技术生成

上文中提到了libeventI/O事件和Signal以及Timer事件的集成,本文将分析如何将Signal集成到事件主循环的框架中 

集成策略——使用socket pair

上一文已经做了足够多的介绍了,基本方法就是采用“消息机制”libevent中这是通过socket pair完成的,下面就来详细分析一下。socket pair就是一个socket对,包含两个socket,一个读socket,一个写socket。工作方式如下图所示:


创建一个socket pair并不是复杂的操作,可以参见下面的流程图,清晰起见,其中忽略了一些错误处理和检查。libevent提供了辅助函数evutil_socketpair()来创建一个socket pair,可以结合上面的创建流程来分析该函数。

 

集成到事件主循环——通知event_base

socket pair创建好了,可是libevent的事件主循环还是不知道Signal是否发生了啊,看来我们还差了最后一步,那就是:socket pair的读socketlibeventevent_base实例上注册一个persist的读事件。这样当向写socket写入数据时,读socket就会得到通知,触发读事件,从而event_base就能相应的得到通知了。

前面提到过,Libevent会在事件主循环中检查标记,来确定是否有触发的signal,如果标记被设置就处理这些signal,这段代码在各个具体的I/O机制中,以Epoll为例,在epoll_dispatch()函数中,代码片段如下:

res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);
if (res == -1) {
	if (errno != EINTR) {
		event_warn("epoll_wait");
		return (-1);
	}
	evsignal_process(base);// 处理signal事件
	return (0);
} else if (base->sig.evsignal_caught) {
	evsignal_process(base);// 处理signal事件
}

完整的处理框架如下所示:

           

1libevent中,初始化阶段并不注册读socket的读事件,而是在注册信号阶段才会测试并注册;

2libevent中,检查I/O事件是在各系统I/O机制的dispatch()函数中完成的,该dispatch()函数在event_base_loop()函数中被调用; 

 注册、注销signal事件

注册signal事件是通过evsignal_add(struct event *ev)函数完成的,libevent对所有的信号注册同一个处理函数evsignal_handler(),注册过程如下:

  • 取得ev要注册到的信号signo
  • 如果信号signo未被注册,那么就为signo注册信号处理函数evsignal_handler()
  • 如果事件ev_signal还没有注册,就注册ev_signal事件;
  • 将事件ev添加到signoevent链表中;

signo上注销一个已注册的signal事件就更简单了,直接从其已注册事件的链表中移除即可。如果事件链表已空,那么就恢复旧的处理函数;

下面的讲解都以signal()函数为例,sigaction()函数的处理和signal()相似。

处理函数evsignal_handler()函数做的事情很简单,就是记录信号的发生次数,并通知event_base有信号触发,需要处理:

static void evsignal_handler(int sig)   
{   
	int save_errno = errno; // 不覆盖原来的错误代码   
	if (evsignal_base == NULL) {   
		event_warn("%s: received signal %d, but have no base configured", __func__, sig);   
		return;   
	}   
	// 记录信号sig的触发次数,并设置event触发标记   
	evsignal_base->sig.evsigcaught[sig]++;   
	evsignal_base->sig.evsignal_caught = 1;   
#ifndef HAVE_SIGACTION   
	signal(sig, evsignal_handler); // 重新注册信号   
#endif   
	// 向写socket写一个字节数据,触发event_base的I/O事件,从而通知其有信号触发,需要处理   
	send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0);   
	errno = save_errno; // 错误代码   
}

小节

本文介绍了libeventsignal事件的具体处理框架,包括事件注册、删除和socket pair通知机制,以及是如何将Signal事件集成到事件主循环之中的。

集成定时器事件

Signal相比,Timer事件的集成会直观和简单很多。Libevent对堆的调整操作做了一些优化。

集成到事件主循环

因为系统的I/O机制像select()epoll_wait()都允许程序制定一个最大等待时间(也称为最大超时时间)timeout即使没有I/O事件发生,它们也保证能在timeout时间内返回。

那么根据所有Timer事件的最小超时时间来设置系统I/Otimeout时间当系统I/O返回时,再激活所有就绪的Timer事件就可以了,这样就能将Timer事件完美的融合到系统的I/O机制中了。具体的代码在源文件event.cevent_base_loop()中,现在就对比代码来看看这一处理方法:

if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK))
{	// 根据Timer事件计算evsel->dispatch的最大等待时间
	timeout_next(base, &tv_p);
} 
else 
{	// 如果还有活动事件,就不要等待,让evsel->dispatch立即返回
	evutil_timerclear(&tv);
}
// ...
// 调用select() or epoll_wait() 等待就绪I/O事件
res = evsel->dispatch(base, evbase, tv_p);
// ...
// 处理超时事件,将超时事件插入到激活链表中
timeout_process(base);

timeout_next()函数根据堆中具有最小超时值的事件和当前时间来计算等待时间,下面看看代码

static int timeout_next(struct event_base *base, struct timeval **tv_p)
{
	struct timeval now;
	struct event *ev;
	struct timeval *tv = *tv_p;
	// 堆的首元素具有最小的超时值
	if ((ev = min_heap_top(&base->timeheap)) == NULL) {
		// 如果没有定时事件,将等待时间设置为NULL,表示一直阻塞直到有I/O事件发生
		*tv_p = NULL;
		return (0);
	}
	// 取得当前时间
	gettime(base, &now);
	// 如果超时时间<=当前值,不能等待,需要立即返回
	if (evutil_timercmp(&ev->ev_timeout, &now, <=)) {
		evutil_timerclear(tv);
		return (0);
	}
	// 计算等待的时间=当前时间-最小的超时时间
	evutil_timersub(&ev->ev_timeout, &now, tv);
	return (0);
}

Timer小根堆

libevent使用堆来管理Timer事件,其key值就是事件的超时时间,源代码位于文件min_heap.h中。

所有的数据结构书中都有关于堆的详细介绍,向堆中插入、删除元素时间复杂度都是O(lgN)N为堆中元素的个数,而获取最小key值(小根堆)的复杂度为O(1)堆是一个完全二叉树,基本存储方式是一个数组。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值