|
平台:x86
内核:2.6.24
time_after() , time_before() , time_after_eq() , time_before_eq() 其实是 4 个和时间比较相关的宏,定义在 include/linux/jiffies.h 文件中。
定义代码如下:
[C++] 纯文本查看 复制代码 #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)
这 4 个宏对时间的比较是和 jiffies 相关的比较。在宏中,参数 a 是 jiffies 在某个时刻的快照。在 time_after(a,b) 里,如果 a 所代表的时间比 b 靠后,那么返回真。如果 a 比 b 靠前,则 time_before(a,b) 返回真;而 time_after_eq(a,b) 和 time_before(a,b) 则分别比较 “靠后或者相等” 及 “靠前或者相等”。
另外,在这 4 个宏中,实际上都会用到 typecheck() 这个宏进行类型的检查。关于 typecheck() 的分析可参考:http://www.groad.net/bbs/read.php?tid-3066.html
为什么要使用上面的宏,而不是直接比较时间?
下面这段代码会因为 jiffies 的溢出而导致逻辑错误:
[C++] 纯文本查看 复制代码 unsigned long timeout;
init_device(); // 初始化设备
timeout = jiffies + HZ;
while (!device_is_ok()) { //如果设备还没准备好且没有超时就在此等待,如果超时则退出
if (jiffies > timeout) {
timeout()
exit;
}
}
other_works();
在 init_device(); 初始化设备后,设定在 1 秒钟超时时间 timeout 。如果 1 秒钟后设备还不能准备就绪( device_is_ok() == 1),那么进行超时处理,然后退出;如果在此期间设备准备好,那么就进行其它的工作 (other_works()) 。
上面的程序逻辑乍看上去没问题,但是如果考虑到 jiffies 的溢出,就会发现导致逻辑出错的可能。现在假设 jiffies 的值处于 (2^32 - 1 - 999) ~ (2^32 - 1) 这个范围中,同时 HZ 假设取值 1000 ,那么 timeout 的取值必定在 (0 ~ 999)之间。因为外设的响应速度比较慢,第一次通过 device_is_ok() 函数来判断设备是否就绪往往不会成功(函数返回 0),那么接下来就会再判断时间是否超时。也就是这时候,我们可以假设有这样一种情况,比如程序在进行 jiffies + HZ 计算时 jiffies 处于 2^32 - 5 这里,那么在加上 1000 后,则 timeout 的值则为 995 (发生了回绕) 。那么在接下来的比较中,jiffies 必定大于 timeout ,也就是说才执行第一次判断就发生了超时,这样显然是个错误。
而如果使用了 time_after() 这样的宏,它就不会出现这种错误。time_after() 宏(其它的类似),首先确保两个输入参数 a 和 b 为无符号长整型,然后再强制转换为又符号的长整型再进行比较。在又符号的长整型中,是存在负数的,比如 0xfffffffe 是 -2 而不是 4294967294 。所以,像上面的那种假设,我们是用 995 和 -2 在比较,故而不会有溢出的错误了。
需要注意的是,对于 32 位无符号整型,两个值之间的相差从逻辑上来讲应该小于 2,147,483,647 (0x7fffffff)。这是因为在将无符号整型转换为有符号整型后,到了 0x80000000 就会出现负数的情况,所以这时候再比较也会比较错误。也就是说,对于 HZ=1000 ,两个用于比较的 jiffies 所代表的时间值之差不应当超过 2,147,483,647/1000 秒,即 24.85 天。在实际的应用中,需要比较先/后的两个时间值之间一般都相差很小,范围大致在 1秒 -- 1天 左右,故用上面的宏可以成功的解决因为 32 位 jiffies 变量因回绕而导致逻辑错误的问题。
示例代码:
[C++] 纯文本查看 复制代码 #include <linux/config.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/time.h>
#include <linux/timer.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/types.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <asm/hardirq.h>
int delay = HZ;
int jit_busy(char *buf, char **start, off_t offset, int len, int *eof, void *data)
{
unsigned long j0, j1;
j0 = jiffies;
j1 = j0 + delay;
while (time_before(jiffies, j1)) {
cpu_relax();
}
len = sprintf (buf, "%9li %9li\n", j0, j1);
*start = buf;
return len;
}
int __init jit_init(void)
{
create_proc_read_entry ("jitbusy", 0, NULL, jit_busy, NULL);
return 0;
}
void __exit jit_cleanup(void)
{
remove_proc_entry("currentime", NULL);
}
module_init (jit_init);
module_exit (jit_cleanup);
运行输入:[root@centos jit]# dd bs=20 count=5 < /proc/jitbusy
8137857 8138107
8138108 8138358
8138358 8138608
8138608 8138858
8138858 8139108
5+0 records in
5+0 records out
100 bytes (100 B) copied, 5.0023 seconds, 0.0 kB/s 上面使用 time_before() 作忙等待,每 1 秒钟进行一次输出。dd 命令读取 /proc/jitbusy 文件时,每次读取 20 个字节数据,总共读取 5 次,也就是说整个读取过程经历 5 秒。从输出可以看出,每次输出的刚好间隔 1S 。 |
|