概念
libev是一个轻量级的事件通知库,具备支持多种事件通知能力。
数据结构
在熟悉代码之前先了解其相关数据结构往往更加方便后续代码的阅读。
在libev中关键的数据结构是,loop结构体,该结构体定义的字段较多,但是主要核心的可以分为两大类。
ev_loop结构体(loop为ev_loop结构的全局变量)的字段定义在ev_vars.h头文件中,然后在ev.c中通过include的方式导入。
watch集合
loop中有支持很多类型的事件,如下:
ev_io // IO可读可写
ev_stat // 文件属性变化
ev_signal // 信号处理
ev_timer // 相对定时器
ev_periodic // 绝对定时器
ev_child // 子进程状态变化
ev_fork // fork事件
ev_cleanup // event loop退出触发事件
ev_idle // event loop空闲触发事件
ev_embed // 嵌入另一个后台循环
ev_prepare // event loop之前事件
ev_check // event loop之后事件
ev_async // 线程间异步事件
这些事件的监控管理都对应一种类型的watcher数组,比如
(loop)->anfds :维护所有fd事件
(loop)->timers :维护所有的定时器
(loop)->periodics:周期性事件
(loop)->prepares:该事件是loop启动之前就会执行的事件等,每类事件都能在loop结构中找到对应的数组来维护对应的watchers。
watch结构
对于不同类型的事件的watcher,采用继承的方式来实现各个类型的watcher,libev是使用c的宏定义来实现继承。
active表示这个监视器是否处于激活状态。
priority表示监视器的优先级,其值可以从-2~2,共5个级别。其中2是最高级别,-2是最低级别。级别高的监视器会优先于级别低的监视器执行。
cb是事件响应函数指针,data则是用于保存用户自定义的数据。这样的组合设计在使用回调函数的开源库中很常见。因为回调的调用机会并不由我们掌握,我们无法区分每次回调对应于我们哪次注册行为。而可以通过在向框架注册回调函数时保存回调调用的数据来达到区分的目的。
pending用于表示该监视器在触发过的相同优先级下所有监视器数组的索引下标。因为相同优先级的监视器可能有很多,所以我们需要一个结构保存这样的一组数据,于是就需要索引/下标进行区分。
比如io事件的watcher如上定义,类似的各种watcher都采取此类方式来生成(这样的好处是可以减少代码的重复度,降低了代码维护的成本,但是可读性方面也相应降低)。
全局触发事件集合
loop->pendings记录管理当前已经发生等待调用回调函数的watcher集合,libev检查到事件发生后将对应的watcher加入到loop->pendings。
数据结构
*Watcher[N][M]:二位数组用来维护一轮循环下来,需要触发回调函数的watcher
其中N:是每一类watcher的优先级
另外还有loop->pendingcnt结构,也是一个二维数组,用来维护pending中每一行最大watcher下标。
以下是事件函数回调过程的代码。
本质上就是个遍历loop->pendings挨个调用watcher的注册的回调函数,可以看到按照优先级从高(小)到低(大),对于同一优先级watcher按照下标从大到小的方式来调用callBack函数。
值得注意的,就是pendingcnt结构,该结构维护当前每一行watcher数组当前的最大下标。
事件触发之IO事件
该节将会以IO事件在EPOLL平台的触发整个流程来阐述libev是如何来实现事件触发回调的整个过程。在介绍IO事件触发之前需要介绍一下相关的数据结构
ANFD:用来记录对一个fd的所有监听事件,每一个监听事件使用链表结构进行组织。
anfds:是ANFD类型的数组,用来管理每一个fd的监听的事件
fdchanges:是int类型数组,每个元素记录当前发生更改的fd,比如加入新的监听fd,或者fd的监听事件发送修改。
在每次循环之前,都会对fdchanges和anfds的结构中对应的fd事件列表的所有event进行 | 操作,得到当前整个fd当前的监听事件和ANFD.events进行对比,如果没有修改将不会该表epoll的fd的监听事件,这样做的好处,避免了无效的修改,保证了所有对epoll的修改都是必须的,毕竟频繁对epoll进行修改代价还是挺大的。
下面分析IO事件是如何触发的,当一个fd监听事件加入时
step1:调用ev_io_start将watcher加入到anfds中,并将修改记录在changfds中
steps2: 调用fd_reify,通过比对changfds & anfds确定是否需要加入epoll事件,此时显然是需要的。
step3:调用backend_poll函数得到当前监听已经发生的事件
#得到事件的fd & 已经发生的events,从anfds中获取对应的ANFD
#遍历ANFD中的watcher链表,比对监听事件和已经发生的监听事件,如果符合将该watcher加入到loop->pendings,修改watcher中的pending变量标记在loop->pendings中的数组下标
step4:遍历循环loop->pendings结构,挨个调用回调函数,从而完成事件触发的一个完整过程。
定时器原理
libev在初始化默认循环时调用了ev_default_loop方法,其会在底层调用evpipe_init方法。它会通过eventfd创建一个永远等不到的事件。这样我们就可以调整等待该事件的超时时间来达到定时执行的目的。
void noinline
ev_signal_start (EV_P_ ev_signal *w) EV_THROW
{
if (expect_false (ev_is_active (w)))
return;
assert (("libev: ev_signal_start called with illegal signal number", w->signum > 0 && w->signum < EV_NSIG));
#if EV_MULTIPLICITY
assert (("libev: a signal must not be attached to two different loops",
!signals [w->signum - 1].loop || signals [w->signum - 1].loop == loop));
signals [w->signum - 1].loop = EV_A;
ECB_MEMORY_FENCE_RELEASE;
#endif
EV_FREQUENT_CHECK;
#if EV_USE_SIGNALFD
if (sigfd == -2)
{
sigfd = signalfd (-1, &sigfd_set, SFD_NONBLOCK | SFD_CLOEXEC);
if (sigfd < 0 && errno == EINVAL)
sigfd = signalfd (-1, &sigfd_set, 0); /* retry without flags */
if (sigfd >= 0)
{
fd_intern (sigfd); /* doing it twice will not hurt */
sigemptyset (&sigfd_set);
ev_io_init (&sigfd_w, sigfdcb, sigfd, EV_READ);
ev_set_priority (&sigfd_w, EV_MAXPRI);
ev_io_start (EV_A_ &sigfd_w);
ev_unref (EV_A); /* signalfd watcher should not keep loop alive */
}
}
if (sigfd >= 0)
{
/* TODO: check .head */
sigaddset (&sigfd_set, w->signum);
sigprocmask (SIG_BLOCK, &sigfd_set, 0);
signalfd (sigfd, &sigfd_set, 0);
}
#endif
ev_start (EV_A_ (W)w, 1);
wlist_add (&signals [w->signum - 1].head, (WL)w);
if (!((WL)w)->next)
# if EV_USE_SIGNALFD
if (sigfd < 0) /*TODO*/
# endif
{
# ifdef _WIN32
evpipe_init (EV_A);
signal (w->signum, ev_sighandler);
# else
struct sigaction sa;
evpipe_init (EV_A);
sa.sa_handler = ev_sighandler;
sigfillset (&sa.sa_mask);
sa.sa_flags = SA_RESTART; /* if restarting works we save one iteration */
sigaction (w->signum, &sa, 0);
if (origflags & EVFLAG_NOSIGMASK)
{
sigemptyset (&sa.sa_mask);
sigaddset (&sa.sa_mask, w->signum);
sigprocmask (SIG_UNBLOCK, &sa.sa_mask, 0);
}
#endif
}
EV_FREQUENT_CHECK;
}
因为定时器并非是由事件触发而执行,而是由于事件没有触发导致等待超时而执行。所以backend_poll函数指针调用时,不可以一直等待下去,而要传递超时时间。从而让libev中利用“永远等不到的事件”相关的监视器有机会执行。
利用等待超时这个思路非常有意思。但是又面临另一个问题,超时时间的选择?比如我们现在有两个定时器:2秒一次和3秒一次,那么超时时间该设置成多少呢?如果设置成2秒超时,那么3秒一次的定时器将被延期1秒执行(需要等待到第二个周期)。如果设置为3秒超时,2秒一次的定时器也将被延期1秒执行。如果设置成1秒超时,则超时导致循环的次数增多……这种固定超时的方案怎么都不太好。那么libev是如何解决这个问题的呢?
libev在实现的内部不会有“定时”这样的概念,也就是说每次事件等待的时长是不确定的。这也是为什么各个IO模型需要暴露backend_poll方法的原因——需要每次指定超时的时间。
那这个超时时间怎么计算?每个需要使用等待超时功能触发的监视器,都会在一个结构中保存下次触发的时间。以上面例子为例,并且假设没有其他事件的干扰,假如现在时间是12:00:00,则2秒一次定时器监视器(后称2秒监视器)的“下次执行时间”为12:00:02;3秒一次的定时器监视器(后称3秒监视器)的“下次执行时间”为12:00:03。那么本次等待的时间是离当前时间最近的2秒监视器“下次执行时间”减去当前时间,即12:00:02-12:00:00=2秒。等到时间为12:00:02时,2秒定时器会被执行,并且其“下次执行时间”修改成12:00:04。假设2秒定时器和本次循环中逻辑的执行时间消耗了0.5秒,此时时钟已经走到12:00:02.5。此时离现在最近的“下次执行时间”是3秒监视器,则下次循环的等待时间是12:00:03-12:00:02.5=0.5秒。于是12:00:03时,3秒监视器会被执行。
ev_run函数
1.更新更改的FD事件
2.进行必要的sleep
3.backend_poll收集pending的IO事件
4.收集pending的timer事件
5.调用所有pending的事件
int
ev_run (EV_P_ int flags)
{
#if EV_FEATURE_API
++loop_depth;
#endif
assert (("libev: ev_loop recursion during release detected", loop_done != EVBREAK_RECURSE));
loop_done = EVBREAK_CANCEL;
EV_INVOKE_PENDING; /* in case we recurse, ensure ordering stays nice and clean */
do
{
#if EV_VERIFY >= 2
ev_verify (EV_A);
#endif
#ifndef _WIN32
if (expect_false (curpid)) /* penalise the forking check even more */
if (expect_false (getpid () != curpid))
{
curpid = getpid ();
postfork = 1;
}
#endif
#if EV_FORK_ENABLE
/* we might have forked, so queue fork handlers */
if (expect_false (postfork))
if (forkcnt)
{
queue_events (EV_A_ (W *)forks, forkcnt, EV_FORK);
EV_INVOKE_PENDING;
}
#endif
#if EV_PREPARE_ENABLE
/* queue prepare watchers (and execute them) */
if (expect_false (preparecnt))
{
queue_events (EV_A_ (W *)prepares, preparecnt, EV_PREPARE);
EV_INVOKE_PENDING;
}
#endif
if (expect_false (loop_done))
break;
//检测是否有fork事件,如果有进行fork事件的回调函数
/* we might have forked, so reify kernel state if necessary */
if (expect_false (postfork))
loop_fork (EV_A);
//检测fd的监听事件是否发生变化,是否需要修改epoll的监听事件
/* update fd-related kernel structures */
fd_reify (EV_A);
/*
计算需要休眠的事件
#根据定时器 & 周期任务 & timeout_blocktime(超时事件收集间隔事件) & io_blocktime(io事件收集间隔事件)
等信息计算此次循环需要sleep的时间
*/
/* calculate blocking time */
{
ev_tstamp waittime = 0.;
ev_tstamp sleeptime = 0.;
/* remember old timestamp for io_blocktime calculation */
ev_tstamp prev_mn_now = mn_now;
/* update time to cancel out callback processing overhead */
time_update (EV_A_ 1e100);
/* from now on, we want a pipe-wake-up */
pipe_write_wanted = 1;
ECB_MEMORY_FENCE; /* make sure pipe_write_wanted is visible before we check for potential skips */
if (expect_true (!(flags & EVRUN_NOWAIT || idleall || !activecnt || pipe_write_skipped)))
{
waittime = MAX_BLOCKTIME;
if (timercnt)
{
ev_tstamp to = ANHE_at (timers [HEAP0]) - mn_now;
if (waittime > to) waittime = to;
}
#if EV_PERIODIC_ENABLE
if (periodiccnt)
{
ev_tstamp to = ANHE_at (periodics [HEAP0]) - ev_rt_now;
if (waittime > to) waittime = to;
}
#endif
/* don't let timeouts decrease the waittime below timeout_blocktime */
if (expect_false (waittime < timeout_blocktime))
waittime = timeout_blocktime;
/* at this point, we NEED to wait, so we have to ensure */
/* to pass a minimum nonzero value to the backend */
if (expect_false (waittime < backend_mintime))
waittime = backend_mintime;
/* extra check because io_blocktime is commonly 0 */
if (expect_false (io_blocktime))
{
sleeptime = io_blocktime - (mn_now - prev_mn_now);
if (sleeptime > waittime - backend_mintime)
sleeptime = waittime - backend_mintime;
if (expect_true (sleeptime > 0.))
{
//如果需要休眠则进行休眠
ev_sleep (sleeptime);
waittime -= sleeptime;
}
}
}
#if EV_FEATURE_API
++loop_count;
#endif
assert ((loop_done = EVBREAK_RECURSE, 1)); /* assert for side effect */
backend_poll (EV_A_ waittime);//这里开始调用上层封装的epool,select进行轮询,收集pending事件
assert ((loop_done = EVBREAK_CANCEL, 1)); /* assert for side effect */
pipe_write_wanted = 0; /* just an optimisation, no fence needed */
ECB_MEMORY_FENCE_ACQUIRE;
if (pipe_write_skipped)
{
assert (("libev: pipe_w not active, but pipe not written", ev_is_active (&pipe_w)));
ev_feed_event (EV_A_ &pipe_w, EV_CUSTOM);
}
/* update ev_rt_now, do magic */
time_update (EV_A_ waittime + sleeptime);
}
/* queue pending timers and reschedule them */
timers_reify (EV_A); /* relative timers called last *///对pending的timer事件进行收集
#if EV_PERIODIC_ENABLE
periodics_reify (EV_A); /* absolute timers called first */
#endif
#if EV_IDLE_ENABLE
/* queue idle watchers unless other events are pending */
idle_reify (EV_A);
#endif
#if EV_CHECK_ENABLE
/* queue check watchers, to be executed first */
if (expect_false (checkcnt))
queue_events (EV_A_ (W *)checks, checkcnt, EV_CHECK);
#endif
EV_INVOKE_PENDING;//遍历所有pending事件
}
while (expect_true (
activecnt
&& !loop_done
&& !(flags & (EVRUN_ONCE | EVRUN_NOWAIT))
));
if (loop_done == EVBREAK_ONE)
loop_done = EVBREAK_CANCEL;
#if EV_FEATURE_API
--loop_depth;
#endif
return activecnt;
}