转自:http://decimal.blog.51cto.com/1484476/410673

Title: jiffies溢出与时间先后比较
编制: chinakapok@sina.com
日期:2005-05-25

1. 概述
在Linux内核中,TCP/IP协议栈在很多用到时间比较的地方都使用了jiffies?本文介绍了什么是jiffies,jiffies溢出可能造成的问题,使用time_after等宏来正确地比较时间及其背后的原理。

2. jiffies简介

2.1 时钟中断
在Linux内核中,TCP/IP协议栈在很多用到时间比较的地方都使用了jiffies。
那么jiffies是什么呢?我们知道,操作系统应该能够在将来某个时刻准时调度某个任务,所以需要一种能保证任务准时调度运行的机制。希望支持每种操作系统的微处理器必须包含一个可周期性中断它的可编程间隔定时器。这个周期性中断被称为系统时钟滴答(或system timer),它象节拍器一样来组织系统任务,也称为时钟中断。
时钟中断的发生频率设定为HZ,HZ是一个与体系结构无关的常数,在文件<linux/param.h>中定义。至少从2.0版到 2.1.43版,Alpha平台上Linux定义HZ的值为1024,而其他平台上定义为100。时钟中断对操作系统是非常重要的,当时钟中断发生时,将周期性地执行一些功能,例如:
. 更新系统的uptime
. 更新time of day
. 检查当前任务的时间片是否用光,如果是则需要重新调度任务
. 执行到期的dynamic timer
. 更新系统资源使用统计和任务所用的时间统计

2.2 jiffies及其溢出
全局变量jiffies取值为自操作系统启动以来的时钟滴答的数目,在头文件<linux/sched.h>中定义,数据类型为 unsigned long volatile (32位无符号长整型)。关于jiffies为什么要采用volatile来限定,可参考《关于volatile和jiffies.txt》。
jiffies转换为秒可采用公式:(jiffies/HZ)计算,将秒转换为jiffies可采用公式:(seconds*HZ)计算。
当时钟中断发生时,jiffies值就加1。因此连续累加一年又四个多月后就会溢出(假定HZ=100,1个jiffies等于1/100 秒,jiffies可记录的最大秒数为(2^32 -1)/100=42949672.95秒,约合497天或1.38年),即当取值到达最大值时继续加1,就变为了0。
在Vxworks操作系统中,定义HZ的值为60,因此连续累加两年又三个多月后也将溢出(jiffies可记录的最大秒数为约合2.27 年)。如果在Vxworks操作系统上的应用程序对jiffies的溢出没有加以充分考虑,那么在连续运行两年又三个多月后,这些应用程序还能够稳定运行吗?
下面我们来考虑jiffies的溢出,我们将从以下几个方面来阐述:
. 无符号整型溢出的具体过程
. jiffies溢出造成程序逻辑出错
. Linux内核如何来防止jiffies溢出
. time_after等比较时间先/后的宏背后的原理
. 代码中使用time_after等比较时间先/后的宏

3. 无符号整型溢出的具体过程
我们首先来看看无符号长整型(unsigned long)溢出的具体过程。实际上,无符号整型的溢出过程都很类似。为了更具体地描述无符号长整型溢出的过程,我们以8位无符号整型为例来加以说明。
8位无符号整型从0开始持续增长,当增长到最大值255时,继续加1将变为0,然后该过程周而复始:
0, 1, 2, ..., 253, 254, 255,
0, 1, 2, ..., 253, 254, 255,
...

4. jiffies溢出造成程序逻辑出错
下面,通过一个例子来看jiffies的溢出。
例4-1:jiffies溢出造成程序逻辑出错
unsigned long timeout = jiffies + HZ/2; /* timeout in 0.5s */

/* do some work ... */
do_somework();

/* then see whether we took too long */
if (timeout > jiffies) {
/* we did not time out, call no_timeout_handler() ... */
no_timeout_handler();

} else {
/* we timed out, call timeout_handler() ... */
timeout_handler();
}
本例的目的是从当前时间起,如果在0.5秒内执行完do_somework(),则调用no_timeout_handler()。如果在0.5秒后执行完do_somework(),则调用timeout_handler()。
我们来看看本例中一种可能的溢出情况,即在设置timeout并执行do_somework()后,jiffies值溢出,取值为0。设在设置 timeout后,timeout的值临近无符号长整型的最大值,即小于2^32-1。设执行do_somework()花费了1秒,那么代码应当调用 timeout_handler()。但是当jiffies值溢出取值为0后,条件timeout > jiffies成立,jiffies值(等于0)小于timeout(临近但小于2^32-1),尽管从逻辑上讲jiffies值要比timeout大。但最终代码调用的是no_timeout_handler(),而不是timeout_handler()。

5. Linux内核如何来防止jiffies溢出
Linux内核中提供了以下四个宏,可有效地解决由于jiffies溢出而造成程序逻辑出错的情况。下面是从Linux Kernel 2.6.7版本中摘取出来的代码:
/*
* These inlines deal with timer wrapping correctly. You are
* strongly encouraged to use them
* 1. Because people otherwise forget
* 2. Because if the timer wrap changes in future you won't have to
* alter your driver code.
*
* time_after(a,b) returns true if the time a is after time b.
*
* Do this with "<0" and ">=0" to only test the sign of the result. A
* good compiler would generate better code (and a really good compiler
* wouldn't care). Gcc is currently neither.
*/
#define time_after(a,b)
(typecheck(unsigned long, a) &&
typecheck(unsigned long, b) &&
((long)(b) - (long)(a) < 0))
#define time_before(a,b) time_after(b,a)

#define time_after_eq(a,b)
(typecheck(unsigned long, a) &&
typecheck(unsigned long, b) &&
((long)(a) - (long)(b) >= 0))
#define time_before_eq(a,b) time_after_eq(b,a)

在宏time_after中,首先确保两个输入参数a和b的数据类型为unsigned long,然后才执行实际的比较。这是以后编码中应当注意的地方。

6. time_after等比较时间先后的宏背后的原理
那么,上述time_after等比较时间先/后的宏为什么能够解决jiffies溢出造成的错误情况呢?
我们仍然以8位无符号整型(unsigned char)为例来加以说明。仿照上面的time_after宏,我们可以给出简化的8位无符号整型对应的after宏:
#define uc_after(a, b) ((char)(b) - (char)(a) < 0)

设a和b的数据类型为unsigned char,b为临近8位无符号整型最大值附近的一个固定值254,下面给出随着a(设其初始值为254)变化而得到的计算值:
a     b     (char)(b) - (char)(a)
254     254         0
255             - 1
0             - 2
1             - 3
...
124             -126
125             -127
126             -128
127             127
128             126
...
252             2
253             1

从上面的计算可以看出,设定b不变,随着a(设其初始值为254)不断增长1,a的取值变化为:
254, 255, (一次产生溢出)
0, 1, ..., 124, 125, 126, 127, 126, ..., 253, 254, 255, (二次产生溢出)
0, 1, ...
...

而(char)(b) - (char)(a)的变化为:
0, -1,
-2, -3, ..., -126, -127, -128, 127, 126, ..., 1, 0, -1,
-2, -3, ...
...

从上面的详细过程可以看出,当a取值为254,255, 接着在(一次产生溢出)之后变为0,然后增长到127之前,uc_after(a,b)的结果都显示a是在b之后,这也与我们的预期相符。但在a取值为 127之后,uc_after(a,b)的结果却显示a是在b之前。
从上面的运算过程可以得出以下结论:
使用uc_after(a,b)宏来计算两个8位无符号整型a和b之间的大小(或先/后,before/after),那么a和b的取值应当满足以下限定条件:
. 两个值之间相差从逻辑值来讲应小于有符号整型的最大值。
. 对于8位无符号整型,两个值之间相差从逻辑值来讲应小于128。
从上面可以类推出以下结论:
对于time_after等比较jiffies先/后的宏,两个值的取值应当满足以下限定条件:
两个值之间相差从逻辑值来讲应小于有符号整型的最大值。
对于32位无符号整型,两个值之间相差从逻辑值来讲应小于2147483647。
对于HZ=100,那么两个时间值之间相差不应当超过2147483647/100秒 = 0.69年 = 248.5天。对于HZ=60,那么两个时间值之间相差不应当超过2147483647/60秒 = 1.135年。在实际代码应用中,需要比较先/后的两个时间值之间一般都相差很小,范围大致在1秒~1天左右,所以以上time_after等比较时间先 /后的宏完全可以放心地用于实际的代码中。

7. 代码中使用time_after等比较时间先/后的宏
下面代码是针对例4-1修改后的正确代码:
例7-1:在例4-1基础上使用time_before宏后的正确代码
unsigned long timeout = jiffies + HZ/2; /* timeout in 0.5s */

/* do some work ... */
do_somework();

/* then see whether we took too long */
if (time_before(jiffies, timeout)) {
/* we did not time out, call no_timeout_handler() ... */
no_timeout_handler();

} else {
/* we timed out, call timeout_handler() ... */
timeout_handler();
}

8. 结论
系统中采用jiffies来计算时间,但由于jiffies溢出可能造成时间比较的错误,因而强烈建议在编码中使用time_after等宏来比较时间先后关系,这些宏可以放心使用。

9. 参考资料
[1] 在《Linux Kernel Development, Second Edition》Chapter 10中给出了与时间相关的HZ、jiffies、延迟、各种timer等更全面,更精彩而有趣的讨论。
[2]《关于volatile和jiffies》,可见http://ggnm.coku.com/。

为何time_before 起作用【转】的更多相关文章

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

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

  2. if __name__== "__main__" 的意思(作用)python代码复用

    if __name__== "__main__" 的意思(作用)python代码复用 转自:大步's Blog  http://www.dabu.info/if-__-name__ ...

  3. (转载)linux下各个文件夹的作用

    linux下的文件结构,看看每个文件夹都是干吗用的/bin 二进制可执行命令 /dev 设备特殊文件 /etc 系统管理和配置文件 /etc/rc.d 启动的配置文件和脚本 /home 用户主目录的基 ...

  4. github中的watch、star、fork的作用

    [转自:http://www.jianshu.com/p/6c366b53ea41] 在每个 github 项目的右上角,都有三个按钮,分别是 watch.star.fork,但是有些刚开始使用 gi ...

  5. web.xml中welcome-file-list的作用

    今天尝试使用struts2+ urlrewrite+sitemesh部署项目,结果发现welcome-file-list中定义的欢迎页不起作用: <welcome-file-list> & ...

  6. web.xml中load-on-startup的作用

    如下一段配置,熟悉DWR的再熟悉不过了:<servlet>   <servlet-name>dwr-invoker</servlet-name>   <ser ...

  7. SQLSERVER中NULL位图的作用

    SQLSERVER中NULL位图的作用 首先感谢宋沄剑提供的文章和sqlskill网站:www.sqlskills.com,看下面文章之前请先看一下下面两篇文章 SQL Server误区30日谈-Da ...

  8. 电容与EMC-电容不同功能时对整板EMC的作用

    一般我们的pcb板的器件有很多种类,但是值得特别关注的,很多人都会说是BGA.接口.IC.晶振之类,因为这些都是layout功能模块以及设计难点.然而数量上占绝对优势的器件却是阻容器件,之前围殴阻抗时 ...

  9. FTP的搭建与虚拟目录作用<之简单讲解>

    操作系统:win7 VS2010编写WebService与在IIS的发布<之简单讲解>中我已经说了IIS安装与使用,不明白的可以跳过去看. 1.添加FTP站点 2. 3. 4. 5. zq ...

随机推荐

  1. 【BZOJ4821】[SDOI2017]相关分析(线段树)

    [BZOJ4821][SDOI2017]相关分析(线段树) 题面 BZOJ 洛谷 题解 看看询问要求的东西是什么.把所有的括号拆开,不难发现要求的就是\(\sum x,\sum y,\sum xy,\ ...

  2. CodeForces 1110F Nearest Leaf | 线段树/换根

    我--又诈尸了-- 代码几乎都不会写了,打场CF居然上分啦,开心!(虽然还是比不过列表里的各路神仙) 题目链接 题目描述 一棵\(n\)个点的有根树,规定一种dfs序(规则:编号小的点优先dfs),\ ...

  3. 【转】NFS服务配置与mount nfs时-o nolock的问题

    NFS文件系统挂载步骤 1.创建共享目录 #mkdir /home/hellolinux/nfs 2.创建或修改/etc/exports文件 #vi /etc/exports home/helloli ...

  4. Asp: Server.mapPath() 注意事项

    今天下午,在删除一个用户后,竟然发现该用户上传的图片还能正常显示. 郁闷了,怎么会这样? 为了再次验证这个结果,我新注册一个用户名,然后上传3张图片,接着删除这个用户,再查看刚刚上传的3张图片,竟然没 ...

  5. Softmax 损失-梯度计算

    本文介绍Softmax运算.Softmax损失函数及其反向传播梯度计算, 内容上承接前两篇博文 损失函数 & 手推反向传播公式. Softmax 梯度 设有K类, 那么期望标签y形如\([0, ...

  6. httpd服务的安装、配置

    httpd服务是架设网站的必须服务下面我就来说下怎么安装配置 1.安装httpd服务 输入下面命令进行安装(如果没网络或者安装速度慢的请设置本地yum源进行安装,设置教程请点击这里查看) yum in ...

  7. 将本地的代码推送到公网的github账号去

    将本地的代码推送到公网的github账号去 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 最近工作上需要用到github账号,拜读了一位叫廖雪峰的大神的文档,把git的前世今生说的 ...

  8. mysql 8.0.12 创建新的数据库、用户并授权

    Mysql安装成功后,默认的root用户密码为空,你可以使用以下命令来创建root用户的密码: [root@host]# mysqladmin -u root password "new_p ...

  9. __attribute__ 机制详解

    GNU C 的一大特色就是__attribute__ 机制.__attribute__ 可以设置函数属性(Function Attribute).变量属性(Variable Attribute)和类型 ...

  10. 深入理解 BFC

    W3C 规范中 BFC的定义: 浮动元素和绝对定位元素,非块级盒子的块级容器(例如 inline-blocks, table-cells, 和 table-captions),以及overflow值不 ...