linux时钟系统概述

一、内核时间概念

1. 了解下linux系统中一些时间概念,在kernel/time/timekeeping.c中定义了多个时间。
RTC时间:在PC中,RTC时间又叫CMOS时间,通常由一个专门的计时硬件来实现,软件可以读取该硬件来获得年月日、时分秒等时间信息,而在嵌入式系统中,有使用专门的RTC芯片,也有直接把RTC集成到Soc芯片中,读取Soc中的某个寄存器即可获取当前时间信息。一般来说,RTC是一种可持续计时的,也就是说,不管系统是否上电,RTC中的时间信息都不会丢失,计时会一直持续进行,硬件上通常使用一个后备电池对RTC硬件进行单独的供电。因为RTC硬件的多样性,开发者需要为每种RTC时钟硬件提供相应的驱动程序,内核和用户空间通过驱动程序访问RTC硬件来获取或设置时间信息。
xtime:xtime和RTC时间一样,都是人们日常所使用的墙上时间,只是RTC时间的精度通常比较低,大多数情况下只能达到毫秒级别的精度,如果是使用外部的RTC芯片,访问速度也比较慢。为此,内核维护了另外一个wall time时间:xtime,取决于用于对xtime计时的clocksource,它的精度甚至可以达到纳秒级别,因为xtime实际上是一个内存中的变量,它的访问速度非常快,内核大部分时间都是使用xtime来获得当前时间信息。xtime记录的是自1970年1月1日24时到当前时刻所经历的纳秒数。
wall_to_monotonic:monotonic时间自系统开机后就一直单调地递增,不像xtime可以因用户调整时间而产生跳变。不过该时间不计算系统休眠的时间,也就是说,系统休眠时,monotonic时间不会递增。奇怪的是,内核并没有直接定义一个这样的变量来记录monotonic时间,而是定义了一个变量wall_to_monotonic,记录了墙上时间和monotonic时间之间的偏移量,当需要获得monotonic时间时,把xtime和wall_to_monotonic相加即可,因为默认启动时monotonic时间为0,所以实际上wall_to_monotonic的值是一个负数,它和xtime同一时间被初始化,请参考timekeeping_init函数。
total_sleep_time:系统休眠时间。获取自系统启动以来的时间(getboottime)就是wall_to_monotonic与total_sleep_time两时间想加。
raw_time:raw monotonic time,与monotonic时间类似,也是单调递增时间,唯一不同是:raw_time“更纯净”,它不受NTP时间调整的影响,它代表着系统独立时钟硬件对时间的统计。

网上总结的时间属性表格:

/*
* The current time
* wall_to_monotonic is what we need to add to xtime (or xtime corrected
* for sub jiffie times) to get to monotonic time. Monotonic is pegged
* at zero at system boot time, so wall_to_monotonic will be negative,
* however, we will ALWAYS keep the tv_nsec part positive so we can use
* the usual normalization.
*
* wall_to_monotonic is moved after resume from suspend for the monotonic
* time not to jump. We need to add total_sleep_time to wall_to_monotonic
* to get the real boot based time offset.
*
* - wall_to_monotonic is no longer the boot time, getboottime must be
* used instead.
*/
static struct timespec xtime __attribute__ ((aligned (16)));
static struct timespec wall_to_monotonic __attribute__ ((aligned (16)));
static struct timespec total_sleep_time;

/*
* The raw monotonic time for the CLOCK_MONOTONIC_RAW posix clock.
*/
static struct timespec raw_time;

2. 系统启动时读取硬件时间,设置系统时间一般在rtc驱动加载后,调用rtc_hctosys(),文件在driver/rtc/hctosys.c中。其根据内核配置CONFIG_RTC_HCTOSYS来决定是否同步时间,并读取CONFIG_RTC_HCTOSYS_DEVICE(一般为rtc0)来获取RTC时间。

二、jiffies和Hz

全局变量jiffies用来记录自系统启动以来产生的节拍的总数。启动时,内核将该变量初始化为0,此后,每次时钟中断处理程序都会增加该变量的值。一秒内时钟中断的次数等于Hz,所以jiffies一秒内增加的值也就是Hz。系统运行时间以秒为单位,等于jiffies/Hz。
将以秒为单位的时间转化为jiffies:
seconds * Hz
将jiffies转化为以秒为单位的时间:
jiffies / Hz
相比之下,内核中将秒转换为jiffies用的多些。
硬件给内核提供一个系统定时器用以计算和管理时间,内核通过编程预设系统定时器的频率,即节拍率(tick rate),每一个周期称作一个tick(节拍)。Linux内核从2.5版内核开始把频率从100调高到1000(当然带来了很多优点,也有一些缺点).
jiffies是内核中的一个全局变量,用来记录自系统启动一来产生的节拍数。譬如,如果计算系统运行了多长时间,可以用 jiffies/tick rate 来计算。jiffies定义在文件<linux/jiffies.h>中:
extern unsigned long volatile jiffies;
可以利用jiffies设置超时等,譬如:

unsigned long timeout = jiffies + tick_rate * 2; // 2秒钟后超时
if(time_before(jiffies, timeout){
// 还没有超时
}
else{
// 已经超时
}

jiffies的回绕wrap around

当jiffies的值超过它的最大存放范围后就会发生溢出。对于32位无符号长整型,最大取值为(2^32)-1,即429496795。如果节拍计数达到了最大值后还要继续增加,它的值就会回绕到0。

内核提供了四个宏来比较节拍计数,这些宏定义在文件<linux/jiffies.h>中:

time_before(unknown, known)
time_after(unknown, known)
time_before_eq(unknown, known)
time_after_eq(unknown, known)

比较的时候用这些宏可以避免jiffies由于过大造成的回绕问题。

用户空间和HZ

问题提出:
在2.6以前的内核中,如果改变内核中的HZ值会给用户空间中某些程序造成异常结果。因为内核是以节拍数/秒的形式给用户空间导出这个值的,应用程序便依赖这个特定的HZ值。如果在内核中改变了HZ的定义值,就打破了用户空间的常量关系---用户空间并不知道新的HZ值。
解决方法:
内核更改所有导出的jiffies值。内核定义了USER_HZ来代表用户空间看到的HZ值。在x86体系结构上,由于HZ值原来一直是100,所以USER_HZ值就定义为100。内核可以使用宏jiffies_to_clock_t()将一个有HZ表示的节拍计数转换为一个由USER_HZ表示的节拍计数。

 

参考:

1. Linux系统下的单调时间函数 http://blog.chinaunix.net/uid-20662820-id-3880162.html

2. Linux时间子系统之三:时间的维护者:timekeeper

3. http://www.cnblogs.com/leaven/archive/2010/08/30/1812338.html

4. http://www.cnblogs.com/simonshi/archive/2010/12/13/1694819.html

posted @ 2016-10-30 19:06  yuxi_o  阅读(1637)  评论(0编辑  收藏  举报