关于 jiffies 的回绕 ,时钟 - 回绕 - 安全 ,timer - wraparound - safe

ReverseEngineering 2009-09-27 04:58:51
看“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 ,怎么办?

而按上文的说法:
如果HZ为1000,那么回绕时间将只有50天左右。

采用宏的处理,那么25天左右就要面临无符号到有符号的回绕问题,岂不是问题更严重了?


高手给与解释一下,我这个想法是哪里出了问题?
...全文
417 4 打赏 收藏 转发到动态 举报
写回复
用AI写文章
4 条回复
切换为时间正序
请发表友善的回复…
发表回复
lvyinghong 2009-09-28
  • 打赏
  • 举报
回复
但你举的例子,人家time_after 宏还是可以得到正确结果的。

如果两个时间之差 小于 2 的 31 次方,这个宏的结果都是对的。网上都是这么说的^_^
ReverseEngineering 2009-09-28
  • 打赏
  • 举报
回复
楼上的, 宏按有符号处理, 并不能解决全部的回绕问题!
lvyinghong 2009-09-27
  • 打赏
  • 举报
回复
此时, J2 < < J1 < timeout ,怎么办?

----------这个没错,但你觉的

time_after(J2,timeout) , time_after(timeout,J1)
的结果有问题吗?

4,436

社区成员

发帖
与我相关
我的任务
社区描述
Linux/Unix社区 内核源代码研究区
社区管理员
  • 内核源代码研究区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧