时间模块
Tor时间模块位于src/lib/time/文件夹
这里的时间是指一个递增的计时器,每次获得的会是一个增加量
关于墙上时钟(wall clock)和递增时间(monotonic time)的更多解释
子系统结构
const subsys_fns_t sys_time = { .name = "time", /* Monotonic time depends on logging, and a lot of other modules depend on * monotonic time. */ .level = -80, .supported = true, .initialize = subsys_time_initialize, };
模块类型
timeval
当系统不存在timeval时,会在这里重定义该类型
通常而言,该类型应该会定义于/usr/include/bits/types/struct_timeval.h
struct timeval { time_t tv_sec; unsigned int tv_usec; };
monotime_t
一般而言,该类型在模块外部不应该被修改,因为其结构定义的属性在不同环境都是不同的。也即对应的成员变量都是私有的
这里实际是重新封装了在不同平台上monotime的实现
typedef struct monotime_t { #ifdef __APPLE__ /* On apple, there is a 64-bit counter whose precision we must look up. */ uint64_t abstime_; #elif defined(HAVE_CLOCK_GETTIME) /* It sure would be nice to use clock_gettime(). Posix is a nice thing. */ struct timespec ts_; #elif defined (_WIN32) /* On Windows, there is a 64-bit counter whose precision we must look up. */ int64_t pcount_; #else #define MONOTIME_USING_GETTIMEOFDAY /* Otherwise, we will be stuck using gettimeofday. */ struct timeval tv_; #endif /* defined(__APPLE__) || ... */ } monotime_t;
monotime_coarse_t
和monotime_t相似,该类型也是重新的封装,但是相对于monotime_t,monotime_coarse_t的精度更低
#if defined(CLOCK_MONOTONIC_COARSE) && \ defined(HAVE_CLOCK_GETTIME) #define MONOTIME_COARSE_FN_IS_DIFFERENT #define monotime_coarse_t monotime_t #elif defined(_WIN32) #define MONOTIME_COARSE_FN_IS_DIFFERENT #define MONOTIME_COARSE_TYPE_IS_DIFFERENT /** Represents a coarse monotonic time in a platform-independent way. */ typedef struct monotime_coarse_t { uint64_t tick_count_; } monotime_coarse_t; #elif defined(__APPLE__) && defined(HAVE_MACH_APPROXIMATE_TIME) #define MONOTIME_COARSE_FN_IS_DIFFERENT #define monotime_coarse_t monotime_t #else #define monotime_coarse_t monotime_t #endif /* defined(CLOCK_MONOTONIC_COARSE) && ... || ... */
模块函数
该模块由于需要对不同平台进行不同的处理,因此大部分的代码都在使用各种宏定义来处理跨平台环境问题。这里单独选出一些代表性的函数为例
monotime_init
该函数用于初始化时间子系统
会首先初始化该平台相关内容,并将记录当前的monotime和monotime_coarse
/** * Initialize the timing subsystem. This function is idempotent. */ void monotime_init(void);
monotime_init_internal
初始化一些平台相关的内容
- 在 Apple 环境下,会使用
mach_timebase_info()初始化mach_time_info,并计算每毫秒、纳秒有多少 tick - 在支持
clock_gettime()函数的环境下,会判断是否支持monotime_coarse,如果不支持,则会将其退化成monotime - 在 Windows 环境下,会调用
kernel32.dll - 在其余环境下,会初始化互斥锁
 
static void monotime_init_internal(void)
monotime_get
- 在 Apple 环境下,会使用
mach_absolute_time()获取递增时间 - 在支持
clock_gettime()函数的环境下,会使用clock_gettime(CLOCK_MONOTONIC, &out->ts_)获取递增时间 - 在 Windows 环境下,会使用
QueryPerformanceCounter()及ratchet_performance_counter获取递增时间 - 在其余环境下,会使用
tor_gettimeofday()及ratchet_timeval()获取递增时间 
/** * Set <b>out</b> to the current time. */ void monotime_get(monotime_t *out);
Q/A
在src/lib/time/compat_time.h中,有一份Q/A文档:
- 应该什么时候使用
time模块?- 如果需要使用永远不会递减的时间,那么应该使用 monotonic time (递增时间);如果需要将一个时间发送给用户或别的进程,以及存储时间,则应该使用时钟模块(前面提到的 wallclock 模块)
 
 - 应该如何选择
monotime和monotime_coarse?- 一般而言,使用
monotime可以得到更高的精度;使用monotime_coarse可以得到更好的性能表现 
 - 一般而言,使用
 - 什么是 monotonic ?
- monotonic 本意是递增的,两个时间的时间差一定是正数(不可能是 0)
 - 但是,在某些情况下仍然可能得到时间差 0(这是与期望不符的行为)
- 在 Windows 系统下使用
monotime_coarse, - 在别的时间精度较低的系统上
 - 在一些使用时钟模块模拟递增时间的模块
 - 时间最小单元比纳秒大的系统上,导致除法计算时精度出问题
 
 - 在 Windows 系统下使用
 
 - 是否可以直接使用
monotime_t或monotime_coarse_t用于毫秒( msec )、微秒 ( usec ) 时间单元?- 使用
monotime_t和monotime_coarse_t会有更高的效率,但是可能会浪费更多的内存用于存储微秒、毫秒 - 在某些系统上转换微秒和毫秒通常会执行 64 位除法,在 32 位系统上这可能会出现问题
 - 时间戳单元类型用于方便执行
monotime_coarse的转换,并且保证在 32 位整数能保证 1 ~ 2 毫秒的精度,但是其缺点是该类型并不是一个自然存在的时间单位 - 由于粗略的递增时间只实现了毫秒级的精度,因此时间戳单元在用于微秒和毫秒时没有别的特别点
 
 - 使用
 monotime_coarse的后端实现是什么?- 一般而言,不同的实现中都是使用从只读内存分页读取当前时间来实现,由于内存页会和内核的 tick 以相同的频率更新,因而可以实现了获得单调时间不需要上下文切换
 - 在 Windows 系统中,
monotime_coarse使用GetCount64()(或已过时的GetTickCount())实现。MSDN 生成精度保证在 10 ~ 16 毫秒范围内。存储monotime_coarse_t共使用 8 字节 - 在 OSX/IOS 中,
monotime_coarse使用mach_approximate_time()获得,并返回标准的 mongotime ,该精度没有文档明确说明,但是其实现是开源的,可以得知其是从内核更新的内存页读取数据,并且存储monotime_coarse_t共使用 8 字节 - 在 Unix-like 系统中,
monotime_coarse使用clock_gettime()和CLOCK_MONOTONIC_COARSE实现,并且函数会返回CLOCK_MONOTONIC。该实现使用 vdso 技巧来读取内核更新页面,并且精度是固定的。但是让然可以使用clock_getres()。在一些 linux desktop 中,生成精度为 1 毫秒,但是这个值取决于系统频率设置。存储monotime_coarse_t将使用 16 字节 - TODO: 尝试 foobsd 的
CLOCK_MONOTONIC_FAST 
monotime的后端实现是什么?- 一般而言,标准的
monotime使用系统调用实现,在系统调用比较轻量的平台中,这么做效果很好,但是在系统调用繁琐的系统中则很吃亏 - 在 Windows 中,使用
QueryPerformanceCounter获取,并且存储monotime_t使用 8 字节 - 在 OSX/Apple 中,使用
mach_absolute_time获取,并且存储monotime_t使用 8 字节 - 在 Unix-like 系统中,使用
CLOCK_MONOTONIC获取,并且存储monotime_t使用 16 字节 
- 一般而言,标准的
 - 获取 64 位毫秒、微秒、纳秒花费如何?
- Windows/低精度:轻量,因为只是乘法运算
 - Windows/高精度:在 32 位系统花费较大,因为需要 64 位除法
 - Apple:在 32 位系统花费较大,因为需要 64 位除法
 - Unix-like:平均比较轻量,因为唯一的除法是将
tv_nsec除以1000,并且纳秒存储在 32 位值中 - 所有系统/时间戳:轻量,不使用除法
 
 - 在 libevent 能获得的精度如何?
- 事实上如果是为了计时超时行为,该问题很有意义
 - 在 Windows 系统中,理论上会有微秒级的精度,但是通常不会那么精确
 - 在 OSX、IOS、BSD 中,由于存在 kqueue ,理论上有纳秒级的精度,但是通常不会那么精确
 - 在 Linux 中,由于存在 epoll ,拥有毫秒级的精度。如果设置了 
EVENT_BASE_FLAG_PRECISE_TIMER,最近的一些 libevents 也可以使用timerfd来获得更高的精度( Tor 没有设置该标志) 
 


    中文博客导航
 萌ICP备20213456号