jiffies变量
全局变量jiffies用来记录自启动以来产生的节拍的总数。系统启动时会将该变量初始化为0,此后,每当时钟中断产生时就会增加该变量的值。jiffies和另外一个变量息息相关:HZ。HZ是每秒系统产生的时钟中断次数,所以jiffies每秒增加的值也就是HZ;在x86体系结构中,内核版本在2.4以前的值为100,在2.6内核中被定义为1000。
jiffies的定义
extern unsigned long volatile jiffies; //定义于<linux/jiffies.h>
值得注意的一点,C 编译器通常只将变量装载一次 。 一 般情况下不能保证循环中的 jiffies 变量在每次循环中被读取时都重新被载入。但是我们要求 jiffies 在每次循环时都必须重新装载,因为在后台 jiffies 值会随时钟中断的发生而不断增加。为了解决这个问题,<linux/jiffies.h >
中 jiffies 变量被标记为关键字 volatile 。关键字 volatile指示编译器在每次访问变量时都重新从主内存中获得,而不是通过寄存器中的变量别名来访问,从而确保前面的循环能按预期的方式执行。
从定义可以看出,jiffies的类型为unsigned long,在32位体系结构上unsigned long是32位,在64位体系结构上是64位。 在32位体系结构上,在系统的HZ值为100的情况下,jiffies的回绕时间为497天,如果HZ为1000,那么回绕时间将只有49.7天左右。如果发生了回绕现象,对内核中直接利用jiffies来比较时间的代码将产生很不利的影响,比如在《linux内核设计与实现》一书中有一个例子可以说明这个问题:
unsigned long timeout = jiffies + HZ/2; //0.5后超时
/*执行一些任务*/
........
/*然后检查时间是否过长*/
if(timeout>jiffies){
/*没有超时...*/
}else{
/*超时了....*/
}
在这个例子中,如果设置了timeout后发生了回绕,那么第一个判断条件将变为真,这与实际情况不符,尽管因为实际的时间比timeout要大,但因为jiffies发生了回绕变成了0,所以jiffies肯定小于timeout的值。 内核也专门针对这种情况提供了四个宏来帮助比较jiffies计数:
#define time_after(unknown,known) ((long)(known) - (long)(unknown)<0)
#define time_before(unkonwn,known) ((long)(unknown) - (long)(known)<0)
#define time_after_eq(unknown,known) ((long)(unknown) - (long)(known)>=0)
#define time_before_eq(unknown,known) ((long)(known) -(long)(unknown)>=0)
这些宏看起来很奇妙,只是将计数值强制转换为long类型然后比较就能避免回绕带来的问题,这是为什么呢?这和计算机中数据存储的形式有关!!
计算机中数据的存储是按照二进制补码的方式存储的,之所以采用补码的方式存储是因为这样可以简化运算规则和线路设计。另外一个相关的概念就是原码,原码采用一个最高位作为符号位,其余位是数据大小的二进制表示。 补码的定义是这样的:正数的补码即为原码,负数的补码为原码除符号位外其他各位取反再加1。举例如下:
[+1]补码 = [+1]原码 = 0000 0001
[-1]补码 = [-1]原码取反+1 = 1111 1110 + 1 = 1111 1111
而c语言中的数据类型相当于在代码和实际机器的存储之间的一个中间层,机器中存储的数据,如果按照不同的类型格式取读取就会得到不同的结果,才代码和实际存储之间,编译器充当了翻译者的角色。这是编译器能实现多种数据类型和强制类型转换的基础。
有了这些基础后,就不难理解上述宏定义的巧妙之处了,为了便于说明,以下假设jiffies是单字节的无符号数,范围为0~255。假如jiffies开始为250,由于是无符号数据,那么它在机器中实际存储的补码为1111 1010
,记为J1
;timeout如果被设为252,实际存储为1111 1100
;而过了一会jiffies发生回绕编变成了1,实际存储变为0000 0001
,记为J2
。 那么此时如果按照无符号数比较其大小关系,有: J1<timeout & J2 <timeout
,这样的结果与实际的时间节拍统计是不符的,但是如果我们按照有符号数来比较会有什么结果呢?
J1
如果按照有符号数读取,首先从补码转换成原码:1000 0110
,转换成十进制为-6;
timeout按照有符号数读取,首先从补码转换成原码:1000 0100
,转换成十进制为-4;
J2
按照有符号数读取,首先从补码转换成原码:0000 0001
,转换成十进制为1;
这样它们的大小关系为: J1<timeout<J2
。 这与实际的节拍计数就吻合了,以上内核定义的几个宏就是通过这种方式巧妙解决jiffies回绕问题的。
但这个写法有一个小小的缺点,就是延时不能大于周期数,否则计算就不正确了,对于32 位的情况就是延时不能大于
231−1
2
31
−
1
,想来也没有哪个程序会一次性 delay 49.7天吧……所以这一套宏便能够胜任了。