libevent源码深度剖析十一

——时间管理
张亮

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

1 初始化检测

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

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

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

2 时间缓存

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

  1. static int gettime(struct event_base *base, struct timeval *tp)
  2. {
  3. // 如果tv_cache时间缓存已设置,就直接使用
  4. if (base->tv_cache.tv_sec) {
  5. *tp = base->tv_cache;
  6. return (0);
  7. }
  8. // 如果支持monotonic,就用clock_gettime获取monotonic时间
  9. #if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
  10. if (use_monotonic) {
  11. struct timespec    ts;
  12. if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
  13. return (-1);
  14. tp->tv_sec = ts.tv_sec;
  15. tp->tv_usec = ts.tv_nsec / 1000;
  16. return (0);
  17. }
  18. #endif
  19. // 否则只能取得系统当前时间
  20. return (evutil_gettimeofday(tp, NULL));
  21. }

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

  1. int event_base_loop(struct event_base *base, int flags)
  2. {
  3. // 清空时间缓存
  4. base->tv_cache.tv_sec = 0;
  5. while(!done){
  6. timeout_correct(base, &tv); // 时间校正
  7. // 更新event_tv到tv_cache指示的时间或者当前时间(第一次)
  8. // event_tv <--- tv_cache
  9. gettime(base, &base->event_tv);
  10. // 清空时间缓存-- 时间点1
  11. base->tv_cache.tv_sec = 0;
  12. // 等待I/O事件就绪
  13. res = evsel->dispatch(base, evbase, tv_p);
  14. // 缓存tv_cache存储了当前时间的值-- 时间点2
  15. // tv_cache <--- now
  16. gettime(base, &base->tv_cache);
  17. // .. 处理就绪事件
  18. }
  19. // 退出时也要清空时间缓存
  20. base->tv_cache.tv_sec = 0;
  21. return (0);
  22. }

时间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缓存的时间。

3 时间校正

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

  1. static void timeout_correct(struct event_base *base, struct timeval *tv)
  2. {
  3. struct event **pev;
  4. unsigned int size;
  5. struct timeval off;
  6. if (use_monotonic) // monotonic时间就直接返回,无需调整
  7. return;
  8. gettime(base, tv); // tv <---tv_cache
  9. // 根据前面的分析可以知道event_tv应该小于tv_cache
  10. // 如果tv < event_tv表明用户向前调整时间了,需要校正时间
  11. if (evutil_timercmp(tv, &base->event_tv, >=)) {
  12. base->event_tv = *tv;
  13. return;
  14. }
  15. // 计算时间差值
  16. evutil_timersub(&base->event_tv, tv, &off);
  17. // 调整定时事件小根堆
  18. pev = base->timeheap.p;
  19. size = base->timeheap.n;
  20. for (; size-- > 0; ++pev) {
  21. struct timeval *ev_tv = &(**pev).ev_timeout;
  22. evutil_timersub(ev_tv, &off, ev_tv);
  23. }
  24. base->event_tv = *tv; // 更新event_tv为tv_cache
  25. }

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

4 小节

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

libevent源码深度剖析十一的更多相关文章

  1. libevent 源码深度剖析十三

    libevent 源码深度剖析十三 —— libevent 信号处理注意点 前面讲到了 libevent 实现多线程的方法,然而在多线程的环境中注册信号事件,还是有一些情况需要小心处理,那就是不能在多 ...

  2. libevent源码深度剖析十二

    libevent源码深度剖析十二 ——让libevent支持多线程 张亮 Libevent本身不是多线程安全的,在多核的时代,如何能充分利用CPU的能力呢,这一节来说说如何在多线程环境中使用libev ...

  3. libevent源码深度剖析十

    libevent源码深度剖析十 ——支持I/O多路复用技术 张亮 Libevent的核心是事件驱动.同步非阻塞,为了达到这一目标,必须采用系统提供的I/O多路复用技术,而这些在Windows.Linu ...

  4. libevent源码深度剖析九

    libevent源码深度剖析九 ——集成定时器事件 张亮 现在再来详细分析libevent中I/O事件和Timer事件的集成,与Signal相比,Timer事件的集成会直观和简单很多.Libevent ...

  5. libevent源码深度剖析八

    libevent源码深度剖析八 ——集成信号处理 张亮 现在我们已经了解了libevent的基本框架:事件管理框架和事件主循环.上节提到了libevent中I/O事件和Signal以及Timer事件的 ...

  6. libevent源码深度剖析七

    libevent源码深度剖析七 ——事件主循环 张亮 现在我们已经初步了解了libevent的Reactor组件——event_base和事件管理框架,接下来就是libevent事件处理的中心部分 — ...

  7. libevent源码深度剖析六

    libevent源码深度剖析六 ——初见事件处理框架 张亮 前面已经对libevent的事件处理框架和event结构体做了描述,现在是时候剖析libevent对事件的详细处理流程了,本节将分析 lib ...

  8. libevent源码深度剖析五

    libevent源码深度剖析五 ——libevent的核心:事件event 张亮 对事件处理流程有了高层的认识后,本节将详细介绍libevent的核心结构event,以及libevent对event的 ...

  9. libevent源码深度剖析四

    libevent源码深度剖析四 ——libevent源代码文件组织 1 前言 详细分析源代码之前,如果能对其代码文件的基本结构有个大概的认识和分类,对于代码的分析将是大有裨益的.本节内容不多,我想并不 ...

随机推荐

  1. SEH:结构化异常处理 学习

    SEH:结构化异常处理 结构化异常处理机制提供了一个操作系统,用于优化结构的方案,为客户提供更强大的程序执行环境.试想一下,你写程序不用考虑内存访问错误,那里是空指针错误,一直在按照程序的逻辑结构来写 ...

  2. Metasploit的基本使用

    Metasploit可以在Linux.Windows和Mac OS X系统上运行.我假设你已安装了Metasploit,或者你使用的系统是Kali Linux.它有命令行接口也有GUI接口. 我使用的 ...

  3. 使用WPScan破解wordpress站点密码

    我这里使用的Kali Linux,它默认安装了WPScan. 在使用WPScan之前,先更新它的漏洞数据库: # wpscan –update 扫描wordpress用户 wpscan -–url [ ...

  4. windows10 配置apache+php+mysql

    apache配置就是个坑!!! 参考win10环境下配置win10Apache+PHP+MySQL环境的方法 注意:把所有"C:/apache2/..."都变为自己的apache目 ...

  5. 【前端】HTML入门笔记

    教程 HTML 指的是超文本标记语言 (Hyper Text Markup Language).使用标记标签来描述网页 HTML 提示:使用小写标签\属性\属性参考手册\HTML颜色\HTML颜色名 ...

  6. NOIP模拟题 序列

    题目大意 给定长为$n$的序列$A$,定义长为$k$的区间中位数为从小到大排完序后第$\lfloor\frac{k}{2}\rfloor$个数的大小. 每次询问给定$l_1,r_1,l_2,r_2$有 ...

  7. 【1】基于quartz框架和Zookeeper实现集群化定时任务系统

    (1)quartz本身可以支持集群化,是基于数据库做协调,现在构想基于zookeeper做协调实现集群化定时系统 流程图如下:

  8. rest_framework使用完之后的简单总结

    首先先大致概括一下使用流程,因为还不是对这个框架很熟悉(其实有很多知识可以对比formModel的) 其实还是遵循django的MTV的模式,还是得从url开始 1.rest_framework有一个 ...

  9. bzoj 5093 [Lydsy1711月赛]图的价值——第二类斯特林数

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=5093 不要见到组合数就拆! 枚举每个点的度数,则答案为 \( n*\sum\limits_{ ...

  10. 关于android api 23 +的权限问题

    Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, n ...