原文如下:

关于jiffies变量: 
    全局变量jiffies用来记录自启动以来产生的节拍的总数。系统启动时会将该变量初始化为0,此后,每当时钟中断产生时就会增加该变量的值。jiffies和另外一个变量息息相关:HZ。HZ是每秒系统产生的时钟中断次数,所以jiffies每秒增加的值也就是HZ;在x86体系结构中,内核版本在2.4以前的值为100,在2.6内核中被定义为1000。  jiffies的定义:
    extern unsigned long volatile jiffies;  //定义于<linux/jiffies.h>
    从定义可以看出,jiffies的类型为unsigned long,在32位体系结构上unsigned long是32位,在64位体系结构上是64位。 在32位体系结构上,在系统的HZ值为100的情况下,jiffies的回绕时间大约为500天左右,如果HZ为1000,那么回绕时间将只有50天左右。如果发生了回绕现象,对内核中直接利用jiffies来比较时间的代码将产生很不利的影响,比如在<<Linux Kernle Developmen>>一书中有一个例子可以说明这个问题:
    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,由于是无符号数据,那么它在机器中实际存储的补码为11111010,记为J1;timeout如果被设为252,实际存储为11111100;而过了一会jiffies发生回绕编变成了1,实际存储变为00000001,记为J2。 那么此时如果按照无符号数比较其大小关系,有: J1<timeout & J2 <timeout,这样的结果与实际的时间节拍统计是不符的,但是如果我们按照有符号数来比较会有什么结果呢?
    J1如果按照有符号数读取,首先从补码转换成原码:10000110,转换成十进制为-6;
    timeout按照有符号数读取,首先从补码转换成原码:10000100,转换成十进制为-4;
    J2按照有符号数读取,首先从补码转换成原码:00000001,转换成十进制为1;
    这样它们的大小关系为: J1<timeout<J2。 这与实际的节拍计数就吻合了,以上内核定义的几个宏就是通过这种方式巧妙解决jiffies回绕问题的

最近在慢慢的啃着 Linux 内核的相关源码,读到 jiffies 这里,这个东西和 windows 下 GetTickCount 获得的值是类似的,就是系统启动以来所经历的 tick 数(windows 下是一毫秒一 tick),神马 timeout、delay、sleep 之类的东西还有进程的调度等,凡是与时间有关的东西大多依赖于它。以前看书的时候总是读到类似这样的内容:这个值一般是32位的,会在0到2^32之间循环,约49.71天。

那时还很天真的想,自己一般一天关机一次,想来是够用了,现在再看,够用个毛啊,一年到头不关机的机器多的是啊。想来那些写操作系统的大牛是不会留下这么二的 BUG 的,果然,今天研究到了这里,在 Linux 内核中使用了一个这样的宏来解决这个问题:

#define time_after(a,b)\
(typecheck(unsigned long,a) &&\
typecheck(unsigned long,b) &&\
((long)(b)-(long)(a)<0) //当 a 在 b 的后面(大于等于),此宏为真  

将无符号数转成有符号数计算,的确是可以解决自加溢出的问题,以 char 为例,unsigned char 的 255 比 0 大,但是转成 signed char 之后其值为 -1 比 0 小,于是 -1, 0, 1 ... 就形成了连续性的递增关系,解决了溢出时的问题(这里不明白的同学请随便找一本C语言书看看补码、反码那一块)。

结合程序来看,假设我需要设置一个 5 tick 的延时,那么 timeout = jiffies + 5;如果运气不好,恰巧这个时候 jiffies 等于254,那么 timeout  等于 254 + 5 = 3,若没有使用宏而直接判断 jiffies > timeout ,那么结果就不对了,本来在 5 个 tick 内 jiffies 都应该小于 timeout 才对的,但 254 直接就大于 3,于是延时失败了。而使用宏的话 254 当做 -2 ,于是 5 个 tick 过后它才会大于 3,符合预期。

但我写到这里,上面的内容基本上等同于废话 ^@^ ,因为那些并不是我读到这个部分源码时所纠结的问题,我纠结的是,当 jiffies 从 127 加到 128 ,由正数转为负数的回绕产生时,time_after 是如何保证比较的结果的正确性的?为此,我自己写了个简化版的宏来做试验:

#define time_after(a,b) ((char)(b)-(char)(a)<0)
//这个宏在 VC 下是不对的,后面解释  

经过一番研究以及阅读别人的帖子,我终于了解了它的原理,在此与大家分享,希望能够对他人有所帮助。

我们假设当前的 jiffies 值为 127 其 5 个 tick 之后的值为 132 ,以有符号来解释该数据则 timeout = -124,那么 jiffies 如何可能会小于 timeout 呢?timeout - jiffies > 0 又是怎么成立的呢?请听下回分解!

别砸显示器,开个玩笑。-124 - 127 = -251 是负数,貌似条件无法成立了,但它就能成立,因为 -251 在内存里的存储是 0xFF05,它超出了一个 byte 的范围,于是将其高位截去剩下 0x05,它是正数,于是 timeout - jiffies > 0 就成立了,坑爹啊!!!

但这个写法有一个小小的缺点,就是延时 tick 不能大于 127,否则计算就不正确了,换成 32 位的情况就是延时 tick 不能大于 2^31-1 想来也没有哪个程序会一次性 delay 25 天吧,所以这一套宏便能够胜任了。

顺便一提,我比较倒霉,因为我是在 VC 的环境下测试的这个宏,结果怎么都不对,原因是在于其运算时隐式转换的规则有细微的不同,它得出结果后并没有将数据截去高位,而是应该升级成了 int 型,于是负号被保留了下来,判断出错。改为如下即可:

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

对 jiffies 溢出、回绕及 time_after 宏的理解的更多相关文章

  1. jiffies溢出与时间先后比较-time_after,time_before【转】

    转自:http://www.cnblogs.com/hfyinsdu/p/4600052.html 参考地址: http://blog.csdn.net/jk110333/article/detail ...

  2. jiffies溢出与时间先后比较-time_after,time_before

    参考地址: http://blog.csdn.net/jk110333/article/details/8177285 http://blog.chinaunix.net/uid-23629988-i ...

  3. linux内核中jiffies的回绕问题【转】

    本文转载自:http://blog.csdn.net/yuanlulu/article/details/6019862 ======================================== ...

  4. 关于jiffies回绕以及time_after,time_before

    系统中有非常多变量用来记录一个单调递增的现实,典型的有两个,一个是TCP的序列号.还有一个就是jiffies,可是由于计算机内表示的数字都是有限无界的,所以不论什么数字都不能做到全然意义的单调递增,它 ...

  5. linux内核计算时间差以及jiffies溢出

    jiffies是每一个时钟中断,都会加1.这就导致一个问题.不管jiffies(一般来说是unsigned long类型)多少个字节,总有溢出的时候. 更极端的时候.当期jiffies是0xfffff ...

  6. 使用jiffies的时间比较函数time_after、time_before

    1. jiffies简介 首先,操作系统有个系统专用定时器(system timer),俗称滴答定时器,或者系统心跳. 全局变量jiffies取值为自操作系统启动以来的时钟滴答的数目,在头文件< ...

  7. C 宏定义 理解(1)

  8. jiffies和HZ

    全局变量jiffies用来记录自系统启动以来产生的节拍的总数.启动时,内核将该变量初始化为0,此后,每次时钟中断处理程序都会增加该变量的值.一秒内时钟中断的次数等于Hz,所以jiffies一秒内增加的 ...

  9. Linux内核jiffies简介

    在LINUX的时钟中断中涉及至二个全局变量一个是xtime,它是timeval数据结构变量,另一个则是jiffies,首先看timeval结构struct timeval{time_t tv_sec; ...

随机推荐

  1. 2015 多校联赛 ——HDU5416(异或)

    CRB has a tree, whose vertices are labeled by 1, 2, …, N. They are connected by N – 1 edges. Each ed ...

  2. [cf453e]Little Pony and Lord Tirek

    来自FallDream的博客,未经允许,请勿转载,谢谢. 更博客= = 有n个数,每个数字都有一个初始大小ai和最大值mi,然后每秒会增加ri,你需要回答m个发生时间依此增大的询问,每次询问区间和并且 ...

  3. [bzoj4236]JOIOJI

    来自FallDream的博客,未经允许,请勿转载,谢谢. JOIOJI桑是JOI君的叔叔.“JOIOJI”这个名字是由“J.O.I”三个字母各两个构成的. 最近,JOIOJI桑有了一个孩子.JOIOJ ...

  4. const的一些用法和理解

    首先先说一下const常量的用处,我们知道宏定义#define是没有数据类型的,编译器在编译的时候,不会对宏常量进行类型检查,只进行简单的字符串替换,字符串替换时极易产生意想不到的错误,所以这个时候, ...

  5. Spring中的InitializingBean接口的使用

    InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法. 测试,如下: imp ...

  6. 在java中String类为什么要设计成final

    在java中String类为什么要设计成final? - 胖胖的回答 - 知乎 https://www.zhihu.com/question/31345592/answer/114126087

  7. kindeditor配合requirejs使用时,ready失效

    KindEditor官方的文档在使用KindEditor时是这样的: KindEditor.ready(function(K)) { K.create('#editor_id'); } 使用了自己提供 ...

  8. C指针 指针和数组 二维数组的指针 指针应用

    直接到变量名标识的存储单元中读取变量的值--直接寻址 通过其他变量间接找到变量的地址读取变量的值--间接寻址 指针变量存放地址 显示变量的地址 指针变量使用前必须初始化,若不知指向哪,可先指向NULL ...

  9. ios中的safari转换时间戳问题

    后台下发的时间格式如:2016-09-15 15:30:10:也没多想,直接用new Date('2016-09-15 15:30:10').getTime()获取时间戳:开发时候,电脑调试OK,当放 ...

  10. Linux安装JProfiler监控tomcat

    下载JProfiler包wget http://download-keycdn.ej-technologies.com/jprofiler/jprofiler_linux_9_2.rpm 安装JPro ...