上一篇文章我们简单了解了一些关于时间的概念,以及Linux内核中的关于时间的基本理解。而本篇则会简单说明时钟硬件,以及Linux时间子系统相关的一些数据结构。
前文曾经提到过,内核时间子系统的实现也需要有硬件的支持。在计算机里一共有三类时钟硬件,分别是真时钟RTC(Real Time Clock)、定时器Timer、计时器Counter。
那生活中的场景举例,我们可以理解成RTC相当于是手表、座钟,定时器相当于是闹钟,计时器当然就是运动会中的计时器。
注意这是三类时钟硬件,而不是三个,某一类时钟可能有多个不同的硬件,某一个时钟硬件也可能实现多种不同的时钟类型。
计算机中还有其它的时钟类型,比如晶振时钟,是驱动CPU运行的周期信号,用来触发和同步CPU内部的操作,我们常说某CPU是多少GHz,就是说这个时钟晶振每秒向CPU发送多少信号。
晶振时钟一般在CPU内部,但有些嵌入式CPU的晶振在外部。时钟晶振在软件层不可见。还有一些设备也有自己的时钟,还有相应的驱动可以控制它。由于这些时钟都和时间子系统关系不大,所以本文中就不讨论它们了。
再具体点的话,我们以x86平台上的时钟举例说说:
Linux kernel 时间子系统的源文件位于linux/kernel/time/目录下,基本包含如下:
这里面也包含几个重要的数据结构,接下来会分开说说:
clock source又被叫做时钟源,如果它的频率是10MHZ,就代表它每秒增加10M次,每增长一次我们称cycle加一,而且两次增长的时间间隔相同,通过这个性质,可以在两个时间点读取clock souce,相减得到一个差值,这个差值 / 频率就可以得到两个时间点的时间间隔。
设cycles:两个时间点的cycle差值,hz :每一纳秒的cycle值,time :两点之间的时间差(ns为单位)
所以可得:time = cycles/ hz
可以看到,通过cycles和hz做除法,可以很轻松的获得两时间点的具体时间差,但是落到代码中,就没那么简单了。
内核中因为效率或者兼容性问题,禁用了浮点数运算,如果用整数除法那么精度会受到影响,速度也不高,所以内核中用了乘法和移位运算的方式来实现上述公式,虽然也有误差,但运算速度很高。
内核计算时间差的公式:time = (cycles * mul) >> shift,计算mul和shift的过程如下:
下面详细解释一下这个计算过程:
上述代码,part2 很好理解,就是根据 mul = (time << shift) / cycle,做计算,part1可能比较难理解,意义如下:
time = (cycles * mul) >> shift,可以看到想要获取time,先要cycles * mul,如果cycles * mul溢出了,那计算结果就完全错误了,所以要对mul的值做限制,保证任何 可能的cycle的值 和mul值相乘都不会溢出,这就是part1的作用。
要达成不产生溢出的要求,首先要明确可能的cycle的值的范围,cycle一般是两次中断之间的时钟源计数差值。所以,求cycle值范围的问题,就转化成了,两次时钟中断的最长间隔。目前两次时钟中断的最长间隔被假设成了10分钟。
为什么是10分钟?这是以下两个因素互相平衡做取舍的结果:
综上两点,内核选择 10分钟 这个值,作为两次时间中断的时间间隔最大值,该逻辑可以在init_time_arch()中体现。
clocksrouce和clock_event_device之间的关系如上图所示。
在arm平台(其他平台应该也是类似)的设计中,硬件定时器设备和时钟源设备是配合使用的,硬件定时器可以设置时钟源到达何值时会产生一个中断。在smp系统中,为了减少处理器间的通信开销,基本上每个cpu都会具备一个属于自己的本地timer_device,独立地为该cpu提供时钟事件服务,smp中的每个cpu基于本地timer_device,建立自己的高精度定时器。
所谓timekeeping,如字面意思,就是让时间持续更新下去。
linux内核中维护了有三种时间概念:
上面三种时间通过 xtime变量计算,xtime会在系统启动的时候通过从rtc获取的值来初始化,之后通过每次时钟中断的时候,加上当前时间和上次中断产生时间的差值。
可能会有的疑问:为什么需要维护xtime,每次需要获取时间的时候读取rtc不就好了?但其实读取rtc也有缺点,比如:
传统的低精度定时器,是指让硬件定时器每隔固定时间(1ms或者10ms)产生一次中断,这种操作的默认语义就是允许产生ms级的延迟,这种时钟中断频率作为任务调度用途来说还可以接受,但是如果有更高精度需求的设备就完全无法满足了。
所以就出现了高精度定时器这种形式,它和低精度定时器的最大差别点在于:低精度是被动的等待下一次固定间隔的时钟中断的到来,而高精度定时器则会主动去设置硬件定时器,让它在第几个cycle上产生中断,从而满足自己的需求。
很自然的可以推测出来,如果要实现高精度定时器,那么必须保证硬件定时器支持one-shot模式,也就是可以以变化的中断频率出现。
同时为了满足 任务调度的需求和原来系统的对 周期性时钟中断的依赖,专门安排了一个hrtimer来按照(CONFIG_CPU_HZ)规定的频率来对硬件定时器进行设置,从而达到周期性产生时钟中断的效果。
相信通过两期的内容,可以帮助大家对计算机时间子系统有一个大概的了解,同时更好的理解hrtimer和timekeeping原理。显然关于Linux时间子系统的内容,还有很多可以深入挖掘,在此篇幅有限,就不做过多赘述了,也希望同样对操作系统感兴趣的小伙伴与我们一起交流。
部分内容出自:
CSDN博主「wmzjzwlzs」的原创文章,原文链接:https://blog.csdn.net/wmzjzwlzs/article/details/131617402
以及蜗窝科技 http://www.wowotech.net/timer_subsystem/time-subsyste-architecture.html