时间模块
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 没有设置该标志)