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

============================================
作者:yuanlulu
http://blog.csdn.NET/yuanlulu

版权没有,但是转载请保留此段声明
============================================

1。网上遇到的一个问题。先贴出来问题,再说解决方法。

看“Linux 内核设计与实现” 的 jiffies 的回绕这里,产生一个疑问(后面再说),于是又到网上查到了这么一篇文章:

http://ericchan77.spaces.live.com/blog/cns!b2dc351bf474ddf2!287.entry

关于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回绕问题的

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

这段文字把:
  #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 , unsigned long, 到有符号long ,signed long ,的回绕呢?

也就是说,比如无符号long 是32位,现在 J1 是 7FFF FFF0,timeout 是 7FFF FFFF ,J2 是 7FFF FFFF + 1 ,转成有符号long 后,J1 是 7FFF FFF0 (一个很大正数,是 2 的 31 次方减 15 ),timeout 是 7FFF FFFF (一个很大正数,是 2 的 31 次方减 1 ),而 J2 成了 8000 0000 ,一个非常小的负数,是 负 2 的 31 次方,这就是说,本来 J1 < timeout < J2 ,此时, J2 << J1 < timeout ,怎么办?

2。再说一下自己验证的程序和结果。

我的程序如下:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/jiffies.h>

static int __init hello_mode_init(void)
{
 printk("/nHello ,Module/n");
 unsigned long j1  = 0x7ffffff0;
 unsigned long timeout = 0x7fffffff;
 unsigned long j2  = 0x80000002;
 
 printk("/nj2:%x,timeout:%x,time_after(j2, timeout):%d/n",
      j2, timeout, time_after(j2, timeout)); 
 printk("j2:%d, timeout:%d/n", (long)j2, (long)timeout);
 printk("typecheck(unsigned long, j2):%d,typecheck(unsigned long, timeout):%d/n", 
      typecheck(unsigned long, j2), typecheck(unsigned long, timeout));
 printk("( ((long)timeout - (long)j2<0)):%d /n", ((long)timeout - (long)j2<0));
 printk("( (( (long)timeout - (long)j2 )<0) ):%d /n", (((long)timeout - (long)j2)<0));

return 0;
}
static void __exit hello_mode_exit(void)
{
 printk(KERN_EMERG "/nBYe, MOdule!/n");
}

module_init(hello_mode_init);
module_exit(hello_mode_exit);

MODULE_LICENSE("Dual BSD/GPL");

打印的结果如下:

[root@lpc3250 tmp]# insmod hello_mod.ko

Hello ,Module

j2:80000002,timeout:7fffffff,time_after(j2, timeout):1
j2:-2147483646, timeout:2147483647
typecheck(unsigned long, j2):1,typecheck(unsigned long, timeout):1
( ((long)timeout - (long)j2<0)):1
( (( (long)timeout - (long)j2 )<0) ):1

3。问题分析与解决

程序的结果是正确的,这说明内核中的time_after()这个宏是没问题的。

内核中time_after的定义为:

#define time_after(a,b)  /
 (typecheck(unsigned long, a) && /
  typecheck(unsigned long, b) && /
  ((long)(b) - (long)(a) < 0))

typecheck是类型检查,是unsigned long类型就会返回1,打印信息表明它的确返回了1:typecheck(unsigned long, j2):1,typecheck(unsigned long, timeout):1

问题是(long)timeout - (long)j2<0为什么会为真。

我们打印的结果显示,j2转化为long类型以后变为负数,一个正数减去一个负数会小于0?有这回事?还真有。

j1,j2,timeout的关系如图。

转化为long类型之后,大于2^31-1的unsigned long类型的数变为负数。大小关系如下图

现在看到(long)timeout - (long)j2的值其实就是图中x2的值,只要第一张图片中的x1小于2^31-1, x2就大于2^31-1。大于2^31-1的数,最高的比特位(bit31)必定为1,由于计算是按long类型计算的,所以bit31上的1被当做负号处理,(long)timeout - (long)j2的结果其实是一个负数,当然小于0。因此正数减去负数也会小于0,。

好了,现在说说使用范围,为了使,(long)timeout - (long)j2小于零,必须使x1小于2^31-1。也就是说只要timeout和j2之差绝对值不超过2^31-1,time_after这组宏就不会出问题。

这组宏其实是牺牲了范围换取正确性。

linux内核中jiffies的回绕问题【转】的更多相关文章

  1. 对linux内核中jiffies+Hz表示一秒钟的理解

    jiffies在内核中是一个全局变量,它用来统计系统启动以来系统中产生的总节拍数,这个变量定义在include/linux/jiffies.h中,定义形式如下. unsigned long volat ...

  2. (五)对linux内核中jiffies+Hz表示一秒钟的理解

    jiffies在内核中是一个全局变量,它用来统计系统启动以来系统中产生的总节拍数,这个变量定义在include/Linux/jiffies.h中,定义形式如下. unsigned long volat ...

  3. Linux内核中的jiffies及其作用介绍及jiffies等相关函数详解

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

  4. 向linux内核中添加外部中断驱动模块

    本文主要介绍外部中断驱动模块的编写,包括:1.linux模块的框架及混杂设备的注册.卸载.操作函数集.2.中断的申请及释放.3.等待队列的使用.4.工作队列的使用.5.定时器的使用.6.向linux内 ...

  5. Linux内核中流量控制

    linux内核中提供了流量控制的相关处理功能,相关代码在net/sched目录下:而应用层上的控制是通过iproute2软件包中的tc来实现, tc和sched的关系就好象iptables和netfi ...

  6. 浅析linux内核中timer定时器的生成和sofirq软中断调用流程(转自http://blog.chinaunix.net/uid-20564848-id-73480.html)

    浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_timer添加的定时器timer在内核的软中断中发生调用,__run_timers会spin_lock_irq(& ...

  7. 浅析linux内核中timer定时器的生成和sofirq软中断调用流程【转】

    转自:http://blog.chinaunix.net/uid-20564848-id-73480.html 浅析linux内核中timer定时器的生成和sofirq软中断调用流程 mod_time ...

  8. Linux 内核中的 Device Mapper 机制

    本文结合具体代码对 Linux 内核中的 device mapper 映射机制进行了介绍.Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机 ...

  9. Linux内核中双向链表的经典实现

    概要 前面一章"介绍双向链表并给出了C/C++/Java三种实现",本章继续对双向链表进行探讨,介绍的内容是Linux内核中双向链表的经典实现和用法.其中,也会涉及到Linux内核 ...

随机推荐

  1. 查看CentOS上Apache位置,版本,停止,启动

    查看Apache是否被安装: [root@asg11 ~]# find / -name 'httpd'/etc/sysconfig/httpd/etc/httpd/etc/logrotate.d/ht ...

  2. 用MFC时,如果程序崩溃,检查内存,然后注意GDI数量,在任务管理器里选项-查看列-GDI数量

    用MFC时,如果程序崩溃,检查内存,然后注意GDI数量,在任务管理器里选项-查看列-GDI数量

  3. Swift闭包

    把Swift中的 block 常见的声明和写法作一个总结.以免后续忘了,好查阅. // //  blockDemo.swift //  swiftDemo // //  Created by appl ...

  4. 脚本编程中的test、bash调试、变量计算、参数

    脚本编程中的test.bash调试.变量计算.参数 1.文件测试 -e FILE:测试文件是否存在 -f FILE:测试文件是否为普通文件 -d FILE:测试路径是否为目录 -r FILE:测试当前 ...

  5. centOS中wget的使用方法

    对于 Linux 用户来说,几乎每天都在使用它. 下面为大家介绍几个有用的 CentOS wget 小技巧,可以让你更加高效而灵活的使用CentOS wget. CentOS wget 使用技巧 $ ...

  6. UIStoryboard类介绍(如何从Storyboard中加载View Controller)

    如何从Storyboard中加载View Controller? 1. 首先了解下UIStoryboard类: @class UIViewController; @interface UIStoryb ...

  7. 解决Regsvr32: DllRegisterServer entry point was not found

    原因: 虽然项目里面包含了DEF文件, 但是尼玛没有配置项目属性让链接器处理它啊! 解决方案: 项目属性->链接器->输入->模块定义文件->{输入文件名}

  8. iOS - (集成支付宝SDK大坑总结)

    其实集成支付宝相对于集成微信支付来说,支付宝算是简单的了,后续有空再去研究微信支付,现目前先总结一下集成支付宝所遇到的坑,其实支付宝的坑也不算太多,细算下来大概5-6个左右,但是其报错方式有点恶心,不 ...

  9. SQL exist

    EXISTS = IN,意思相同不过语法上有点点区别,好像使用IN效率要差点,应该是不会执行索引的原因SELECT ID,NAME FROM A WHERE ID IN (SELECT AID FRO ...

  10. eclipse 插件未安装成功定位

    以gef未安装成功为例 在eclipse根目录下: eclipse –clean –console –noExit 右击窗口标题栏,属性,勾中快速编辑模式,这样可以在命令行窗口点击右键将剪贴板上的内容 ...