Linux核心几个重要跟时间有关的名词或变数,底下将介绍HZ、tick与jiffies。
1)HZ
Linux核心每隔固定周期会发出timer interrupt (IRQ 0),HZ是用来定义每一秒有几次timer
interrupts。举例来说,HZ为1000,代表每秒有1000次timer interrupts。
HZ可在编译核心时设定,如下所示(以核心版本2.6.20-15为例):
adrian@adrian-desktop:~$ cd /usr/src/linux
adrian@adrian-desktop:/usr/src/linux$ make menuconfig
Processor type and features ---> Timer frequency (250 HZ) --->
其中HZ可设定100、250、300或1000。以小弟的核心版本预设值为250。
定义位置在
./kernel/arch/arm/configs/sc8810-sp8810-trusted-abs-android_defconfig CONFIG_HZ=100
使用位置
kernel/arch/arm/include/asm
# define HZ CONFIG_HZ
然后系统就可以使用HZ了
小实验
观察/proc/interrupt的timer中断次数,并于一秒后再次观察其值。理论上,两者应该相差250左右。
adrian@adrian-desktop:~$ cat /proc/interrupts | grep timer && sleep 1
&& cat /proc/interrupts | grep timer
0: 9309306 IO-APIC-edge timer
0: 9309562 IO-APIC-edge timer
上面四个栏位分别为中断号码、CPU中断次数、PIC与装置名称。
要检查系统上HZ的值是什么,就执行命令
cat /boot/config-`uname -r` | grep '^CONFIG_HZ='
还可以直接更改文件os/linux/include/asm-cris/param.h
2)Tick
Tick是HZ的倒数,意即timer interrupt每发生一次中断的时间。如HZ为250时,tick为4毫秒(millisecond)。
3)jiffies
jiffies为Linux核心全局变量(32位元变数,unsigned long),它被用来纪录系统自开机以来,已经过多少的tick。每发生一次timer
interrupt,Jiffies变数会被加1。值得注意的是,Jiffies于系统开机时,并非初始化成零,而是被设为-300*HZ
(arch/i386/kernel/time.c),即代表系统于开机五分钟后,jiffies便会溢位。那溢位怎么办?事实上,Linux核心定义几个macro(timer_after、time_after_eq、time_before与time_before_eq),即便是溢位,也能藉由这几个macro正确地取得jiffies的内容。
另外,80x86架构定义一个与jiffies相关的变数jiffies_64 ,此变数64位元,要等到此变数溢位可能要好几百万年。因此要等到溢位这刻发生应该很难吧。
那如何经由jiffies_64取得jiffies资讯呢?事实上,jiffies被对应至jiffies_64最低的32位元。因此,经由jiffies_64可以完全不理会溢位的问题便能取得jiffies。
HZ的设定:
#make menuconfig
processor type and features--->Timer frequency (250 HZ)--->
HZ的不同值会影响timer (节拍)中断的频率
例子
内核中一般不会直接使用人脑理解的时间单位s来管理时间,下面使用jiffies HZ的例子
1.本 例的目的是从当前时间起,如果在0.5秒内执行完do_somework(),则调用no_timeout_handler()。
如果在0.5秒后执行完 do_somework(),则调用timeout_handler()
unsigned long timeout = jiffies + HZ/2; /* timeout in 0.5s */
/* do some work ... */
do_somework();
/* then see whether we took too long */
if (timeout > jiffies) {
/* we did not time out, call no_timeout_handler() ... */
no_timeout_handler();
} else {
/* we timed out, call timeout_handler() ... */
timeout_handler();
}
2./kernel/kernel/power/wakelock.c
mod_timer(&expire_timer, jiffies + expire_in);
//从执行到此段代码算起,此定时器超时的时间是expire_in/HZ s
/kernel/drivers/watchdog/powerkey_wdt.c
int powerkey_wdt_start(void)
{
printk(KERN_INFO "%s(): %d\n", __func__, powerkey_wdt_enable);
/* trigger a watchdog.
* if has triggered, return directly, otherwise, watchdog reboot will denied by repeat click */
if (powerkey_wdt_enable && !powerkey_wdt_triggered) {
powerkey_wdt_triggered = 1;
mod_timer(&wdt_verify_timer, jiffies + (powerkey_wdt_click * HZ / 2));
// (powerkey_wdt_click * HZ / 2)/HZ 即powerkey_wdt_clic/2 后定时器wdt_verify_timer超时
mod_timer(&wdt_watchdog_timer, jiffies + (powerkey_wdt_margin * HZ));
// (powerkey_wdt_margin * HZ)/HZ 即powerkey_wdt_margin 后定时器wdt_watchdog_timer超时
}
return 0;
}
static int powerkey_wdt_margin = 3; /* seconds */
static int powerkey_wdt_click = 1; /* 0.5 seconds */
时钟和定时器对Linux内核来说十分重要。首先内核要管理系统的运行时间(uptime)和当前墙上时间(wall time), 即当前实际时间。其次,内核中大量的活动由时间驱动(time driven)。其中一些活动是周期性的,比如调度调度器(scheduler)中的运行队列(runqueue)或者刷新屏幕这样的活动,它们以固有的 频率定时发生;同时,内核要非周期性地调度某些函数在未来某个时间发生,比如推迟执行的磁盘I/O操作等。
实时时钟(RTC)
---------------------------------------------------------
内核必须借助硬件来实现时间管理。实时时钟(real time clock)是用来持久存放系统时间的设备,它与CMOS集成在一起,并通过主板电池供电,所以即便在关闭计算机系统之后,实时时钟仍然能继续工作。
系统启动时,内核读取实时时钟,将所读的时间存放在变量xtime中作为墙上时间(wall time),xtime保存着从1970年1月1日0:00到当前时刻所经历的秒数。虽然在Intel x86机器上,内核会周期性地将当前时间存回实时时钟中,但应该明确,实时时钟的主要作用就是在启动时初始化墙上时间xtime。
系统定时器与动态定时器
---------------------------------------------------------
周期性发生的事件都是由系统定时器(system timer)驱动。在X86体系结构上,系统定时器通常是一种可编程硬件芯片(如8254 CMOS芯片),又称可编程间隔定时器(PIT, Programmable Interval Timer),其产生的中断就是时钟中断(timer interrupt)。时钟中断对应的处理程序负责更新系统时间和执行周期性运行的任务。系统定时器的频率称为节拍率(tick rate),在内核中表示为HZ。
以X86为例,在2.4之前的内核中其大小为100; 从内核2.6开始,HZ = 1000, 也就是说每秒时钟中断发生1000次。这一变化使得系统定时器的精度(resolution)由10ms提高到1ms,这大大提高了系统对于时间驱动事件 调度的精确性。过于频繁的时钟中断不可避免地增加了系统开销(overhead),但是总的来说,在现在计算机系统上,HZ = 1000不会导致难以接受的系统开销。
与系统定时器相对的是动态定时器(dynamic timer),它是调度事件(执行调度程序)在未来某个时刻发生的时机。内核可以动态地创建或销毁动态定时器。
系统定时器及其中断处理程序是内核管理机制的中枢,下面是一些利用系统定时器周期执行的工作(中断处理程序所做的工作):
(1) 更新系统运行时间(uptime)
(2) 更新当前墙上时间(wall time)
(3) 在对称多处理器系统(SMP)上,均衡调度各处理器上的运行队列
(4) 检查当前进程是否用完了时间片(time slice),如果用尽,则进行重新调度
(5) 运行超时的动态定时器
(6) 更新资源耗尽和处理器时间的统计值
内核动态定时器依赖于系统时钟中断,因为只有在系统时钟中断发生后内核才会去检查当前是否有超时的动态定时器。
X86体系结构中时钟资源还包括CPU本地APIC(local Advanced Programmable Interrupt Controller)中的定时器和时间戳计时器TSC(Time Stamp Counter)。高精度定时器将使用CPU本地APIC作为高精度定时中断源。
高精度定时器的设计与实现
---------------------------------------------------------
X86体系结构中,内核2.6.X的HZ = 1000, 即系统时钟中断执行粒度为1ms,这意味着系统中周期事情最快为1ms执行一次,而不可能有更高的精度。动态定时器随时都可能超时,但由于只有在系统时钟 中断到来时内核才会检查执行超时的动态定时器,所以动态定时器的平均误差大约为半个系统时钟周期(即0.5ms).
对于实时要求较高的电信应用来说,普通Linux在实时性方面与电信平台的要求之间还存在一定的差距。CGL为了增强Linux的软实时能力,在以下方面 对内核进行了改进:提供高精度的动态定时器;提供可抢占式内核(preemption kernel)等。下面主要介绍高精度实时器的设计思想及实现,该实现遵循PISIX 1003.1b中时钟和定时器相关API标准,方便应用程序开发人员的使用。
高精度定时器的基本设计思想为:用 (jiffies+sub_jiffie)表示动态定时器的超时时间,PIT仍然按频率HZ = 1000产生系统时钟中断。如果在一个时钟中断tick与下一个时钟中断(tick+1)之间,即[jiffies, jiffies+1)之间,有高精度动态定时器等待处理(超时时间表示为(jiffies+sub_jiffie), sub_jiffie < 1), 那么用最近的动态定时器超时值sub_jiffie对硬件定时器(PIT或local APIC)进行设定,使其在时刻(jiffies+sub_jiffie)产生中断,通知内核对该高精度定时器进行处理。而不必总是等到系统时钟中断到来 后才检查执行所有超时的定时器,从而达到提高动态定时器精度的目的。
高精度定时器的中断源
---------------------------------------------------------
高精度定时器在内核中,仍然使用可编程间隔定时器PIC产生每秒HZ次的系统时钟中断,对于采用哪种硬件定时器产生高精度定时器中断则取决于CPU上是否 有本地APIC。若CPU上没有本地APIC,那么仍可使用PIT产生高精度定时中断,虽然这种然PIT即产生系统时钟中断又产生高精度定时器中断的做法 效率不高。
获取高精度定时器的发生时间
---------------------------------------------------------
高精度定时器发生在连续两个jiffies之间(即时刻(jiffies+sub_jiffie)),要确定其产生时间,就必须确定sub_jiffie 的大小。通过函数get_arch_cycles(ref_jiffies)可获取sub_jiffie的值,sub_jiffe以CPU时钟周期为最小 计时单位。函数具体实现思想是,通过访问计数器TSC,计算从上一个jiffies到当前时刻ref_jiffies之间的TSC差值,最终确定 sub_jiffies的大小。