hloop_process_events函数在上一篇中已经看过,今天深入看一下这部分代码。
如有理解错误,欢迎批评指正。
代码总览
// hloop_process_ios -> hloop_process_timers -> hloop_process_idles -> hloop_process_pendings
static int hloop_process_events(hloop_t* loop) {
// ios -> timers -> idles
int nios, ntimers, nidles;
nios = ntimers = nidles = 0;
// calc blocktime
int32_t blocktime = HLOOP_MAX_BLOCK_TIME;
if (loop->timers.root) {
hloop_update_time(loop);
uint64_t next_min_timeout = TIMER_ENTRY(loop->timers.root)->next_timeout;
int64_t blocktime_us = next_min_timeout - hloop_now_hrtime(loop);
if (blocktime_us <= 0) goto process_timers;
blocktime = blocktime_us / 1000;
++blocktime;
blocktime = MIN(blocktime, HLOOP_MAX_BLOCK_TIME);
}
if (loop->nios) {
nios = hloop_process_ios(loop, blocktime);
}
else {
msleep(blocktime);
}
hloop_update_time(loop);
// wakeup by hloop_stop
if (loop->status == HLOOP_STATUS_STOP) {
return 0;
}
process_timers:
if (loop->ntimers) {
ntimers = hloop_process_timers(loop);
}
int npendings = loop->npendings;
if (npendings == 0) {
if (loop->nidles) {
nidles= hloop_process_idles(loop);
}
}
int ncbs = hloop_process_pendings(loop);
// printd("blocktime=%d nios=%d/%u ntimers=%d/%u nidles=%d/%u nactives=%d npendings=%d ncbs=%d\n",
// blocktime, nios, loop->nios, ntimers, loop->ntimers, nidles, loop->nidles,
// loop->nactives, npendings, ncbs);
return ncbs;
}
首先看一下与定时器相关的代码
if (loop->timers.root) {
hloop_update_time(loop);
uint64_t next_min_timeout = TIMER_ENTRY(loop->timers.root)->next_timeout;
int64_t blocktime_us = next_min_timeout - hloop_now_hrtime(loop);
if (blocktime_us <= 0) goto process_timers;
blocktime = blocktime_us / 1000;
++blocktime;
blocktime = MIN(blocktime, HLOOP_MAX_BLOCK_TIME);
}
if (blocktime_us <= 0) goto process_timers;
从这部分代码可以看出,对于超时事件,libhv会优先处理。
更新loop时间
在获取下一个要处理的超时事件前,先调用了hloop_update_time(loop),这个函数是在修改操作系统时间后,调整事件循环的开始时间(start_ms)。具体看一下是怎么做的。
void hloop_update_time(hloop_t* loop) {
loop->cur_hrtime = gethrtime_us();
if (ABS((int64_t)hloop_now(loop) - (int64_t)time(NULL)) > 1) {
// systemtime changed, we adjust start_ms
loop->start_ms = gettimeofday_ms() - (loop->cur_hrtime - loop->start_hrtime) / 1000;
}
}
首先用gethrtime_us()获取了当前时间
unsigned long long gethrtime_us() {
#ifdef OS_WIN
static LONGLONG s_freq = 0;
if (s_freq == 0) {
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
s_freq = freq.QuadPart;
}
if (s_freq != 0) {
LARGE_INTEGER count;
QueryPerformanceCounter(&count);
return (unsigned long long)(count.QuadPart / (double)s_freq * 1000000);
}
return 0;
#elif defined(OS_SOLARIS)
return gethrtime() / 1000;
#elif HAVE_CLOCK_GETTIME
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec*(unsigned long long)1000000 + ts.tv_nsec / 1000;
#else
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec*(unsigned long long)1000000 + tv.tv_usec;
#endif
}
在Linux上,调用了clock_gettime(CLOCK_MONOTONIC, &ts);获取了当前时间。clock_gettime的作用如下:
int clock_gettime(clockid_t clk_id,struct timespec *tp);
clk_id : 检索和设置的clk_id指定的时钟时间。
CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变,即从UTC1970-1-1 0:0:0开始计时,
中间时刻如果系统时间被用户改成其他,则对应的时间相应改变
CLOCK_MONOTONIC:从系统启动这一刻起开始计时,不受系统时间被用户改变的影响
CLOCK_PROCESS_CPUTIME_ID:本进程到当前代码系统CPU花费的时间
CLOCK_THREAD_CPUTIME_ID:本线程到当前代码系统CPU花费的时间
所以gethrtime_us()的作用是,从系统启动开始后经过的时间,与系统时间无关,当用户修改了系统时间后,也不受影响。
uint64_t hloop_now(hloop_t* loop) {
return loop->start_ms / 1000 + (loop->cur_hrtime - loop->start_hrtime) / 1000000;
}
hloop_now()的作用是,从事件循环开始,经过的us数。此时的时间是以设置loop的start_ms的系统时间为基准的。
那么如果用户修改了系统时间,下面的代码就会判定为“真”,并修改事件循环的开始事件。
if (ABS((int64_t)hloop_now(loop) - (int64_t)time(NULL)) > 1) {
// systemtime changed, we adjust start_ms
//开始时间 = 当前系统时间(修改后) - (事件循环执行经过的时间)
loop->start_ms = gettimeofday_ms() - (loop->cur_hrtime - loop->start_hrtime) / 1000;
}
IO事件
整个IO事件的过程,后面应该会专门写一个,今天先看下如何处理的。
if (loop->nios) {
nios = hloop_process_ios(loop, blocktime);
}
这里blocktime时间就是当前时间和下一个超时事件的时间差,也就是说,IO事件等待的超时时间,不能影响下一个超时事件的处理。
static int hloop_process_ios(hloop_t* loop, int timeout) {
// That is to call IO multiplexing function such as select, poll, epoll, etc.
int nevents = iowatcher_poll_events(loop, timeout);
if (nevents < 0) {
hloge("poll_events error=%d", -nevents);
}
return nevents < 0 ? 0 : nevents;
}
这里只分析用epoll的方式处理io事件。
int iowatcher_poll_events(hloop_t* loop, int timeout) {
epoll_ctx_t* epoll_ctx = (epoll_ctx_t*)loop->iowatcher;
if (epoll_ctx == NULL) return 0;
if (epoll_ctx->events.size == 0) return 0;
//等待IO事件的发生
int nepoll = epoll_wait(epoll_ctx->epfd, epoll_ctx->events.ptr, epoll_ctx->events.size, timeout);
if (nepoll < 0) {
perror("epoll");
return nepoll;
}
//没有IO事件,直接返回
if (nepoll == 0) return 0;
int nevents = 0;
//遍历所有的IO事件
for (int i = 0; i < epoll_ctx->events.size; ++i) {
//取出io事件
struct epoll_event* ee = epoll_ctx->events.ptr + i;
int fd = ee->data.fd;
uint32_t revents = ee->events;
if (revents) {
++nevents;
//根据fd从io事件数组中取出io事件对象
hio_t* io = loop->ios.ptr[fd];
if (io) {
//判断接收到的事件类型
/*
EPOLLIN 当关联的文件可以执行 read ()操作时
EPOLLHUP 当指定的文件描述符被挂起的时候。
EPOLLERR 当关联的文件发生错误的时候
EPOLLOUT 当关联的文件可以执行 write ()操作时
*/
if (revents & (EPOLLIN | EPOLLHUP | EPOLLERR)) {
io->revents |= HV_READ;
}
if (revents & (EPOLLOUT | EPOLLHUP | EPOLLERR)) {
io->revents |= HV_WRITE;
}
//添加到待执行的链表中
EVENT_PENDING(io);
}
}
if (nevents == nepoll) break;
}
return nevents;
}
定时器事件
if (loop->ntimers) {
ntimers = hloop_process_timers(loop);
}
static int hloop_process_timers(hloop_t* loop) {
int ntimers = 0;
htimer_t* timer = NULL;
uint64_t now_hrtime = hloop_now_hrtime(loop);
while (loop->timers.root) {
// NOTE: root of minheap has min timeout.
timer = TIMER_ENTRY(loop->timers.root);
//下一个要执行的时间大于当前时间,也就是没有超时的事件
if (timer->next_timeout > now_hrtime) {
break;
}
//如果不是无限循环的定时器事件,就减少一次重复数
if (timer->repeat != INFINITE) {
--timer->repeat;
}
//如果重复数为0的话,就从堆里面删除
if (timer->repeat == 0) {
// NOTE: Just mark it as destroy and remove from heap.
// Real deletion occurs after hloop_process_pendings.
__htimer_del(timer);
}
else {
// NOTE: calc next timeout, then re-insert heap.
//从堆里面移除
heap_dequeue(&loop->timers);
//计算下一次超时时间
if (timer->event_type == HEVENT_TYPE_TIMEOUT) {
while (timer->next_timeout <= now_hrtime) {
timer->next_timeout += (uint64_t)((htimeout_t*)timer)->timeout * 1000;
}
}
else if (timer->event_type == HEVENT_TYPE_PERIOD) {
hperiod_t* period = (hperiod_t*)timer;
timer->next_timeout = (uint64_t)cron_next_timeout(period->minute, period->hour, period->day,
period->week, period->month) * 1000000;
}
//重新加入到堆中
heap_insert(&loop->timers, &timer->node);
}
//添加到待执行的链表中
EVENT_PENDING(timer);
++ntimers;
}
return ntimers;
}
定时器部分的代码相对还是比较容易理解的,核心问题在存放定时器事件的堆是如何设计的,这个以后专门去分析。
空闲事件
int npendings = loop->npendings;
if (npendings == 0) {
if (loop->nidles) {
nidles= hloop_process_idles(loop);
}
}
如果既没有io事件,又没有定时器超时事件,那么就空闲事件
static int hloop_process_idles(hloop_t* loop) {
int nidles = 0;
struct list_node* node = loop->idles.next;
hidle_t* idle = NULL;
//遍历所有的空闲事件,(这里用的是双向循环链表)
while (node != &loop->idles) {
idle = IDLE_ENTRY(node);
node = node->next;
if (idle->repeat != INFINITE) {
--idle->repeat;
}
if (idle->repeat == 0) {
// NOTE: Just mark it as destroy and remove from list.
// Real deletion occurs after hloop_process_pendings.
__hidle_del(idle);
}
//将所有的空闲事件都加入到待处理的链表中
EVENT_PENDING(idle);
++nidles;
}
return nidles;
}
代码也是很简洁,核心是存储空闲事件的双向链表的设计,后续分析。
执行待处理事件
将io事件,定时器超时事件,空闲事件都加入到待处理链表中后,下面看一下是如何处理的。
static int hloop_process_pendings(hloop_t* loop) {
if (loop->npendings == 0) return 0;
hevent_t* cur = NULL;
hevent_t* next = NULL;
int ncbs = 0;
// NOTE: invoke event callback from high to low sorted by priority.
for (int i = HEVENT_PRIORITY_SIZE-1; i >= 0; --i) {
//由优先级由高到低,取出相应的链表
cur = loop->pendings[i];
while (cur) {
//遍历链表,取出事件
next = cur->pending_next;
if (cur->pending) {//等待处理标志
if (cur->active && cur->cb) {
cur->cb(cur);//执行回调函数
++ncbs;
}
cur->pending = 0;
// NOTE: Now we can safely delete event marked as destroy.
if (cur->destroy) {
EVENT_DEL(cur);//删除事件
}
}
cur = next;
}
loop->pendings[i] = NULL; //清空当前优先级链表
}
loop->npendings = 0;
return ncbs;
}
总结
以上就是对hloop_process_events函数的一个分析,主要了解了如何获取各种待处理的事件的。代码十分的简洁,比较容易理解。但更多核心代码需要进一步的分析,比如定时器用到的堆,IO事件用到的数组,空闲事件用到的链表,以及为什么没有明显看到对用户自定义事件的处理等。