RTC
导言
典型的操作系统会使用APIC或PIT进行计时。 但是,RTC的工作原理也一样。 RTC代表实时时钟-Real Time Clock。 它是使你的计算机时钟保持更新的芯片。 芯片内也有64字节的 CMOS RAM。
如果你只是想从RTC中读取日期/时间的信息,请参阅 CMOS 文章。 本文其余部分介绍了RTC中断的使用。
能力
RTC能够支持多个频率。 将基频预编程为32.768 kHz。(译者注:因为32,768等于2的15次方,所以该频率很容易分频用于为人工计时) 可以更改此值,但这是唯一能保持适当计时的基频。 因此,强烈建议你不要更改基准频率。 该芯片还具有一个 “分频器” 寄存器,该寄存器将从基频生成其它频率。 输出(中断)分频器频率默认设置为1024 Hz的中断率。 如果需要1024 Hz以外的中断频率,理论上实时时钟可以产生2 Hz到32768 Hz之间的15个中断频率。 但是,在大多数计算机上,RTC中断频率不能高于8 kHz。
对RTC进行编程
默认情况下RTC中断已被禁用。 如果打开RTC中断,RTC将定期生成IRQ 8。
编程时应避免NMI和其它中断
编程RTC时,禁用NMI(不可屏蔽中断-non maskable interrupt)和其它中断是“极其必要的”。 这是因为如果发生中断,RTC可能会处于 “未定义” (不工作) 状态。 这通常不是什么大事,但是有两个问题: RTC从不由BIOS初始化,它由电池备份。 因此,即使是冷重启也可能不足以使RTC脱离未定义的状态! 请参阅NMI页面,了解有关禁用和启用它以及其影响的更多信息。
设置寄存器
用于RTC和 CMOS 的2个IO端口是0x70和0x71。 端口0x70用于指定索引或“寄存器号”,并禁用NMI。 端口0x71用于读取/写入该CMOS配置空间字节。 仅使用三个字节的CMOS RAM来控制RTC周期性中断功能。 它们被称为RTC状态寄存器A、B和C。 它们位于CMOS RAM中的偏移量0xA、0xB和0xC。 要将0x20写入状态寄存器A,你将执行以下操作:
disable_ints(); // 重要过程:不发生中断(执行CLI) outportb(0x70, 0x8A); //选择状态寄存器A,禁用NMI(通过设置0x80位) outportb(0x71, 0x20); // 写入CMOS/RTC RAM enable_ints(); //(执行STI)并根据需要重新启用NMI
CMOS RAM的其它字节用于RTC其它功能(或BIOS的其它此类服务)。
IRQ风险
由于 IRQ 编号为8,因此它在 PIC 中的优先级比具有较低编号的IRQ低。 在处理其它中断时(直到操作系统发送EOI和STI),操作系统将不会收到任何时钟信号。 任何依赖时钟节拍的IRQ处理程序都可能因此而失败,因为较高编号的IRQ不会抢占较低编号的IRQ。
打开IRQ 8
要打开周期性中断,只需执行以下操作:
disable_ints(); // 禁用中断 outportb(0x70, 0x8B); // 选择寄存器B,然后禁用NMI char prev=inportb(0x71); // 读取寄存器B的当前值 outportb(0x70, 0x8B); // 再次设置索引(读取会将索引重置到寄存器D) outportb(0x71, prev | 0x40); // 用0x40写入以前的值。这将打开寄存器B的位6 enable_ints();
这将以默认的1024 Hz频率打开IRQ。 确保在启用RTC IRQ之前安装IRQ处理程序。 中断几乎会立即发生。
更改中断频率
更改输出分频器会改变中断频率,而不会干扰RTC保持适当时间的能力。 寄存器A的低4位是除法器值。 默认值为0110b(十进制6)。 该设置必须是介于1到15之间的值。 值0禁用中断。 要计算新频率,请执行以下操作:
frequency = 32768 >> (rate-1);
“Rate”是除数设置。 如果rate选择为1或2,则RTC将出现问题并 “roll over”(为-1后为0和1),从而产生.81毫秒和3.91毫秒的中断,而不是61.0微秒或30.5微秒。 所以,你可以选择的最快rate是3。 这将产生8 kHz或122微秒的中断。 用如下方法更改rate:
rate &= 0x0F; // rate必须高于2且不超过15 disable_ints(); outportb(0x70, 0x8A); // 将索引设置为寄存器A,禁用NMI char prev=inportb(0x71); // 获取寄存器A的初始值 outportb(0x70, 0x8A); // 将索引重置为A outportb(0x71, (prev & 0xF0) | rate); // 只写我们的rate到A。注意,rate是底部的4位。 enable_ints();
中断和寄存器C
重要的是要知道,在IRQ 8时,状态寄存器C将包含一个位掩码,说明发生了哪个中断。 RTC能够产生周期性中断 (如本文所描述的),更新结束中断和警报中断。 如果你只是将RTC用作一个简单的定时器,这并不重要。 重要的是,如果在IRQ 8之后没有读取寄存器C,则中断将不会再次发生。 所以,即使你不在乎什么类型的中断 实际上,只需将此代码附加到IRQ处理程序的底部,以确保获得另一个中断。 如果你使用的是Bochs,还建议在初始化实时时钟之后读取状态寄存器C,以确保在初始化实时时钟之前(期间)挂起的任何实时时钟中断都得到确认(在Bochs 2.6.6上观察到,不这样做会导致实时时钟计时器在至少一种情况下在重启后不发送任何中断,请参见此论坛主题)。
outportb(0x70, 0x0C); //选择寄存器C inportb(0x71); // 扔掉内容就可以了
可能的用途
你可以只使用RTC而不使用PIT(在我看来,RTC更容易编程)。 我最喜欢的用法是将RTC用于我的主要内核时钟 (控制调度和全部),而使用PIT提供更准确的wait()等待功能, 它可以在微秒级的时间尺度上运行。
问题?
某些RTC定时器代码可能在某些真实机器上不起作用。 我只发现大概有两成的计算机无法正常工作。 观察到的问题是,定时器的tick大约每秒发生一次。 我不确定为什么会这样,我正在努力寻找解决方案。 我观察到的这台机器是一台2005左右的戴尔 (它也有USB引导问题...)