前言
(由于之前的blog已经关闭了,所以将此文章迁移至这里,并非转载)
之前已经大体的写过APIC的一些内容,这次是写一些APIC定时器的内容,当然,也是翻译了一些来自OSDev的资料(不要问我为什么不翻译Intel手册,其实都一样,Intel手册里面写的太长了,有时候不一定要把所有东西看完才能使用里面的内容)。如果喜欢看原文,可以点APIC timer – OSDev Wiki.链接。有任何疑问,可以e-mail联系,xuezhe.liu@hotmail.com。(其实msn也行)
使用APIC Timer的最大好处是每个cpu内核都有一个定时器(避免了定时器资源的抢占问题,你要是抢走了一个cpu核心,你就肯定抢走了一个定时器),这里的cpu内核是指和核心数,把超线程技术的那个内核也算进去了。相反的我们之前使用的PIT(Programmable Interval Timer)就不这样,PIT是共用的一个。因为这样,我们不需要考虑任何资源管理问题,我们能够很轻松的使用它(老外说话有时候很..)。但是APIC有一个比较麻烦的是,他的精度和CPU的频率有关(这里的频率指的是CPU的外频,现在主流的就是200MHz,但是有点飘,这个我在最后解释)。而PIT使用的是一个标准的频率(1193182Hz)。如果要使用APICTimer,那么你必须自己知道每秒钟能触发多少个中断。
APIC Timer的模式
APIC定时器一般包含2-3种定时器模式,前两种(周期触发periodic和一次性触发one-shot)一般被现在所有的Local APIC所支持。但是第三种(TSC-Deadline模式)目前只在最新的CPU里面支持(话说我手上拿的CPU貌似都支持)。
周期触发模式(Periodic Mode)
周期触发模式中,程序设置一个”初始计数“寄存器(Initial Count),同时Local APIC会将这个数复制到”当前计数“寄存器(Current Count)。Local APIC会将这个数(当前计数)递减,直到减到0为止,这时候将触发一个IRQ(可以理解为触发一次中断),与此同时将当前计数恢复到初始计数寄存器的值,然后周而复始的执行上述逻辑。可以使用这种方法通过Local APIC实现定时按照一定时间间隔触发中断的功能。”当前计数“寄存器中的值的递减速度依赖于当前CPU的主频除以一个叫做”分频寄存器“(“Divide Configuration Register”)的值(换个思路来说就是,多少个tick减少1)。
举例来说,对于一个2.4GHz的CPU使用的CPU总线频率是800MHz(大家说的外频800MHz),”分频寄存器“(“Divide Configuration Register”)设置的是“除四”(”divide by 4“),并且初始计数(initial count)设置到123456。那么结果就是当前计数(current count)每200M分之1秒减1,大约617.28us触发一个irq(一次中断),也就是1620.01Hz。
一次性触发模式(One-Shot Mode)
对于一次性触发模式,Local APIC中的“当前计数”寄存器的减少方式和周期触发模式一样,也是当“当前计数“寄存器的值到0的时候触发一次定时器IRQ(中断)。但是它不会自动恢复到初始计数。这样,软件必须每次都要写“初始计数”寄存器一个值来让其再一次的计时并触发IRQ。这种模式的有点事,软件可以精确地控制定时器的IRQ的发生时间。例如,系统可以根据进程的优先级来设置任务切换(一些任务使用较短的CPU时间,一些任务使用较长的CPU时间),并且不会产生任何不必要的IRQs(这句话我也不太清楚什么意思,不过大约就是可以精确地控制切换进程的时间到时IRQ产生,因为进程切换也耽误时间)。一些系统通过为计数器付值的方法去实现通用的高精度计时器服务。换个例子来说就是,有3个任务需要分别在1234ns、333ns、4444ns的时候去做,这样的话,就设定定时器显示333ns,到时中断发生时执行任务二,同时设定定时器901ns,到时中断发生时执行任务一,同时在设定定时器441111ns,定时后执行任务三(原文的英语的例子我是不理解为什么要写那么折腾了,我就简单的按上面举例了)。
缺点是,一次性触发可能会影响实时性(因为在设置的时候会耽误一些,导致的不确定性),并且还需要为了避免竞争,维护新的计数值和老的计数值。
TSC-Deadline Mode(不是我不翻译,我是真不会翻译)
TSC-Deadline模式同上述两种模式有很大的区别。触发方式发生了区别,上述两种方式是基于当前计数减少到0触发的,而TSC-Deadline模式是软件设置了一个”deadline“,然后当CPU的时间戳大于或等于”deadline“的时候触发一次IRQ。
尽管存在如上的差异,软件可能会将它用于替代一次性触发模式,相比于一次性触发模式,这样可以得到更高的精度(因为时间戳的生成时CPU的时钟,而不是CPU总线频率),并且它更容易处理资源竞争(最后这句话我真不太理解)。
使用APIC Timer
使能APIC Timer
首先,应该通过写MSR寄存器使能Local APIC硬件。
其次,配置一个用于定时器的中断,并且软件开启APIC。
最后,配置APIC Timer的中断向量以及操作模式。
具体操作方法,参考Inter开发手册Vol3A Chapter 9。
初始化步骤
这里使用的方式是使用CPU总线频率作为基准源, 有很多种方法能够完成这部分所讲的内容,并且每种方法都不一样。例如:Real Time Clock,TimeStamp Counter,PIT or even polling CMOS registers。这里要讲的内容仍然要用到PIT,因为他很简单。按照如下步骤执行:
- 重置APIC到一个已知的状态
- 使能APIC Timer
- 重置APIC Timer的计数器
- 设置另外的一个已知时钟源
- 获取APIC TImer的计数器的当前值
- 以秒为基准校准一下
- 设置APIC Timer计数器的分频
- 设置接受APIC Timer中断触发
APIC Timer可以被设置为经过若干个tick以后触发一次中断,这里设置的地方叫做”分频数“(divide value)。这意味着可以通过将这一数值乘以APIC Timer的计数来获得当前CPU总线的频率。这