struct event_base {
// ... ...
/**存储通用时间队列 */
struct common_timeout_list **common_timeout_queues;
/** The number of entries used in common_timeout_queues */
int n_common_timeouts;
/**通用时间队列中元素个数 */
int n_common_timeouts_allocated;
// ... ...
/** Stored timeval; used to detect when time is running backwards. */
struct timeval event_tv;
/** 管理超时事件的小根堆*/
struct min_heap timeheap;
/** 时间缓存,避免程序内部多次调用gettimeofday操作,引发的上下文切换 */
struct timeval tv_cache;
#if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
/** Difference between internal time (maybe from clock_gettime) and
* gettimeofday. */
struct timeval tv_clock_diff;
/** Second in which we last updated tv_clock_diff, in monotonic time. */
time_t last_updated_clock_diff;
#endif
//... ...
};
static int
gettime(struct event_base *base, struct timeval *tp)
{
EVENT_BASE_ASSERT_LOCKED(base);
if (base->tv_cache.tv_sec) {
*tp = base->tv_cache;
return (0);
}
#if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
if (use_monotonic) {
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
return (-1);
tp->tv_sec = ts.tv_sec;
tp->tv_usec = ts.tv_nsec / 1000;
//1s 更新一次
if (base->last_updated_clock_diff + CLOCK_SYNC_INTERVAL
< ts.tv_sec) {
struct timeval tv;
evutil_gettimeofday(&tv,NULL);
evutil_timersub(&tv, tp, &base->tv_clock_diff);
base->last_updated_clock_diff = ts.tv_sec;
}
return (0);
}
#endif
return (evutil_gettimeofday(tp, NULL));
}
在gettime函数中,优先获取tv_cache的时间,如果缓存时间为0,才去调用系统函数获取时间。
1. Cache Time
struct timeval tv_cache,用于缓存当前程序获取到的系统时间,为了避免多次调用gettimeofday系统函数,造成不必要的性能消耗。
static inline void
update_time_cache(struct event_base *base)
{
base->tv_cache.tv_sec = 0;
if (!(base->flags & EVENT_BASE_FLAG_NO_CACHE_TIME))
gettime(base, &base->tv_cache);
}
update_time_cache 函数将调用gettime更新缓存中的时间。对超时的处理主要集中在event_base_loop函数中。
int
event_base_loop(struct event_base *base, int flags)
{
const struct eventop *evsel = base->evsel;
struct timeval tv;
struct timeval *tv_p;
int res, done, retval = 0;
/* Grab the lock. We will release it inside evsel.dispatch, and again
* as we invoke user callbacks. */
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
if (base->running_loop) {
event_warnx("%s: reentrant invocation. Only one event_base_loop"
" can run on each event_base at once.", __func__);
EVBASE_RELEASE_LOCK(base, th_base_lock);
return -1;
}
base->running_loop = 1;
//清空缓存时间,这么做是为了保证每次循环开始时,缓存时间是最新的
//因为只有缓存时间为0,才会调用系统函数去更新缓存时间
clear_time_cache(base);
if (base->sig.ev_signal_added && base->sig.ev_n_signals_added)
evsig_set_base(base);
done = 0;
#ifndef _EVENT_DISABLE_THREAD_SUPPORT
base->th_owner_id = EVTHREAD_GET_ID();
#endif
base->event_gotterm = base->event_break = 0;
while (!done) {
base->event_continue = 0;
/* Terminate the loop if we have been asked to */
if (base->event_gotterm) {
break;
}
if (base->event_break) {
break;
}
//进行时间校准,如果用户修改了系统时间,需要重新处理超时的绝对时间
timeout_correct(base, &tv);
tv_p = &tv;
if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
timeout_next(base, &tv_p);
} else {
/*
* if we have active events, we just poll new events
* without waiting.
*/
evutil_timerclear(&tv);
}
/* If we have no events, we just exit */
if (!event_haveevents(base) && !N_ACTIVE_CALLBACKS(base)) {
event_debug(("%s: no events registered.", __func__));
retval = 1;
goto done;
}
//更新event_tv,event_tv需要定期的更新。因为time_correct将用event_tv作为用户修改前的系统时间标准,
//用来计算修改的差值。定期更新是为了防止用户在修改完时间后又多次进行修改。这样可以尽可能保证准确。
gettime(base, &base->event_tv);
//这里需要再次清空缓存时间,因为在dispatch函数中,event可能会wait一段时间,这样缓存中的时间就不准了,
//如果在dispatch的时候又event_add,在调用gettime获取的时间非正常时间。
clear_time_cache(base);
//多路IO复用函数。
res = evsel->dispatch(base, tv_p);
if (res == -1) {
event_debug(("%s: dispatch returned unsuccessfully.",
__func__));
retval = -1;
goto done;
}
//更新缓存时间
update_time_cache(base);
//处理超时event
timeout_process(base);
if (N_ACTIVE_CALLBACKS(base)) {
int n = event_process_active(base);
if ((flags & EVLOOP_ONCE)
&& N_ACTIVE_CALLBACKS(base) == 0
&& n != 0)
done = 1;
} else if (flags & EVLOOP_NONBLOCK)
done = 1;
}
event_debug(("%s: asked to terminate loop.", __func__));
done:
clear_time_cache(base);
base->running_loop = 0;
EVBASE_RELEASE_LOCK(base, th_base_lock);
return (retval);
}
timeout_process 函数用于激活超时event
static void
timeout_process(struct event_base *base)
{
/* Caller must hold lock. */
struct timeval now;
struct event *ev;
if (min_heap_empty(&base->timeheap)) {
return;
}
gettime(base, &now);
//从小根堆依次取最小的超时时间,如果未超时则退出循环,否则,调用event_del_internal,将event
//从对应的队列中删除,然后调用event_active_nolock激活event
while ((ev = min_heap_top(&base->timeheap))) {
if (evutil_timercmp(&ev->ev_timeout, &now, >))
break;
/* delete this event from the I/O queues */
event_del_internal(ev);
event_debug(("timeout_process: call %p",
ev->ev_callback));
//EV_TIMEOUT表示改event的回调为超时回调,1表示将有1个回调被调用。
event_active_nolock(ev, EV_TIMEOUT, 1);
}
}
前面提到的,如果用户在event未被触发前修改了系统时间,那么就会导致gettimeofday获取时间错误。为了解决该问题,libevent使用了CLOCK_MONOTONIC属性。CLOCK_MONOTONIC获取到的是系统启动到现在所走过的时间,这个时间用户无法修改。在支持CLOCK_MONOTONIC的系统上运行,只需要clock_gettime(CLOCK_MONOTONIC, &ts),便可以获取到一个单调递增的time。
CLOCK_MONOTONIC处理系统时间跳变很方便,但是不是所有的系统都支持。所以libevent提供了自己处理系统时间跳变的方法。当用户改变了系统时间后,可以将已经存放在小根堆里的所有Time都减去相同的time difference。因为小根堆里存放的都是每个Timer event超时的absolutely time,所以这就相当于把minheap里的所有Timer也随着系统时间一起做了调整。对每个元素进行相同的时间调整本身不会改变minheap的结构,只需要遍历每个元素即可。
static void
timeout_correct(struct event_base *base, struct timeval *tv)
{
/* Caller must hold th_base_lock. */
struct event **pev;
unsigned int size;
struct timeval off;
int i;
if (use_monotonic)
return;
/* Check if time is running backwards */
gettime(base, tv);
//检测是否系统时间被往回调整
if (evutil_timercmp(tv, &base->event_tv, >=)) {
base->event_tv = *tv;
return;
}
event_debug(("%s: time is running backwards, corrected",
__func__));
//计算出往回调整的时间差off
evutil_timersub(&base->event_tv, tv, &off);
//对于小根堆,因为对每个元素都做相同的改变,不会影响堆结构,只需遍历
pev = base->timeheap.p;
size = base->timeheap.n;
for (; size-- > 0; ++pev) {
struct timeval *ev_tv = &(**pev).ev_timeout;
evutil_timersub(ev_tv, &off, ev_tv);
}
//对于通用时间队列,也只需要遍历调整
for (i=0; in_common_timeouts; ++i) {
struct event *ev;
struct common_timeout_list *ctl =
base->common_timeout_queues[i];
TAILQ_FOREACH(ev, &ctl->events,
ev_timeout_pos.ev_next_with_common_timeout) {
struct timeval *ev_tv = &ev->ev_timeout;
ev_tv->tv_usec &= MICROSECONDS_MASK;
evutil_timersub(ev_tv, &off, ev_tv);
ev_tv->tv_usec |= COMMON_TIMEOUT_MAGIC |
(i<event_tv = *tv;
}