最近工作中,同事问我,linux如何实现us的delay,认为:x86的hz定义为100,所以具体时间间隔应该是(1000ms/HZ)=10ms,然后调度的时候是以这个10ms为单位,实现不了微秒的delay。
关于这个问题,我们要首先了解linux的时钟:
时钟的硬件如下:
RTC(real time clock):
一种时钟芯片,pc掉电后,继续工作,保持时间
I/0端口:
0x70 寄存器地址索引端口 0x71 数据端口
Linux
内核对
RTC
的唯一用途就是
把
RTC
用作“离线”或“后台”的时间与日期维护器。当
Linux
内核启动时,它从
RTC
中读取时间与日期的基准值。然后再运行期间内核就完全抛开
RTC
,从而以软件的形式维护系统的当前时间与日期,并在需要时将时间回写到
RTC
芯片中。
PIT(可编程间隔定时器): 每个pc都有一个PIT,以通过IRQ0产生周期性的时钟中断信号,普遍的是 inter 8254
I/0端口:
0x40 ~0x43
Intel 8254 PIT
有
3
个计时通道:
通道
0
用来负责更新系统时钟。每当一个时钟滴答过去时,它就会通过
IRQ0
向系统产生一次时钟中断。
通道
1
通常用于控制
DMAC
对
RAM
的刷新
通道
2
被连接到
PC
机的扬声器,以产生方波信号。
TSC(
时间戳记数器
):
该寄存器实际上是一个不断增加的计数器,它在
CPU
的每个时钟周期到来时加
1
按 照上面的分析,有下面的概念我们先要认清楚:
时钟滴答的频率(HZ):也即1秒时间内PIT所产生的时钟滴答次数
时钟周期(clock cycle)的频率:8253/8254
PIT的本质就是对由晶体振荡器产生的时钟周期进行计数,晶体振荡器在1秒时间内产生的时钟脉冲个数就是时钟周期的频率。Linux用宏
CLOCK_TICK_RATE来表示8254
PIT的输入时钟脉冲的频率(在PC机中这个值通常是1193180HZ),该宏定义在include/asm-i386/timex.h头文件中:
#define CLOCK_TICK_RATE 1193180 /* Underlying HZ */
时钟滴答(clock tick): 当PIT通道0的计数器减到0值时,它就在IRQ0上产生一次时钟中断,也 即一次时钟滴答。PIT通道0的计数器的初始值决定了要过多少时钟周期才产生一次时钟中断,因此也 就决定了一次时钟滴答的时间间隔长度
根据HZ的值,我们也可以知道一次时钟滴答的具体时间间隔应该是(1000ms/HZ)=10ms。
也就是我们上面认为了10ms的时间,在 IRQ0上产生一次时钟中断。
宏LATCH:Linux用宏LATCH来定义要写到PIT通道0的计数器中的值,它表示PIT将没隔多少个时钟周期产生一次时钟中断。显然LATCH应该由下列公式计算
LATCH=(1秒之内的时钟周期个数)÷(1秒之内的时钟中断次数)=(CLOCK_TICK_RATE)÷(HZ)
从上面可以看到,这个直是1193180/100
取整
#define LATCH ((CLOCK_TICK_RATE + HZ/2) / HZ) /* For divider */
一般来说,每隔10ms会产生一次时钟滴答(产生一次时间中断),但是这10ms有LATCH的时钟周期。
表示内核心的时间结构:
- 全局变量 jiffies : 32 位的无符号整数, 表示自内核启动以来的时钟滴答次数。 每发生一次时钟滴答,内核的时钟中断处理函数 timer_interrupt ()都要将该全局变量 jiffies 加 1
- 全局变量xtime: 表示当前时间距 UNIX 时间基准 1970 - 01 - 01 00 : 00 : 00 的相对秒数值
- 全局变量sys_tz : timezone类型, 表示系统当前的时区信息
与可编程定时器PIT相比,用TSC寄存器可以获得更精确的时间度量。但是在可以使用TSC之前,它必须精确地确定1个TSC计数值到底代表多长的时间间 隔,也即到底要过多长时间间隔TSC寄存器才会加1。Linux内核用全局变量fast_gettimeoffset_quotient来表示这个值
定义在arch/i386/kernel/time.c文件中的函数calibrate_tsc()就是根据上述公式来计算
fast_gettimeoffset_quotient的值的。显然这个计算过程必须在内核启动时完成,因此,函数calibrate_tsc()只被
初始化函数time_init()所调用。
用TSC实现高精度的时间服务
在拥有TSC(TimeStamp Counter)的x86 CPU上,Linux内核可以实现微秒级的高精度定时服务,也即可以确定两次时钟中断之间的某个时刻的微秒级时间值