原文如下:

关于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 溢出、 系统滴答数ticks、time_after 宏的分析理解

    (转载请注明出处:http://blog.csdn.net/gujintong1110/article/details/44504343) 最近用到了tick timer系统滴答计时器,遇到了溢出的问 ...

  2. C/C++中宏概念理解

    C/C++中宏概念理解 C/C++中宏概念理解 宏替换是C/C++系列语言的技术特色,C/C++语言提供了强大的宏替换功能,源代码在进入编译器之前,要先经过一个称为"预处理器"的模 ...

  3. java 内存溢出的分类_【深入理解Java虚拟机】读后感:JVM内存划分与内存溢出小结...

    扫码关注公众号:Java 技术驿站 发送:vip 将链接复制到本浏览器,永久解锁本站全部文章 [公众号:Java 技术驿站] [加作者微信交流技术,拉技术群] # JVM内存划分与内存溢出小结 # 1 ...

  4. Linux内核中container_of宏的理解

    对 typeof 的理解: 实际上, typeof 并不是宏定义,它是GCC的关键字,是GCC特有的特性.如果只知道一个变量的名字要得到其类型,并不是宏定义能够完成的,这需要编译时的信息.所以,typ ...

  5. size_t,__T,_T,TEXT,_TEXT等一些特殊宏的理解

    typedef char TCHAR ;    现在开始讨论字符串文字中的L问题.如果定义了_UNICODE标识符,那么一个称作__T的宏就定义如下: #define __T(x) L##x    这 ...

  6. 录制宏,理解编程的方法

    本文以文字转曲为例,介绍CDR中录制宏(脚本)的方法. 插件编程很大的一个难点就是不知道宿主程序提供的程序集是如何操作的.比如你对文字转曲的编程方法不甚了解,那怎么办呢? 一个比较好用的方法就是:录制 ...

  7. 对宏offsetof理解

    #ifndef offsetof #define offsetof(TYPE, MEMBER) ((size_t)&((TYPE*)0)->MEMBER) #endif offset这个 ...

  8. CV_ELEM_SIZE1 和 CV_ELEM_SIZE两个宏的理解

    CV_ELEM_SIZE1的定义如下: /** Size of each channel item,    0x28442211 = 0010 1000 0100 0100 0010 0010 000 ...

  9. Linux Ext 文件系统

    引言 本文整理了 Linux 内核中 Ext 文件系统的相关知识.更多相关文章和其他文章均收录于贝贝猫的文章目录. 文件系统种类 前面我们讨论了虚拟文件系统,它对所有下层文件系统进行了封装,统一了上层 ...

最新文章

  1. 全国默哀 网站首页都要变成灰色的简单解决办法
  2. abap table control里面各种属性和事件的写法
  3. 前端三部曲之Html -- 1(html的基本结构和常见的meta标签的作用)
  4. oracle获取序列跳号,Oracle sequence跳号知多少
  5. SpringMVC整合Shiro
  6. MMdetection安装使用(1)
  7. VS2008+Qt 项目目录
  8. 解决访问被拒绝:Microsoft.Web.UI.WebControls的问题
  9. java反射创建实例_Java反射创建实例
  10. 北京大学生物信息学(8)
  11. 著名的软件项目开发和生命周期管理软件MKS.Code.Integrity.Enterprise.Edition.v12
  12. linux怎么卸载vsftpd软件,vsftpd配置详解之软件安装和卸载
  13. python绘制相频特性曲线_详解基于python的图像Gabor变换及特征提取
  14. 基于PID算法的房间温度控制及Python程序
  15. 读《因果的真相》第四章摘抄笔记
  16. Qt编写地图综合应用12-路线查询
  17. Verilog 避免 Latch
  18. iOS 保存图片到手机的几种方法--(OC)
  19. 装linux系统的工具箱,PE,Dos工具箱,自动安装linux的U盘制作
  20. hdu 4745 区间dp

热门文章

  1. JVM中线程是否可以并行执行
  2. Spring源码解析 - AbstractBeanFactory 实现接口与父类分析
  3. 详细解析ASP.NET中Request接收参数乱码原理
  4. sql中的text字段如何导入oracle
  5. 艾伟:memcached全面剖析–3.memcached的删除机制和发展方向
  6. 阿里云盾技术强在哪里?轻松防御DDoS、CC攻击
  7. 文件上传(上传至独立的文件服务器)
  8. LAMP编译安装(一)——安装Apache2.4
  9. sql连oracle链接服务器
  10. 【274天】每日项目总结系列012(2017.11.06)