前言 : 为了支持定时器, Libevent 必须和系统时间打交道。主要涉及到时间的加减辅助函数、事件缓存、时间校正和定时器堆的时间值调整等。
1 初始化检测
Libevent 在初始化时会检测系统时间的类型,通过调用函数 detect_monotonic() 完成,它通过调用 clock_gettime() 来检测系统是否支持 monotonic 时钟类型
static void detect_monotonic(){
#if defined(HAVE_CLOCK_GETTIME) &&defined(CLOCK_MONOTONIC)
struct timespec ts;
if(clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
use_monotonic = 1;
#endif
}
2 事件缓存
结构体 event_base 中的 tv_cache,用来记录事件缓存。需要分析 gettime() 函数
static int gettime(struct event_base *base, struct timeval *tp){
// 如果 ev_cache 时间缓存已设置,就直接使用
if(base -> tv_cache.tv_sec){
*tp = base -> tv_cache;
return 0;
}
// 如果支持 monotonic, 就用 clock_gettime 获取 monotonic 时间
#if defined (HAVE_CLCOK_GETTIME) && defined(CLOCK_MONOTONIC)
if(use_monotonic)
{
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
return -1;
tv -> tv_sec = ts.tv_sec;
tp -> tv_usec = ts.tv_nsec / 1000;
return 0;
}
#endif
// 否则只能取得系统的当前时间
return evutil_gettimeofday(tp, NULL);
}
在每次系统循环中,时间缓存 tv_cache 将会被相应的清空和重置。
时间 event_tv 指示了 dispatch()上次返回,也就是 I/O 事件就绪的时间。第一次循环时, tv_cache 被清空,因此 gettime() 获取当前系统时间。而后会更新 tv_cache 时间
时间 tv_cache 在 dispatch() 返回后被设置为系统时间,因此它缓存了本次 I/O 事件就绪的时间 : event_tv
3 时间校正
如果系统不支持 monotonic 时间,用户可能手动的调整时间,这就需要调整时间。由函数 tomeout_correct() 完成
static void timeout_correct(struct event_base *base, struct timeval *tv){
struct event **pev;
unsigned int size;
struct timeval off;
if(use_monotonic) return;
gettime(base, tv);
//如果 tv < event_tv 表明用户向前调整时间了,需要校正
if(evutil_timercmp(tv, &base -> event_tv, >=))
{
base -> event_tv = *tv;
return ;
}
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);
}
// 更新 event_tv 为 tv_cache
base -> event_tv = *tv;
}