`
isiqi
  • 浏览: 16385575 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

linux内核中jiffies的回绕问题

阅读更多

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变量.pdf

    #### 五、jiffies的回绕问题 由于jiffies是一个无符号整型变量,当它的值达到最大值后(对于32位系统来说,最大值为 `(2^32)-1`),将会发生回绕现象,即jiffies的值会从最大值回绕到0。为了避免这种回绕对程序逻辑...

    linux中的jiffies变量文.pdf

    #### 四、jiffies的回绕问题 由于jiffies是无符号整型变量,因此当其值达到最大值时(32位系统为`(2^32)-1`,即4294967295)会发生溢出,其值将回绕至0。为了避免这种现象导致的问题,内核提供了一系列宏来帮助处理...

    需要了解的linux HZ Tick Jiffies.docx

    Jiffies 是 Linux 内核中的一个计数器,用于记录从系统启动以来经过的时间。Jiffies 的值是一个无符号长整型数,表示从系统启动以来经过的 Tick 数量。Jiffies 的值会不断增加,直到溢出(overflow),然后重新从 0 ...

    linux定时器和Jiffies.pdf

    HZ、Tick 和 Jiffies 是 Linux 内核中三个重要的时间相关概念,它们之间存在紧密的关系,并且在 Linux 内核中扮演着重要的角色。通过理解这些概念,我们可以更好地了解 Linux 操作系统的工作机理。

    linux内核定时器

    `jiffies`是Linux内核中的一个全局变量,用于跟踪系统运行的时间。每当系统硬件时钟中断发生,`jiffies`就会增加。这个值通常与系统的时钟频率有关,因此可以通过计算`jiffies`的变化来获取时间间隔。 四、实现10秒...

    Linux内核延时研究与函数代码分析

    Linux内核延时研究是Linux系统中一个重要的概念,主要涉及到驱动程序的延时处理和函数代码的分析。在Linux系统中,驱动程序需要与硬件同步,需要非常短的延迟来实现同步。 Linux内核提供了多种延时函数,包括...

    Linux内核中断机制

    本文主要探讨Linux内核中的时钟中断机制以及动态定时器机制。 #### 二、Linux内核时钟中断机制 Linux内核时钟中断机制是确保系统定时任务得以准确执行的核心部分。通过定期触发的时钟中断,系统能够维持时间的准确...

    linux内核时间[PPT下载]

    - 在Linux内核中,`jiffies`是一个全局变量,记录了自系统启动以来发生的时钟中断次数。时间片(Time Slice)是分配给每个进程运行的时间长度,通常以jiffies为单位计算。 4. **调度器与时间管理** - Linux内核...

    Linux内核引导过程

    Linux内核引导过程是操作系统启动过程中一个非常关键的步骤。在这一过程中,内核通过一系列复杂的初始化操作,为系统的正常运行奠定基础。本文将深入探讨Linux内核的引导过程,重点介绍内核引导的第一部分——核心...

    Linux2.6内核标准教程(共计8-- 第1个)

    Linux内核是Linux操作系统中最核心的部分,用于实现对硬件部件的编程控制和接口操作。《Linux2.6内核标准教程》深入、系统地讲解了 Linux内核的工作原理,对Linux内核的核心组件逐一进行深入讲解。 全书共8章,首先...

    深入理解Linux内核 中文第三版 第6章

    2. **jiffies概念**:在Linux内核中,`jiffies`是一个全局变量,记录自系统启动以来的 ticks(时钟中断)数量。每个tick代表了系统的最小时间单位,通常是毫秒或微秒。定时器的到期时间通常与jiffies关联。 3. **...

    linux 内核定时器 编程

    1. `timer_list`结构体:这是Linux内核2.6.x版本之前的定时器表示,包含定时器的到期时间、回调函数、数据指针等字段。 2. `hrtimer`结构体:从Linux内核2.6.27引入,提供纳秒级精度,比`timer_list`更高效,适用于...

    Linux内核的数据类型.doc

    在Linux内核中,内核对象的指针通常定义为unsigned long类型,因为这个类型可以保存指针值,并且可以避免在不同的体系结构中出现的大小问题。同时,Linux内核也定义了一些特定的数据类型,例如intptr_t和uintptr_t,...

    Linux内核定时器的实例

    在Linux内核中,定时器由`struct timer_list`结构体表示,它包含了定时器的相关信息,如定时器的函数指针(用于设定超时后执行的回调函数)、定时器的到期时间和当前状态等。内核定时器有两种类型:软定时器和硬...

    linux内核驱动学习

    总结来说,Linux内核驱动中的延时处理涉及到HZ的计算、jiffies计数器、宏定义、等待队列、`schedule()`函数、`schedule_timeout()`以及忙等待延时函数等。理解并正确使用这些工具,能够有效地管理驱动程序中的延时,...

    深入理解linux内核-6

    时间戳记录了事件发生的时间,而jiffies是Linux内核中的一种时间单位,通常用来表示自系统启动以来经过的滴答数。 其次,定时器在Linux内核中的实现和管理是一个关键部分。内核提供了两种主要类型的定时器:软...

    Linux2.6内核标准教程(共计8--第8个)

    Linux内核是Linux操作系统中最核心的部分,用于实现对硬件部件的编程控制和接口操作。《Linux2.6内核标准教程》深入、系统地讲解了 Linux内核的工作原理,对Linux内核的核心组件逐一进行深入讲解。 全书共8章,首先...

    Linux2.6内核标准教程(共计8--第6个)

    Linux内核是Linux操作系统中最核心的部分,用于实现对硬件部件的编程控制和接口操作。《Linux2.6内核标准教程》深入、系统地讲解了 Linux内核的工作原理,对Linux内核的核心组件逐一进行深入讲解。 全书共8章,首先...

    linux内核源代码分析-定时器与时间管理.ppt

    - 为了避免jiffies回绕带来的问题,内核提供了如time_after()等宏来检查时间间隔,确保正确处理。 综上所述,Linux内核中的时间管理和定时器涉及到复杂的硬件交互、中断处理、时间表示和精度控制,以及内存管理等...

    Linux2.6内核标准教程(共计8--第3个)

    Linux内核是Linux操作系统中最核心的部分,用于实现对硬件部件的编程控制和接口操作。《Linux2.6内核标准教程》深入、系统地讲解了 Linux内核的工作原理,对Linux内核的核心组件逐一进行深入讲解。 全书共8章,首先...

Global site tag (gtag.js) - Google Analytics