定时器和时间管理
系统定时器是一种可编程硬件芯片,它能以固定频率产生中断。该中断就是所谓的定时器中断,它所对应的中断处理程序负责更新系统时间,还负责执行需要周期性运行的任务。系统定时器和时钟中断处理程序是Linux系统内核管理机制中的中枢。
另外一个关注的焦点是动态定时器——一种用来推迟执行程序的工具。比如说,如果软驱马达在一定时间内都未活动,那么软盘驱动程序会使用动态定时器关闭软驱马达。内核可以动态创建或销毁动态定时器。
1.内核中的时间概念
系统定时器以某种频率自行触发时钟中断,该频率可以通过编程预定,称为节拍率(tick rate)。连续两次时钟中断的间隔时间称为节拍(tick),它等于节拍率分之一秒。
2.节拍率HZ
系统定时器频率(节拍率)是通过静态预处理定义的,也就是HZ,在系统启动时按照HZ值对硬件进行设置。体系结构不同,HZ的值也不同,其中 arm 是 100HZ,i386 是 1000HZ。
提高节拍率意味着时钟中断产生得更加频繁,所以中断处理程序也会更频繁地执行,如此一来会给整个系统带来如下好处:
1)内核定时器能够以更高的频度和更高的准确度运行。
2)依赖定时值执行的系统调用,比如 poll 和 select,能够以更高的精度运行。
3)对诸如资源消耗和系统运行时间等的测量会有更精细的解析度。
4)提高进程抢占的准确度。
提高节拍率也会产生负作用:节拍率越高,意味着时钟中断频率越高,也就意味着系统负担越重。
3.jiffies
全局变量 jiffies 用来记录自系统启动以来产生的节拍的总数。启动时,内核将该变量初始化为0,此后,每次时钟中断处理程序都会增加该变量的值。因为一秒内时钟中断的次数等于HZ,系统运行时间以秒为单位就等于 jiffies/HZ。
jiffies 变量总是无符号长整数(unsigned long),因此,在32位体系结构上是32位,在64位体系结构是64位,当 jiffies 的值超过它的最大存放范围后就会发生溢出,它的值会回绕到0。
内核提供了四个宏来帮助比较节拍计数,它们能正确地处理节拍计数回绕情况。这些宏定义在文件<linux/jiffies.h>中:
#define time_after( unknown, known ) ((long)(known) - (long)(unknown) < 0) #define time_before( unknown, known ) ((long)(unknown) - (long)(known) < 0) #define time_after_eq( unknown, known ) ((long)(unknown) - (long)(known) >= 0) #define time_before_eq( unknown, known ) ((long)(known) - (long)(unknown) >= 0)
其中 unknown 参数通常是 jiffies,known 参数是需要对比的值。
4.硬时钟和定时器
实时时钟(RTC)是用来持久存放系统时间的设备,即使系统关闭后,也可以依靠主板上的微型电池提供的电力保持系统的计时。当系统启动时,内核通过读取RTC来初始化墙上时间,该时间存放在 xtime 变量中。
系统定时器是内核定时机制中最为重要的角色,它提供一种周期性触发中断机制。
5.时钟中断处理程序
时钟中断处理程序可以划分为两个部分:体系结构相关部分和体系结构无关部分。与体系结构相关的例程作为系统定时器的中断处理程序而注册到内核中,以便在产生时钟中断时,它能够相应地运行。虽然处理程序的具体工作依赖于特定的体系结构,但是绝大多数处理程序最低限度都要执行如下工作:
1)获得 xtime_lock 锁,以便对访问 jiffies_64 和墙上时间 xtime 进行保护。
2)需要时应答或重新设置系统时钟。
3)周期性地使用墙上时间更新实时时钟。
4)调用体系结构无关的时钟例程:do_timer()。
中断服务程序主要通过调用与体系结构无关的例程 do_timer 执行下面的工作:
1)给 jiffies_64 变量增加 1 (这个操作即使是在 32 位体系结构上也是安全的,因为前面已经获得了 xtime_lock 锁)。
2)更新资源消耗的统计值,比如当前进程所消耗的系统时间和用户时间。
3)执行已经到期的动态定时器。
4)执行 scheduler_tick 函数。
5)更新墙上时间,该时间存放在 xtime 变量中。
6)计算平均负载值。