libevent源码深度剖析十一

前言

为了支持定时器,Libevent必须和系统时间打交道,这一部分的内容也比较简单,主要涉及到时间的加减辅助函数、时间缓存、时间校正和定时器堆的时间值调整等。下面就结合源代码来分析一下。

初始化检测

Libevent在初始化时会检测系统时间的类型,通过调用函数detect_monotonic()完成,它通过调用clock_gettime()来检测系统是否支持monotonic时钟类型:

static void detect_monotonic(void)
{
#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
    struct timespec    ts;
    if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
        use_monotonic = 1; // 系统支持monotonic时间
#endif
}

Monotonic时间指示的是系统从boot后到现在所经过的时间,如果系统支持Monotonic时间就将全局变量use_monotonic设置为1,设置use_monotonic到底有什么用,这个在后面说到时间校正时就能看出来了。

时间缓存

结构体event_base中的tv_cache,用来记录时间缓存。这个还要从函数gettime()说起,先来看看该函数的代码:

static int gettime(struct event_base *base, struct timeval *tp)
{
    // 如果tv_cache时间缓存已设置,就直接使用
    if (base->tv_cache.tv_sec) {
        *tp = base->tv_cache;
        return (0);
    }
    // 如果支持monotonic,就用clock_gettime获取monotonic时间
#if defined(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;
        return (0);
    }
#endif
    // 否则只能取得系统当前时间
    return (evutil_gettimeofday(tp, NULL));
}

如果tv_cache已经设置,那么就直接使用缓存的时间;否则需要再次执行系统调用获取系统时间。
函数evutil_gettimeofday()用来获取当前系统时间,在Linux下其实就是系统调用gettimeofday();Windows没有提供函数gettimeofday,而是通过调用_ftime()来完成的。
在每次系统事件循环中,时间缓存tv_cache将会被相应的清空和设置,再次来看看下面event_base_loop的主要代码逻辑:

int event_base_loop(struct event_base *base, int flags)
{
    // 清空时间缓存
    base->tv_cache.tv_sec = 0;
    while(!done){
        timeout_correct(base, &tv); // 时间校正
        // 更新event_tv到tv_cache指示的时间或者当前时间(第一次)
         // event_tv <--- tv_cache
        gettime(base, &base->event_tv);
        // 清空时间缓存-- 时间点1
        base->tv_cache.tv_sec = 0;
        // 等待I/O事件就绪
        res = evsel->dispatch(base, evbase, tv_p);
        // 缓存tv_cache存储了当前时间的值-- 时间点2
         // tv_cache <--- now
        gettime(base, &base->tv_cache);
        // .. 处理就绪事件
    }
    // 退出时也要清空时间缓存
    base->tv_cache.tv_sec = 0;
    return (0);
}

时间event_tv指示了dispatch()上次返回,也就是I/O事件就绪时的时间,第一次进入循环时,由于tv_cache被清空,因此gettime()执行系统调用获取当前系统时间;而后将会更新为tv_cache指示的时间。

时间tv_cache在dispatch()返回后被设置为当前系统时间,因此它缓存了本次I/O事件就绪时的时间(event_tv)。从代码逻辑里可以看出event_tv取得的是tv_cache上一次的值,因此event_tv应该小于tv_cache的值。

设置时间缓存的优点是不必每次获取时间都执行系统调用,这是个相对费时的操作;在上面标注的时间点2到时间点1的这段时间(处理就绪事件时),调用gettime()取得的都是tv_cache缓存的时间。

时间校正

如果系统支持monotonic时间,该时间是系统从boot后到现在所经过的时间,因此不需要执行校正。
根据前面的代码逻辑,如果系统不支持monotonic时间,用户可能会手动的调整时间,如果时间被向前调整了(MS前面第7部分讲成了向后调整,要改正),比如从5点调整到了3点,那么在时间点2取得的值可能会小于上次的时间,这就需要调整了,下面来看看校正的具体代码,由函数timeout_correct()完成:

static void timeout_correct(struct event_base *base, struct timeval *tv)
{
    struct event **pev;
    unsigned int size;
    struct timeval off;
    if (use_monotonic) // monotonic时间就直接返回,无需调整
        return;
    gettime(base, tv); // tv <---tv_cache
    // 根据前面的分析可以知道event_tv应该小于tv_cache
    // 如果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);
    }
    base->event_tv = *tv; // 更新event_tv为tv_cache
}

在调整小根堆时,因为所有定时事件的时间值都会被减去相同的值,因此虽然堆中元素的时间键值改变了,但是相对关系并没有改变,不会改变堆的整体结构。因此只需要遍历堆中的所有元素,将每个元素的时间键值减去相同的值即可完成调整,不需要重新调整堆的结构。
当然调整完后,要将event_tv值重新设置为tv_cache值了。

小节

主要分析了一下libevent对系统时间的处理,时间缓存、时间校正和定时堆的时间值调整等,逻辑还是很简单的,时间的加减、设置等辅助函数则非常简单,主要在头文件evutil.h中,就不再多说了。

相关博客

libevent源码剖析

转自:libevent源码深度剖析十一

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
libevent是一个事件驱动的网络编程库,适用于高并发的网络应用。通过深度剖析libevent源码,我们可以更好地理解其工作原理和实现机制。 首先,在下载libevent源码之前,我们需要确认所需的版本和平台兼容性,这样可以避免不必要的错误和兼容性问题。 在深度剖析libevent源码时,我们可以从以下几个方面入手: 1. 事件循环机制:libevent基于事件循环机制实现事件的响应和处理。源码中会包含事件循环的实现细节,如事件的注册、删除、触发等操作。研究这些实现可以帮助我们理解事件驱动模型的运行机制。 2. IO多路复用:libevent在底层使用了IO多路复用技术,可以同时处理多个网络连接,提高并发处理能力。源码中会涉及到IO多路复用的实现细节,如select、epoll等。了解这些实现可以帮助我们深入理解libevent是如何高效地管理和处理网络连接的。 3. 常用数据结构和算法:libevent源码中使用了一些常用的数据结构和算法,如链表、堆等。通过研究这些数据结构和算法的实现,可以提高我们对libevent整体架构的理解。 4. 错误处理和调试机制:源码中通常也会包含一些错误处理和调试机制,可以帮助我们排查和解决问题。了解这些机制可以提高我们在使用libevent时的调试和排错能力。 总之,深度剖析libevent源码可以帮助我们更好地理解其工作原理和实现机制,从而更好地使用和调优libevent,提高网络应用的性能和并发处理能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值