Libevent源码解读(三)
事件主循环
根据系统提供的事件多路分发机制执行事件循环,堆已注册的就绪事件,调用注册事件的回调函数来处理事件
事件处理主循环
Libevent 的事件主循环主要是通过event_base_loop()函数完成的,其主要操作入下面的流程图,event_base_loop所做的就是持续执行下面的循环
IO和Timer 事件的统一
Libevent将信号事件和定时器事件都集成到了系统的IO的demultiplex机制中,首先将Timer事件的最小超时时间来设置系统IO的timeout时间:当系统IO返回时,在激活所有就绪的Timer事件就可以。这样就能将Timer事件完美融入系统的IO机制中。这是Reactor和Proactor模式的处理的经典方法
堆(小根堆)插入和删除的事件复杂度为LogN,N为堆中元素,二获取最小Key值为O(1)
IO和signal事件的统一
signal是异步事件的经典实例,如果当signal事件发生时,并不立即调用event的callback函数,而是设法通知系统IO机制,让其返回。然后在统一和IO事件以及Timer一起处理。
问题核心,当signal发生是如何通知系统的IO多路复用机制,比如pipe
总结:介绍了libevent是如何处理就绪的IO事件,定时器和信号事件
集成信号处理
主要分析signal集成到事件主循环的框架中
集成策略 — socket pair
可以实现上述在同一个文件描述符中进行读写的功能。该系统调用能创建一对已连接的UNIX族socket。在Linux中,完全可以把这一对socket当成pipe返回的文件描述符一样使用,唯一的区别就是这一对文件描述符中的任何一个都可读和可写,libevent使用的就是socket pair
int socketpair(int d, int type, int protocol, int sv[2]);
socketpair创建逻辑
listener = socket(AF_INET, type, 0);
if (listener < 0)
return -1;
memset(&listen_addr, 0, sizeof(listen_addr));
listen_addr.sin_family = AF_INET;
listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
listen_addr.sin_port = 0; /* kernel chooses port. */
if (bind(listener, (struct sockaddr *) &listen_addr, sizeof (listen_addr))
== -1)
goto tidy_up_and_fail;
if (listen(listener, 1) == -1)
goto tidy_up_and_fail;
connector = socket(AF_INET, type, 0);
if (connector < 0)
goto tidy_up_and_fail;
memset(&connect_addr, 0, sizeof(connect_addr));
/* We want to find out the port number to connect to. */
size = sizeof(connect_addr);
if (getsockname(listener, (struct sockaddr *) &connect_addr, &size) == -1)
goto tidy_up_and_fail;
if (size != sizeof (connect_addr))
goto abort_tidy_up_and_fail;
if (connect(connector, (struct sockaddr *) &connect_addr,
sizeof(connect_addr)) == -1)
goto tidy_up_and_fail;
size = sizeof(listen_addr);
acceptor = accept(listener, (struct sockaddr *) &listen_addr, &size);
if (acceptor < 0)
goto tidy_up_and_fail;
if (size != sizeof(listen_addr))
goto abort_tidy_up_and_fail;
/* Now check we are talking to ourself by matching port and host on the
two sockets. */
if (getsockname(connector, (struct sockaddr *) &connect_addr, &size) == -1)
goto tidy_up_and_fail;
if (size != sizeof (connect_addr)
|| listen_addr.sin_family != connect_addr.sin_family
|| listen_addr.sin_addr.s_addr != connect_addr.sin_addr.s_addr
|| listen_addr.sin_port != connect_addr.sin_port)
goto abort_tidy_up_and_fail;
evutil_closesocket(listener);
fd[0] = connector;
fd[1] = acceptor;
return 0;
集成到事件主循环----通知event_base
socketPair创建好了,可是libevent的主循环还是不知道signal是否发生了:为socket pair的读socket在libevent的event_base实例上注册一个persist的读事件
前面提到饿了Libevent会在事件主循环检查标记,来确定是否有触发的signal,如果有标记就处理。已epoll为例子,在epoll_dispatch()中
res = epoll_wait(epollop->epfd, events, epollop->nevents, timeout);
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
if (res == -1) {
if (errno != EINTR) {
event_warn("epoll_wait");
return (-1);
}
evsignal_process(base);//处理信号事件
return (0);
}else if(base->sig.evsignal_caught)
evsignal_process(base);//处理信号事件
evsignal_info结构体
/* Data structure for the default signal-handling implementation in signal.c
*/
struct evsig_info {
/* Event watching ev_signal_pair[1] */
struct event ev_signal;
//为socketpair的读socket向event_base注册读事件时使用的event结构体
/* Socketpair used to send notifications from the signal handler */
evutil_socket_t ev_signal_pair[2];
/* True iff we've added the ev_signal event yet. */
int ev_signal_added;
/* Count of the number of signals we're currently watching. */
int ev_n_signals_added;
/* Array of previous signal handler objects before Libevent started
* messing with them. Used to restore old signal handlers. */
#ifdef EVENT__HAVE_SIGACTION
struct sigaction **sh_old;
#else
ev_sighandler_t **sh_old;
#endif
/* Size of sh_old. */
int sh_old_max;
};
注册 注销signal事件
注册signal事件是evsignal_add(struct event *ev)函数完成的,libevent对所有信号注册同一个处理函数evsignal_handler(),该函数将下一段介绍,注册过程如下:
static void __cdecl
evsig_handler(int sig)
{
int save_errno = errno;
#ifdef _WIN32
int socket_errno = EVUTIL_SOCKET_ERROR();
#endif
ev_uint8_t msg;
if (evsig_base == NULL) {
event_warnx(
"%s: received signal %d, but have no base configured",
__func__, sig);
return;
}
//记录信号sig的触发次数,并且设置event触发标记
//evsignal_base->sig.evsigcaught[sig]++;
//evsignal_base->sig.evsig_caught = 1;
#ifndef EVENT__HAVE_SIGACTION
signal(sig, evsig_handler); //重新注册信号
#endif
/* Wake up our notification mechanism */
msg = sig;
#ifdef _WIN32
send(evsig_base_fd, (char*)&msg, 1, 0);
#else
{
int r = write(evsig_base_fd, (char*)&msg, 1);
(void)r; /* Suppress 'unused return value' and 'unused var' */
}
#endif
errno = save_errno;
#ifdef _WIN32
EVUTIL_SET_SOCKET_ERROR(socket_errno);
#endif
}
总结:主要介绍了libevent对signal事件的具体处理框架,包括事件的注册,删除和socket pair通知机制,以及是如何将signal事件集成到事件主循环中
集成定时器事件
相较于signal事件来说,Timer与IO的集成较为直观和简单
集成到事件主循环
由于IO多路复用都允许一个最大等待时间(最大超时时间)timeout,即使事件没有发生都能够保证timeout事件内放回。因此根据小根堆的最小超时时间来设置超时时间:系统IO放回后,在激活所有的就绪的Timer时间
具体代码在源文件event.c的event_base_loop()中
if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
//根据Timer事件计算evsel->dispatch的最大等待时间
timeout_next(base, &tv_p);
} else {
/*
* if we have active events, we just poll new events
* without waiting.
*/
evutil_timerclear(&tv);
}
//调用select() or epoll_wait()等待IO事件
res = evsel->dispatch(base, tv_p);
//处理超时事件,将超时事件插入到激活链表中
timeout_process(base);
Timer小根堆
Libevent使用堆来管理Timer事件,其key值就是事件超时时间,源代码位于min_heap.h中
支持IO多路复用技术
Libevent的核心是事件驱动和同步非阻塞。为了达到这一目标。必须使用系统提供的IO多路复用