hrtimer 首先要实现的功能就是要克服 timer wheel 的缺点:低精度以及与内核其他模块的高耦合性。在正式介绍 hrtimer 之前,有必要先介绍几个常用的基本概念:
时钟源设备(clock-source device)
系统中可以提供一定精度的计时设备都可以作为时钟源设备。如 TSC,HPET,ACPI PM-Timer,PIT 等。但是不同的时钟源提供的时钟精度是不一样的。像 TSC,HPET 等时钟源既支持高精度模式(high-resolution mode)也支持低精度模式(low-resolution mode),而 PIT 只能支持低精度模式。此外,时钟源的计时都是单调递增的(monotonically),如果时钟源的计时出现翻转(即返回到 0 值),很容易造成计时错误, 内核的一个 patch(commit id: ff69f2)就是处理这类问题的一个很好示例。时钟源作为系统时钟的提供者,在可靠并且可用的前提下精度越高越好。在 Linux 中不同的时钟源有不同的 rating,具有更高 rating 的时钟源会优先被系统使用。如图 2 所示:
表 1. 时钟源中 rating 的定义
1 ~ 99
100 ~ 199
200 ~ 299
300 ~ 399
400 ~ 499
非常差的时钟源,只能作为最后的选择。如 jiffies
基本可以使用但并非理想的时钟源。如 PIT
正确可用的时钟源。如 ACPI PM Timer,HPET
快速并且精确的时钟源。如 TSC
理想时钟源。如 kvm_clock,xen_clock
时钟事件设备(clock-event device)
系统中可以触发 one-shot(单次)或者周期性中断的设备都可以作为时钟事件设备。如 HPET,CPU Local APIC Timer 等。HPET 比较特别,它既可以做时钟源设备也可以做时钟事件设备。时钟事件设备的类型分为全局和 per-CPU 两种类型。全局的时钟事件设备虽然附属于某一个特定的 CPU 上,但是完成的是系统相关的工作,例如完成系统的 tick 更新;per-CPU 的时钟事件设备主要完成 Local CPU 上的一些功能,例如对在当前 CPU 上运行进程的时间统计,profile,设置 Local CPU 上的下一次事件中断等。和时钟源设备的实现类似,时钟事件设备也通过 rating 来区分优先级关系。
tick device
Tick device 用来处理周期性的 tick event。Tick device 其实是时钟事件设备的一个 wrapper,因此 tick device 也有 one-shot 和周期性这两种中断触发模式。每注册一个时钟事件设备,这个设备会自动被注册为一个 tick device。全局的 tick device 用来更新诸如 jiffies 这样的全局信息,per-CPU 的 tick device 则用来更新每个 CPU 相关的特定信息。
broadcast
CPU 的 C-STATECPU 在空闲时会根据空闲时间的长短选择进入不同的睡眠级别,称为 C-STATE。C0 为正常运行状态,C1 到 C7 为睡眠状态,数值越大,睡眠程度越深,也就越省电。CPU 空闲越久,进入睡眠的级别越高,但是唤醒所需的时间也越长。唤醒也是需要消耗能源的,因此,只有选择合适的睡眠级别才能确保节能的最大化。
Broadcast 的出现是为了应对这样一种情况:假定 CPU 使用 Local APIC Timer 作为 per-CPU 的 tick device,但是某些特定的 CPU(如 Intel 的 Westmere 之前的 CPU)在进入 C3+ 的状态时 Local APIC Timer 也会同时停止工作,进入睡眠状态。在这种情形下 broadcast 可以替代 Local APIC Timer 继续完成统计进程的执行时间等有关操作。本质上 broadcast 是发送一个 IPI(Inter-processor interrupt)中断给其他所有的 CPU,当目标 CPU 收到这个 IPI 中断后就会调用原先 Local APIC Timer 正常工作时的中断处理函数,从而实现了同样的功能。目前主要在 x86 以及 MIPS 下会用到 broadcast 功能。
Timekeeping & GTOD (Generic Time-of-Day)
Timekeeping(可以理解为时间测量或者计时)是内核时间管理的一个核心组成部分。没有 Timekeeping,就无法更新系统时间,维持系统“心跳”。GTOD 是一个通用的框架,用来实现诸如设置系统时间 gettimeofday 或者修改系统时间 settimeofday 等工作。为了实现以上功能,Linux 实现了多种与时间相关但用于不同目的的数据结构。
struct timespec {
__kernel_time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
timespec 精度是纳秒。它用来保存从 00:00:00 GMT, 1 January 1970 开始经过的时间。内核使用全局变量 xtime 来记录这一信息,这就是通常所说的“Wall Time”或者“Real Time”。与此对应的是“System Time”。System Time 是一个单调递增的时间,每次系统启动时从 0 开始计时。
struct timeval {
__kernel_time_t tv_sec; /* seconds */
__kernel_SUSEconds_t tv_usec; /* microseconds */
};
timeval 精度是微秒。timeval 主要用来指定一段时间间隔。
union ktime {
s64 tv64;
#if BITS_PER_LONG != 64 && !defined(CONFIG_KTIME_SCALAR)
struct {
# ifdef __BIG_ENDIAN
s32 sec, nsec;
# else
s32 nsec, sec;
# endif
} tv;
#endif
};