80x86体系上,内核必须显式与几种时钟、定时器打交道。时钟电路:跟踪当前时间,产生精确的度量。定时器电路由内核编程,所以以固定的、预先定义的频率发出中断。
1.实时时钟(RTC):所有PC都有一个,它独立于CPU及其它芯片,断电仍运行,靠小电池供电。RTC可以以2~8192Hz的频率在IRQ8上发周期性中断。Linux只用RTC获取当前时间、日期。
2.时间戳计数器(TSC):80x86CPU都有一个计数器(硬件)。在CPU的CLK接收到外部晶振振荡时,计数器加1。这个值存放在64位的寄存器中,汇编指令rdtsc读该寄存器。读取后若想换成需要的时间值,得考虑时钟信号的频率。
3.可编程间隔定时器(PIT):它一般是个8254CMOS芯片。它永远以固定频率(大约1000Hz)向IRQ0发中断。这个间隔叫一个“节拍”(tick)。tick是OS的指挥棒(注意是OS,不是CPU,CPU是外部时钟晶振)。tick越短,分辨率越高,但CPU在内核态花时间多,这个频率取决于硬件。PIT由setup_pit_timer()初始化。所谓初始化,无非是对8254芯片内寄存器赋初值。现在考虑2中,OS如何知道CPU频率?只要PIT定个5ms,测其中tsc的信号个数,即可算出。系统初始化时也确实这么做的。
4.CPU本地定时器:最近的80x86的APIC中,类似于3的PIC。但它基于总线时钟,而PIT独立。
5.HPET:高精度事件定时器。由Intel与Microsoft联合开发。下一代可能用它取代8254的PIT。
6.ACPI电源管理定时器:包含在几乎所有的基于ACPI的主板上。3.58M固定频率。实际上它就是简单的计数器,一节拍加1 。内核要靠询问某I/O端口来读计数器。此端口在BIOS在初始化时确定。
以上是常见的硬件电路。Linux要利用它们来完成相关的定时、周期性的操作。Linux的计时体系结构(timekeeping architecture)是一组与时间流相关的内核数据结构和函数。单处理器,多处理器有很大不同,此处只谈单处理器。单处理器上,计时活动都是由全局定时器(PIT或HPET)产生的中断触发。
定时器体系机构的数据结构首先是“定时器对象”,由定时器名称和4个标准方法组成,此对象pet_offset可获得到上一节拍的时间(微秒),叫“定时插补”。cur_timer指出系统可用的定时器对象中最好的。“好”的定义:HPET>ACPI>TSC>PIT。第二个重要的变量是jiffies变量,它记录自系统启动以来产生的节拍总数。32位的话50天会回绕,但有相应的宏处理,所以不用担心。80x86的jiffies作一个64位的低32位。这个64位即jiffies_64。第三个变量是xtime。其中的tv_sec存1970年来的秒数。tv_nsec存放上一秒以来的纳秒。
在单处理器系统中,所有的活动都IRQ0上的PIT触发中断的,初始化时,先从RTC中初始化xtime。确定是用HPET还是PIT发IRQ0。随后选出“最好的”定时器资源让cur_timer指向它,接着,创建IRQ0的中断门。这里要分清,内核选什么做IRQ0外设,选什么测时间。在中断程序中,用测量对象的make_offset计算offset,offset的计算放在中断开头最准。若最好的对象是PIT,那么offset为空,因为它没有其他电路提供比IRQ0更精准的偏移。这里要注意它的计算要考虑到关中断引起的节拍丢失。在中断处理程序中,随后做的是jiffies_**更新、xtime的更新、CPU相关统计资源的更新等。其中,有一重要的步骤是激活软中断。
至此,可以理解软定时器的实现就是将jiffies加上合适的节拍,当内核检查到真正的jiffies的值和这它一致,说明软定时到时。但因为定时器函数到期否由可延迟函数检查,所以可能有几百毫秒的误差。软定时器的一种叫动态定时器,供内核使用。动态的含义是指定时器的相关数据结构被一个链表串起,方便维护。另一种叫间隔定时器,它可由用户态调用。有时,内核要一个几百毫秒的小间隔时不采用定时器实现,而用延迟函数。延迟函数由定时器对象的delay方法实现。
版权声明:本文为博主原创文章,未经博主允许不得转载。