[转载]Linux的时间与时钟中断处理
本文主要介绍在Linux下的时间实现以及系统如何进行时钟中断处理。
一. Linux的硬件时间
PC机中的时间有三种硬件时钟实现,这三种都是基于晶振产生的方波信号输入。这三种时钟为:
- 实时时钟RTC ( Real Time Clock)
- 可编程间隔器PIT(Programmable Interval Timer )
- 时间戳计数器TSC(Time Stamp Clock)
实时时钟 RTC
用于长时间存放系统时间的设备,即时关机后也可依靠主板CMOS电池继续保持系统的计时,原理图如下:
Note: Linux与RTC的关系是,当Linux启动时从RTC读取时间和日期的基准值,然后在Kernel运行期间便抛开RTC,以软件的形式维护系统的时间日期,并在适当时机由Kernel将时间写回RTC Register.
1.1 RTC Register
(1). 时钟与日历Register
共10个,地址:0x00-0x09,分别用于保存时间日历的具体信息,详情如下:
00 Current Second for RTC
01 Alarm Second
02 Current Minute
03 Alarm Minute
04 Current Hour
05 Alarm Hour
06 Current Day of Week(1=Sunday)
07 Current Date of Month
08 Current Month
09 Current Year
(2).状态和控制Register
共四个,地址:0x0a-0x0d,控制RTC芯片的工作方式,并表示当前状态。
l 状态RegisterA , 0x0A 格式如下:
bit[7]——UIP标志(Update in Progress),为1表示RTC正在更新日历寄存器组中的值,此时日历寄存器组是不可访问的(此时访问它们将得到一个无意义的渐变值)。
bit[6:4]——这三位是用来定义RTC的操作频率。各种可能的值如下:
DV2 DV1 DV0
0 0 0 4.194304 MHZ
0 0 1 1.048576 MHZ
0 1 0 32.769 KHZ
1 1 0/1 任何
PC机通常设置成“010”。
bit[3:0]——速率选择位(Rate Selection bits),用于周期性或方波信号输出。
RS3 RS2 RS1 RS0 周期性中断 方波 周期性中断 方波
0 0 0 0 None None None None
0 0 0 1 30.517μs 32.768 KHZ 3.90625ms 256 HZ
0 0 1 0 61.035μs 16.384 KHZ
0 0 1 1 122.070μs 8.192KHZ
0 1 0 0 244.141μs 4.096KHZ
0 1 0 1 488.281μs 2.048KHZ
0 1 1 0 976.562μs 1.024KHZ
0 1 1 1 1.953125ms 512HZ
1 0 0 0 3.90625ms 256HZ
1 0 0 1 7.8125ms 128HZ
1 0 1 0 15.625ms 64HZ
1 0 1 1 31.25ms 32HZ
1 1 0 0 62.5ms 16HZ
1 1 0 1 125ms 8HZ
1 1 1 0 250ms 4HZ
1 1 1 1 500ms 2HZ
PC机BIOS对其默认的设置值是“0110”
l 状态Register B , 0x0B 格式如下:
bit[7]——SET标志。为1表示RTC的所有更新过程都将终止,用户程序随后马上对日历寄存器组中的值进行初始化设置。为0表示将允许更新过程继续。
bit[6]——PIE标志,周期性中断enable标志。
bit[5]——AIE标志,告警中断enable标志。
bit[4]——UIE标志,更新结束中断enable标志。
bit[3]——SQWE标志,方波信号enable标志。
bit[2]——DM标志,用来控制日历寄存器组的数据模式,0=BCD,1=BINARY。BIOS总是将它设置为0。
bit[1]——24/12标志,用来控制hour寄存器,0表示12小时制,1表示24小时制。PC机BIOS总是将它设置为1。
bit[0]——DSE标志。BIOS总是将它设置为0。
l 状态Register C,0x0C 格式如下:
bit[7]——IRQF标志,中断请求标志,当该位为1时,说明寄存器B中断请求 发生。
bit[6]——PF标志,周期性中断标志,为1表示发生周期性中断请求。
bit[5]——AF标志,告警中断标志,为1表示发生告警中断请求。
bit[4]——UF标志,更新结束中断标志,为1表示发生更新结束中断请求。
l 状态Register D,0x0D 格式如下:
bit[7]——VRT标志(Valid RAM and Time),为1表示OK,为0表示RTC 已经掉电。
bit[6:0]——总是为0,未定义。
2.可编程间隔定时器 PIT
每个PC机中都有一个PIT,以通过IRQ0产生周期性的时钟中断信号,作为系统定时器 system timer。当前使用最普遍的是Intel 8254 PIT芯片,它的I/O端口地址是0x40~0x43。
Intel 8254 PIT有3个计时通道,每个通道都有其不同的用途:
(1) 通道0用来负责更新系统时钟。每当一个时钟滴答过去时,它就会通过IRQ0向 系统 产生一次时钟中断。
(2) 通道1通常用于控制DMAC对RAM的刷新。
(3) 通道2被连接到PC机的扬声器,以产生方波信号。
每 个通道都有一个向下减小的计数器,8254 PIT的输入时钟信号的频率是1.193181MHZ,也即一秒钟输入1193181个clock-cycle。每输入一个clock-cycle其时间 通道的计数器就向下减1,一直减到0值。因此对于通道0而言,当他的计数器减到0时,PIT就向系统产生一次时钟中断,表示一个时钟滴答已经过去了。计数 器为16bit,因此所能表示的最大值是65536,一秒内发生的滴答数是:1193181/65536=18.206482.
PIT的I/O端口:
0x40 通道0 计数器 Read/Write
0X41 通道1计数器 Read/Write
0X42 通道2计数器 Read/Write
0X43 控制字 Write Only
Note: 因PIT I/O端口是8位,而PIT相应计数器是16位,因此必须对PIT计数器进行两次读写。
8254 PIT的控制寄存器(0X43)的格式如下:
bit[7:6] — 通道选择位:00 ,通道0;01,通道1;10,通道2;11,read-back command,仅8254。
bit[5:4] – Read/Write/Latch锁定位,00,锁定当前计数器以便读取计数值;01,只读高字节;10,只读低字节;11,先高后低。
bit[3:1] – 设定各通道的工作模式。
000 mode0 当通道处于count out 时产生中断信号,可用于系统定时
001 mode1 Hardware retriggerable one-shot
010 mode2 Rate Generator。产生实时时钟中断,通道0通常工作在这个模式下
011 mode3 方波信号发生器
100 mode4 Software triggered strobe
101 mode5 Hardware triggered strobe
时间戳计数器 TSC
从Pentium开始,所有的Intel 80x86 CPU就都包含一个64位的时间戳记数器(TSC)的寄存器。该寄存器实际上是一个不断增加的计数器,它在CPU的每个时钟信号到来时加1(也即每一个clock-cycle输入CPU时,该计数器的值就加1)。
汇编指令rdtsc可以用于读取TSC的值。利用CPU的TSC,操作系统通常可以得到更为精准的时间度量。假如clock-cycle的频率是400MHZ,那么TSC就将每2.5纳秒增加一次。
二. Linux时钟中断处理程序
1. 几个概念
(1)时钟周期(clock cycle)的频率:8253/8254 PIT的本质就是对由晶体振荡器产生的时钟周期进行计数,晶体振荡器在1秒时间内产生的时钟脉冲个数就是时钟周期的频率。Linux用宏 CLOCK_TICK_RATE来表示8254 PIT的输入时钟脉冲的频率(在PC机中这个值通常是1193180HZ),该宏定义在include/asm-i386/timex.h头文件中
define CLOCK_TICK_RATE 1193180 kernel=2.4 &2.6
(2)时钟滴答(clock tick):当PIT通道0的计数器减到0值时,它就在IRQ0上产生一次时钟中断,也即一次时钟滴答。PIT通道0的计数器的初始值决定了要过多少时钟周期才产生一次时钟中断,因此也就决定了一次时钟滴答的时间间隔长度。
(3)时钟滴答的频率(HZ):1秒时间内PIT所产生的时钟滴答次数。 这个值也由PIT通道0的计数器初值决定的.Linux内核用宏HZ来表示时钟滴答的频率,而且在不同的平台上HZ有不同的定义值。对于ALPHA和 IA62平台HZ的值是1024,对于SPARC、MIPS、ARM和i386等平台HZ的值都是100。该宏在i386平台上的定义如下 (include/asm-i386/param.h):
define HZ 100 kernel=2.4
define HZ CONFIG_HZ kernel=2.6
(4)宏LATCH:定义要写到PIT通道0的计数器中的值,它表示PIT将隔多少个时钟周期产生一次时钟中断。公式计算:
LATCH=(1秒之内的时钟周期个数)÷(1秒之内的时钟中断次数)=(CLOCK_TICK_RATE)÷(HZ)
定义在<include/linux/timex.h>
define LATCH ((CLOCK_TICK_RATE + HZ/2) / HZ)
(5)全局变量jiffies:用于记录系统自启动以来产生的滴答总数。启动时,kernel将该变量初始为0,每次时钟中断处理程序timer_interrupt()将该变量加1。因为一秒钟内增加的时钟中断次数等于Hz,所以jiffies一秒内增加的值也是Hz。由此可得系统运行时间是jiffies/Hz 秒。
jiffies定义于<linux/jiffies.h>中:
extern unsigned long volatile jiffies;
Note:在kernel 2.4,jiffies是32位无符号数;kernel 2.6,jiffies是64位无符号数。
(6)全局变量xtime: 结构类型变量,用于表示当前时间距UNIX基准时间1970-01-01 00:00:00的相对秒数值。当系统启动时,Kernel通过读取RTC Register中的数据来初始化系统时间(wall_time),该时间存放在xtime中。
void __init time_init (void) {
... ...
xtime.tv_sec = get_cmos_time ();
xtime.tv_usec = 0;
... ... }
Note:实时时钟RTC的最主要作用便是在系统启动时用来初始化xtime变量。
2.Linux的时钟中断处理程序
Linux下时钟中断处理由time_interrupt() 函数实现,主要完成以下任务:
l 获得xtime_lock锁,以便对访问的jiffies_64 (kernel2.6)和 xtime进行保护
l 需要时应答或重新设置系统时钟。
l 周期性的使用系统时间(wall_time)更新实时时钟RTC
l 调用体系结构无关的时钟例程:do_timer()。
do_timer()主要完成以下任务:
l 更新jiffies;
l 更新系统时间(wall_time),该时间存放在xtime变量中
l 执行已经到期的动态定时器
l 计算平均负载值
void do_timer(unsigned long ticks)
{
jiffies_64 += ticks;
update_process_times(user_mode(regs));
update_times (ticks);
}
static inline void update_times(unsigned long ticks)
{
update_wall_time ();
calc_load (ticks);
}
time_interrupt ():
static void timer_interrupt(int irq, void *dev_id, struct pt_regs *regs) {
int count;
write_lock (&xtime_lock); //获得xtime_lock锁
if(use_cyclone)
mark_timeoffset_cyclone();
else if (use_tsc) {
rdtscl(last_tsc_low); //读TSC register到last_tsc_low
spin_lock (&i8253_lock); //对自旋锁i8253_lock加锁,对8254PIT访问
outb_p (0x00, 0x43);
count = inb_p(0x40);
count |= inb(0x40) << 8;
if (count > LATCH) {
printk (KERN_WARNING "i8253 count too high! resetting../n");
outb_p (0x34, 0x43);
outb_p (LATCH & 0xff, 0x40);
outb(LATCH >> 8, 0x40);
count = LATCH - 1;
}
spin_unlock (&i8253_lock);
if (count = = LATCH) {
count- -;
}
count = ((LATCH-1) - count) * TICK_SIZE;
delay_at_last_interrupt = (count + LATCH/2) / LATCH;
} //end use_tsc
do_timer_interrupt (irq, NULL, regs);
write_unlock(&xtime_lock);
}//end time_interrupt
do_timer_interrupt():
static inline void do_timer_interrupt(int irq, void dev_id, struct pt_regs regs)
{
……
do_timer(regs);
if((time_status & STA_UNSYNC)= =0&&xtime.tv_sec> last_rtc_update + 660 && xtime.tv_usec >= 500000 - ((unsigned) tick) / 2 && xtime.tv_usec <= 500000 + ((unsigned) tick) / 2) {
if (set_rtc_mmss(xtime.tv_sec) == 0)
last_rtc_update = xtime.tv_sec;
else
last_rtc_update = xtime.tv_sec - 600;
……
}
do_timer_interrupt()主要完成:调用do_timer()和判断是否需要更新CMOS时钟。更新CMOS时钟的条件如下:三个须同时成立
1.系统全局时间状态变量time_status中没有设置STA_UNSYNC标志,即Linux没有设置外部同步时钟(如NTP)
2.自从上次CMOS时钟更新已经过去11分钟。全局变量last_rtc_update保存上次更新CMOS时钟的时间.
3.由于RTC存在Update Cycle,因此应在一秒钟间隔的中间500ms左右调用set_rtc_mmss()函数,将当前时间xtime.tv_sec写回RTC中。
Note. Linux kernel 中定义了一个类似jiffies的变量wall_jiffies,用于记录kernel上一次更新xtime时,jiffies的值。
Summary
Linux kernel在启动时,通过读取RTC里的时间日期初始化xtime,此后由kernel通过初始PIT来提供软时钟。
时钟中断处理过程可归纳为:系统时钟system timer在IRQ0上产生中断;kernel调用time_interrupt();time_interrupt()判断系统是否使用TSC,若使用 则读取TSC register;然后读取PIT 通道0的计数值;调用do_time_interrupt(),实现系统时间更新.
[转载]Linux的时间与时钟中断处理的更多相关文章
- Linux系统时间设置(转载)
Linux时钟分为系统时钟(System Clock)和硬件(Real Time Clock,简称RTC)时钟.系统时钟是指当前Linux Kernel中的时钟,而硬件时钟则是主板上由电池供电的时钟, ...
- linux下系统时间和时钟时间
linux中有关系统时间.时钟时间的命令: 1显示系统时间的命令 ># date 2显示时钟时间的命令 ># clock或hwclock 3系统时间与互联网同步的命令 ># ntpd ...
- linux新内核的时钟机制代码
http://blog.chinaunix.net/uid-22810130-id-384173.html 如果说cfs是linux的一个很有创意的机制的话,那么linux中另一个创意就是nohz,我 ...
- Linux系统时间与RTC时间【转】
http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=3637782 Linux的RTC驱动相对还是比较简单的,可以将它作为一个普通的字符 ...
- [转载]Linux进程调度原理
[转载]Linux进程调度原理 Linux进程调度原理 Linux进程调度的目标 1.高效性:高效意味着在相同的时间下要完成更多的任务.调度程序会被频繁的执行,所以调度程序要尽可能的高效: 2.加强交 ...
- linux 系统时间 硬件时间
Linux时钟分为系统时钟(System Clock)和硬件时钟(Real Time Clock,简称RTC).系统时钟是指当前Linux Kernel中的时钟:而硬件时钟则是主板上由电池供电的时钟, ...
- Linux系统时间同步方法
在Windwos中,系统时间的设置很简单,界面操作,通俗易懂,而且设置后,重启,关机都没关系.系统时间会自动保存在BIOS时钟里面,启动计算机的时候,系统会自动在BIOS里面取硬件时间,以保证时间的不 ...
- Linux 系统时间和硬件时间
linux 的系统时间有时跟硬件时间是不同步的 Linux时钟分为系统时钟(System Clock)和硬件(Real Time Clock,简称RTC)时钟.系统时钟是指当前Linux Kernel ...
- Linux_自动调整linux系统时间和时区与Internet时间同步
调整linux系统时间和时区与Internet时间同步 一.修改时区:# cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime修改为中国的东八区# v ...
随机推荐
- Graph Databases—The NOSQL Phenomenon阅读笔记
本章内容着重对了NOSQL和RDBMS(关系型数据库管理系统)的不同,以及其各自背后设计时考虑的因素.然后接下来,着重讲述了NOSQL的4种分类方法.下面我们将对重要知识点进行汇总. 1.We def ...
- 集成容联:Warning! ivar size mismatch in PSUICollectionView_ - can't change the superclass.解决办法
这个警报其实是无影响的. 解决方案: PSTCollectionView.m 的 char filler[200] 替换成char filler[300]
- Qt读取JSON和XML数据
QJSON JSON(JavaScript Object Notation)是一个轻量级的数据交换格式; 可以将数据以name/value的形式任意组合; QJson 是一个基于Qt的库, 将JSON ...
- CentOS6.x升级MySQL版本号5.1到5.6
有一些虚拟机.云主机提供商仍然使用的是老版本号的安装套件. 预装的应用软件版本号非常低. 比方 techbrood.com 使用的云server,当中MySQL预装版本号为老版本号5.1.x. 而最新 ...
- C# 数学运算符
运算符大致分为如下3类: 一元运算符,处理一个操作符 二元运算符,处理两个操作数 三元运算符,处理三个操作数 大多数运算符都是二元运算符,只有几个一元运算符和一个三元运算符,即条件运算符(条件运算符是 ...
- iis配置出现的问题及解决
唯一密钥属性“value”设置…无法添加类型为add 在配置IIS7.5时,会出现 在唯一密钥属性“value”设置为“default.aspx”(或者index.asp等)时,无法添加类型为“add ...
- 第一个Delphi小程序
第一次应工作需呀,接触这个语言,今晚在自己的电脑搭建好环境,写的第一个超简单的Delphi小程序! var temp:Integer; //求个位数 procedure TForm1.BitBtn1C ...
- myeclipse笔记(3):导入的项目切换jdk版本
有时候,从外面导入的javaweb项目会访问不了,这个时候改变jdk版本就是其中解决的方法之一. 右键点击项目 --> bulid path --> configure 选择需要 ...
- 关于sqfa
有的时候定义状态,下次来到这个状态的时候,不应该就否决掉下次,因为下次的权值可能比这次更优..
- C++中的struct与class继承方式
代码: #include <iostream> #include <cstdio> using namespace std; //class A{ struct A{ publ ...