(十一)定时器和时间管理
系统定时器和时钟中断处理程序是Linux系统内核管理机制中的中枢。
内核中的时间概念
内核必须在硬件的帮助下才能计算和管理时间。
系统定时器以某种频率自行触发时钟中断,该频率可以通过编程预定,称作节拍率,当时钟中断发生时,内核就通过一种特殊的中断处理程序对其进行处理。
因为预编的节拍率对内核来说是可知的,所以内核知道连续两次时钟中断的间隔时间。内核就是靠这种已知的时钟中断间隔来计算墙上时间(实际时间)和系统运行时间的。
内核通过控制时钟中断维护实际时间。
节拍率:HZ
系统定时器频率(节拍率)是通过静态预处理定义的,也就是HZ(赫兹),在系统启动时按照HZ值对硬件进行设置。体系结构不同,HZ的值也不同。
系统定时器使用高频率 or 低频率?
高HZ的优势:
内核定时器能够以更高的频度和更高的准确度运行。
依赖定时值执行的系统调用,比如poll()和select(),能够以更高的精度运行。
对诸如资源消耗和系统运行时间等的测量会有更精细的解析度。
提高进程抢占的准确度。
高HZ的劣势:
节拍率越高,意味着时钟中断频率越高,也就意味着系统负担越重。因为处理器必须花时间来执行时钟中断处理程序,所以节拍率越高,中断处理程序占用的处理器的时间越多。这样不但减少了处理器处理其他工作的时间,而且还会更频繁地打乱处理器高速缓存并增加耗电。
jiffies
全局变量jiffies用来记录自系统启动以来产生的节拍的总数。启动时,内核将该变量初始化为0,此后,每次时钟中断处理程序就会增加该变量的值。因为一秒内时钟中断的次数等于HZ,所以jiffies一秒内增加的值也就为HZ。系统运行时间以秒为单位计算,就等于jiffies/HZ。实际出现的情况可能稍微复杂些:内核给jiffies赋一个特殊的初值,引起这个变量不断地溢出,由此捕捉bug。当找到实际的jiffies值后,首先把这个“偏差”减去。
硬时钟和定时器
实时时钟(RTC)是用来持久存放系统时间的设备,即便系统关闭后,它也可以靠主板上的微型电池提供的电力保持系统的计时。
当系统启动时,内核通过读取RTC来初始化墙上时间,该时间存放在xtime变量中。实时时钟最主要的作用仍是在启动时初始化xtime变量。
系统定时器提供一种周期性触发中断机制。
时钟中断处理程序
时钟中断处理程序可以划分为两个部分:体系结构相关部分和体系结构无关部分。
与体系结构相关的例程作为系统定时器的中断处理程序而注册到内核中,以便在产生时钟中断时,它能够相应地运行。与体系结构无关的例程在中断服务程序中被调用。
定时器
定时器(动态定时器或内核定时器)是管理内核流逝的时间的基础。内核定时器能够使工作在指定时间点上执行——不长不短,正好在希望的时间点上。
定时器并不周期运行,它在超时后就自行撤销,这也正是这种定时器被称为动态定时器的一个原因;动态定时器不断地创建和撤销,而且它的运行次数也不受限制。定时器在内核中应用得非常普遍。
当前节拍计数等于或大于指定的超时时,内核就开始执行定时器处理函数。虽然内核可以保证不会在超时时间到期前运行定时器处理函数,但是有可能延误定时器的执行。一般来说,定时器都会在超时后马上就会执行,但是也有可能推迟到下一次时钟节拍时才能运行,所以不能用定时器来实现任何硬实时任务。
延迟执行
内核代码(尤其是驱动程序)除了使用定时器或下半部机制以外,还需要其他方法来推迟执行任务。这种推迟通常发生在等待硬件完成某些工作时,而且等待的时间往往非常短,比如重新设置网卡的以太模式需要花费2ms,所以在设定网卡速度后,驱动程序必须至少等待2ms才能继续运行。
忙等待:最简单的延迟方法,该方法仅在想要延迟的时间是节拍的整数倍或者精确度要求不高时才可以使用。
实现方式:在循环中不断旋转直到希望的时钟节拍数耗尽。
(几乎不会使用)
短延迟:有时内核代码不但需要很短暂的延迟(比时钟节拍还短),而且还要求延迟的时间很精确。这种情况多发生在和硬件同步时,不能使用基于jiffies的延迟方法。Linux内核提供了三个延迟函数用来处理短延迟,依靠执行数次循环达到延迟效果。
schedule_timeout():该方法会让需要延迟执行的任务睡眠到指定的延迟时间耗尽后再重新运行。但该方法也不能保证睡眠时间正好等于指定的延迟时间,只能尽量使睡眠时间接近指定的延迟时间。当指定的时间到期后,内核唤醒被延迟的任务并将其重新放回运行队列。