时间模块

Tor笔记目录索引

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_tmonotime_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

该函数用于初始化时间子系统
会首先初始化该平台相关内容,并将记录当前的monotimemonotime_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 模块)
  • 应该如何选择monotimemonotime_coarse
    • 一般而言,使用monotime可以得到更高的精度;使用monotime_coarse可以得到更好的性能表现
  • 什么是 monotonic ?
    • monotonic 本意是递增的,两个时间的时间差一定是正数(不可能是 0
    • 但是,在某些情况下仍然可能得到时间差 0(这是与期望不符的行为)
      • 在 Windows 系统下使用monotime_coarse
      • 在别的时间精度较低的系统上
      • 在一些使用时钟模块模拟递增时间的模块
      • 时间最小单元比纳秒大的系统上,导致除法计算时精度出问题
  • 是否可以直接使用monotime_tmonotime_coarse_t用于毫秒( msec )、微秒 ( usec ) 时间单元?
    • 使用monotime_tmonotime_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 没有设置该标志)