您的位置:首页 > 运维架构 > Linux

Linux核心几个重要跟时间有关的名词或变数,底下将介绍HZ、tick与jiffies

2013-07-12 11:00 549 查看
Linux核心几个重要跟时间有关的名词或变数,底下将介绍HZ、tick与jiffies。

(1)HZ

Linux核心每隔固定周期会发出timer interrupt (IRQ 0),HZ是用来定义每一秒有几次timer interrupts。举例来说,HZ为1000,代表每秒有1000次timer interrupts。 HZ可在编译核心时设定,具体查询设定方法可参见上篇文章《linux运行多久了》。其中HZ可设定100、250、300或1000。以核心版本预设值为250,做实验:

观察/proc/interrupt的timer中断次数,并于一秒后再次观察其值。理论上,两者应该相差250左右。

adrian@adrian-desktop:~$ cat /proc/interrupts|grep timer && sleep 1 && cat /proc/interrupts|grep timer 

0: 9309306 IO-APIC-edge timer 

0: 9309562 IO-APIC-edge timer 

补充结果:以上结果是单CPU上运行,在我自己的双核电脑上并没有得到预计结果,怀疑是否适合双核电脑实验。

(2)Tick

Tick是HZ的倒数,意即timer interrupt每发生一次中断的时间。如HZ为250时,tick为4毫秒(millisecond)。

(3)全局变量xtime

xtime是timeval数据结构变量,首先看timeval结构

struct timeval

{

time_t tv_sec; /***second***/

susecond_t tv_usec;/***microsecond***/

}

1秒=1000毫秒(3个零),1秒=1000 000微秒(6个零),1秒=1000 000 000纳秒(9个零),1秒=1000 000 000 000皮秒(12个零)。秒用s表现,毫秒用ms,微秒用us表示,纳秒用ns表示,皮秒用ps表示,他们的分级单位是千,即每次3个零。所以microsecond是表示us。

xtime是从cmos电路中取得的时间,一般是从某一历史时刻开始到现在的时间,也就是为了取得我们操作系统上显示的日期。这个就是所谓的“实时时钟”,它的精确度是微秒。

(4)Jiffies

在<linux/jiffies.h>,定义了Jiffies为Linux核心变数(32位元变数,unsigned long),它被用来纪录系统自开机以来,已经过多少的tick,在linux内核中jiffies远比xtime重要。每发生一次timer interrupt,Jiffies变数会被加一。jiffies 与绝对时间之间的转换,用两个宏来完成两种时间单位的互换:JIFFIES_TO_NS()、NS_TO_JIFFIES()。

硬件给内核提供一个系统定时器用以计算和管理时间,值得注意的是,Jiffies于系统开机时,并非初始化成零,而是被设为-300*HZ (kernel/time.c),即代表系统于开机五分钟后,jiffies便会溢位。那溢位怎么办?事实上,Linux核心定义几个macro(timer_after(unknown, known)、time_after_eq(unknown, known)、time_before(unknown, known)与time_before_eq(unknown, known)),即便是溢位,也能藉由这几个macro正确地取得jiffies的内容。

可以利用jiffies设置超时等,譬如:

unsigned long timeout = jiffies + tick_rate * 2; // 2秒钟后超时,比如tick_rate=250,经过500次的tick中断,2S就过了

if(time_before(jiffies, timeout)

{

// 还没有超时

}

else

{

// 已经超时

}

另外,80x86架构定义一个与jiffies相关的变数jiffies_64 ,此变数64位元,要等到此变数溢位可能要好几百万年。因此要等到溢位这刻发生应该很难吧。那如何经由jiffies_64取得jiffies资讯呢?事实上,jiffies被对应至jiffies_64最低的32位元。因此,经由jiffies_64可以完全不理会溢位的问题便能取得jiffies。

(5)除了系统定时器jiffies外,还有一个与时间有关的时钟:实时时钟(RTC),这是一个硬件时钟,用来持久存放系统时间,系统关闭后靠主板上的微型电池保持计时。系统启动时,内核通过读取RTC来初始化Wall Time,并存放在xtime变量中,这是RTC最主要的作用。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zhandoushi1982/archive/2010/04/27/5536210.aspx

/**********************************************************************************************************************/

1. 概述 

Linux内核中,TCP/IP协议栈在很多用到时间比较的地方都使用了jiffies?本文介绍了什么是jiffies,jiffies溢出可能造成的问题,使用time_after等宏来正确地比较时间及其背后的原理。

2. jiffies简介

2.1 时钟中断 

在Linux内核中,TCP/IP协议栈在很多用到时间比较的地方都使用了jiffies。 

那么jiffies是什么呢?我们知道,操作系统应该能够在将来某个时刻准时调度某个任务,所以需要一种能保证任务准时调度运行的机制。希望支持每种操作系统的微处理器必须包含一个可周期性中断它的可编程间隔定时器。这个周期性中断被称为系统时钟滴答(或system timer),它象节拍器一样来组织系统任务,也称为时钟中断。 

时钟中断的发生频率设定为HZ,HZ是一个与体系结构无关的常数,在文件<linux/param.h> 中定义。至少从2.0版到2.1.43版,Alpha平台上Linux定义HZ的值为1024,而其他平台上定义为100。时钟中断对操作系统是非常重要的,当时钟中断发生时,将周期性地执行一些功能,例如: 

. 更新系统的uptime 

. 更新time of day 

. 检查当前任务的时间片是否用光,如果是则需要重新调度任务 

. 执行到期的dynamic timer 

. 更新系统资源使用统计和任务所用的时间统计

2.2 jiffies及其溢出 

全局变量jiffies取值为自操作系统启动以来的时钟滴答的数目,在头文件<linux/sched.h>中定义,数据类型为 unsigned long volatile (32位无符号长整型)。关于jiffies为什么要采用volatile来限定,可参考《关于 volatile和jiffies.txt》。 

jiffies转换为秒可采用公式:(jiffies/HZ)计算,将秒转换为jiffies可采用公式:(seconds*HZ)计算。 

当时钟中断发生时,jiffies值就加1。因此连续累加一年又四个多月后就会溢出(假定HZ=100,1个jiffies等于1/100 秒,jiffies可记录的最大秒数为(2^32 -1)/100=42949672.95秒,约合497天或1.38年),即当取值到达最大值时继续加 1,就变为了0。 

Vxworks操作系统中,定义HZ的值为60,因此连续累加两年又三个多月后也将溢出(jiffies可记录的最大秒数为约合2.27年)。如果在Vxworks操作系统上的应用程序对jiffies的溢出没有加以充分考虑,那么在连续运行两年又三个多月后,这些应用程序还能够稳定运行吗? 

下面我们来考虑jiffies的溢出,我们将从以下几个方面来阐述: 

. 无符号整型溢出的具体过程 

. jiffies溢出造成程序逻辑出错 

. Linux内核如何来防止jiffies溢出 

. time_after等比较时间先/后的宏背后的原理 

. 代码中使用time_after等比较时间先/后的宏

3. 无符号整型溢出的具体过程 

我们首先来看看无符号长整型(unsigned long)溢出的具体过程。实际上,无符号整型的溢出过程都很类似。为了更具体地描述无符号长整型溢出的过程,我们以8位无符号整型为例来加以说明。 

8位无符号整型从0开始持续增长,当增长到最大值255时,继续加1将变为0,然后该过程周而复始: 

0, 1, 2, ..., 253, 254, 255, 

0, 1, 2, ..., 253, 254, 255, 

...

4. jiffies溢出造成程序逻辑出错 

下面,通过一个例子来看jiffies的溢出。 

例4-1:jiffies溢出造成程序逻辑出错 

unsigned long timeout = jiffies + HZ/2; /* timeout in 0.5s */

/* do some work ... */ 

do_somework();

/* then see whether we took too long */ 

if (timeout > jiffies) { 

/* we did not time out, call no_timeout_handler() ... */ 

no_timeout_handler();

} else { 

/* we timed out, call timeout_handler() ... */ 

timeout_handler(); 



本例的目的是从当前时间起,如果在0.5秒内执行完do_somework(),则调用no_timeout_handler()。如果在0.5秒后执行完do_somework(),则调用timeout_handler()。 

我们来看看本例中一种可能的溢出情况,即在设置timeout并执行do_somework()后,jiffies值溢出,取值为0。设在设置 timeout后,timeout的值临近无符号长整型的最大值,即小于2^32-1。设执行do_somework()花费了1秒,那么代码应当调用 timeout_handler()。但是当jiffies值溢出取值为0后,条件timeout > jiffies成立,jiffies值(等于0)小于timeout(临近但小于2^32-1),尽管从逻辑上讲jiffies值要比timeout大。但最终代码调用的是
no_timeout_handler(),而不是timeout_handler()。

5. Linux内核如何来防止jiffies溢出 

Linux内核中提供了以下四个宏,可有效地解决由于jiffies溢出而造成程序逻辑出错的情况。下面是从Linux Kernel 2.6.7版本中摘取出来的代码: 
/* 
* These inlines deal with timer wrapping correctly. You are 
* strongly encouraged to use them 
* 1. Because people otherwise forget 
* 2. Because if the timer wrap changes in future you won't have to 
* alter your driver code. 

* time_after(a,b) returns true if the time a is after time b. 

* Do this with "<0" and ">=0" to only test the sign of the result. A 
* good compiler would generate better code (and a really good compiler 
* wouldn't care). Gcc is currently neither. 
*/ 
#define time_after(a,b) \ 
(typecheck(unsigned long, a) && \ 
typecheck(unsigned long, b) && \ 
((long)(b) - (long)(a) < 0)) 
#define time_before(a,b) time_after(b,a)

#define time_after_eq(a,b) \ 
(typecheck(unsigned long, a) && \ 
typecheck(unsigned long, b) && \ 
((long)(a) - (long)(b) >= 0)) 
#define time_before_eq(a,b) time_after_eq(b,a)

在宏time_after中,首先确保两个输入参数a和b的数据类型为unsigned long,然后才执行实际的比较。这是以后编码中应当注意的地方。

6. time_after等比较时间先后的宏背后的原理 

那么,上述time_after等比较时间先/后的宏为什么能够解决jiffies溢出造成的错误情况呢? 

我们仍然以8位无符号整型(unsigned char)为例来加以说明。仿照上面的time_after宏,我们可以给出简化的8位无符号整型对应的after宏: 

#define uc_after(a, b) ((char)(b) - (char)(a) < 0)

设a和b的数据类型为unsigned char,b为临近8位无符号整型最大值附近的一个固定值254,下面给出随着a(设其初始值为254)变化而得到的计算值: 

a b (char)(b) - (char)(a) 

254 254 0 

255 - 1 

0 - 2 

1 - 3 

... 

124 -126 

125 -127 

126 -128 

127 127 

128 126 

... 

252 2 

253 1

从上面的计算可以看出,设定b不变,随着a(设其初始值为254)不断增长1,a的取值变化为: 

254, 255, (一次产生溢出) 

0, 1, ..., 124, 125, 126, 127, 126, ..., 253, 254, 255, (二次产生溢出) 

0, 1, ... 

...

而(char)(b) - (char)(a)的变化为: 

0, -1, 

-2, -3, ..., -126, -127, -128, 127, 126, ..., 1, 0, -1, 

-2, -3, ... 

...

从上面的详细过程可以看出,当a取值为254,255, 接着在(一次产生溢出)之后变为0,然后增长到127之前,uc_after(a,b)的结果都显示a是在b之后,这也与我们的预期相符。但在a取值为127之后,uc_after(a,b)的结果却显示a是在b之前。 

从上面的运算过程可以得出以下结论: 

使用uc_after(a,b)宏来计算两个8位无符号整型a和b之间的大小(或先/后,before/after),那么a和b的取值应当满足以下限定条件: 

. 两个值之间相差从逻辑值来讲应小于有符号整型的最大值。 

. 对于8位无符号整型,两个值之间相差从逻辑值来讲应小于128。 

从上面可以类推出以下结论: 

对于time_after等比较jiffies先/后的宏,两个值的取值应当满足以下限定条件: 

两个值之间相差从逻辑值来讲应小于有符号整型的最大值。 

对于32位无符号整型,两个值之间相差从逻辑值来讲应小于2147483647。 

对于HZ=100,那么两个时间值之间相差不应当超过2147483647/100秒 = 0.69年 = 248.5天。对于HZ=60,那么两个时间值之间相差不应当超过2147483647/60秒 = 1.135年。在实际代码应用中,需要比较先/后的两个时间值之间一般都相差很小,范围大致在1 秒~1天左右,所以以上time_after等比较时间先/后的宏完全可以放心地用于实际的代码中。

7. 代码中使用time_after等比较时间先/后的宏 

下面代码是针对例4-1修改后的正确代码: 

例7-1:在例4-1基础上使用time_before宏后的正确代码 

unsigned long timeout = jiffies + HZ/2; /* timeout in 0.5s */

/* do some work ... */ 

do_somework();

/* then see whether we took too long */ 

if (time_before(jiffies, timeout)) { 

/* we did not time out, call no_timeout_handler() ... */ 

no_timeout_handler();

} else { 

/* we timed out, call timeout_handler() ... */ 

timeout_handler(); 

}

8. 结论 

系统中采用jiffies来计算时间,但由于jiffies溢出可能造成时间比较的错误,因而强烈建议在编码中使用time_after等宏来比较时间先后关系,这些宏可以放心使用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: