[linux内核]jiffies如何避免时钟回绕问题?
linux内核中全局变量jiffies用来记录系统启动以来经历的时钟节拍总数,是一个unsgined long
类型的变量,每次时钟中断会增加jiffies的值,一秒的时钟中断是hz,那么jiffies一秒内增加的数量就是hz,系统运行时间的秒数就是jiffies/hz。系统启动的时候讲jiffies的值设置为0,这样看起来32位的jiffies其实很容易溢出。
1. jiffes的定义
jiffies的定义位于/linux/jiffies.h
文件中。
extern unsigned long volatile __cacheline_aligned_in_smp __jiffy_arch_data jiffies;
2. jiffies32位下的溢出
unsigned long timeout = jiffies + HZ/2; // 0.5秒后超时
if(timeout > jiffies){
// 没有到时间
}
else{
// 这里超时了
}
上面的代码如果发生在回绕情况下,就判断错了,所以在linux内核里面用于比较两个时间提供了time_before
、time_after
、time_before_eq
、time_after_eq
,time_in_range
,time_in_range_open
代码如下:
/*
* 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) - (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) - (b)) >= 0))
#define time_before_eq(a,b) time_after_eq(b,a)
/*
* Calculate whether a is in the range of [b, c].
*/
#define time_in_range(a,b,c) \
(time_after_eq(a,b) && \
time_before_eq(a,c))
/*
* Calculate whether a is in the range of [b, c).
*/
#define time_in_range_open(a,b,c) \
(time_after_eq(a,b) && \
time_before(a,c))
那么正确的使用jiffies来延迟操作的正确姿势应该是这样的:
unsigned long timeout = jiffies + HZ/2; // 0.5秒后超时
if(time_before(jiffies, timeout)){
// 没有到时间
}
else{
// 这里超时了
}
我看代码的内核版本是5.8.0,在老版本的linux代码里面time_after
应该是这样的,看起来新版本的实现更简洁且丰富了:
#define time_after(unknown, known) ((long)(known) - (long)(unknown) > 0)
#define time_before(unknown, known) ((long)(unknown) - (long)(known) < 0)
#define time_after_eq(unknown, known) ((long)(known) - (long)(unknown) >= 0)
#define time_before_eq(unknown, known) ((long)(unknown) - (long)(known) <= 0)
上面注释已经很详细了。我们看到time_after
,只是将计算b-a
的结果转换成了一个整数类型。这样就解决了时钟回绕即32位无符号溢出的问题了吗?答案是肯定的。 不过这得从二进制的角度来看问题,我们先拿老的代码来分析,将两个unsigned long
类型的值转换为long
类型的值(一个值溢出)相减:
unsigned long b = 0xFFFFFFFFFFFFFFFF; // b+1之后溢出
unsigned long c = 0xFFFFFFFFFFFFFFFE; // 不会溢出
b = b+1; // 此时b的十六进制为 0x0000000000000000
cout <<"b="<< b << " c=" << c << endl;
cout <<"(long)b="<< (long)b << " (long)c=" << (long)c << endl;
cout <<"(long)b - (long)c=" << (long)b-(long)c << endl;
bitset<64> bb((long)b);
bitset<64> bc((long)c);
cout <<"bitset b=" << bb << endl;
cout <<"bitset c=" << bc << endl;
输出如下:
b=0 c=18446744073709551614
(long)b=0 (long)c=-2
(long)b - (long)c=2
bitset b=0000000000000000000000000000000000000000000000000000000000000000
bitset c=1111111111111111111111111111111111111111111111111111111111111110
我用64位的来做演示,不过这并不影响结果的正确性。从上面可以看出其实unsigned long
在溢出之后从long
类型的角度来看其实就变成了正数,而上面很大的正数就变成了负数,相减得到的结果仍然是正确的。那么就是说溢出后从二进制角度来讲取差值或者说相对值是正确的,如果是其他方式展示,你得用正确的方式去看待它。
在c/c++里面,有符号数的最高位为0表示正数,为1表示负数。
这里的差值结果就好比孩子画了一幅画,你觉得什么鬼东西,画画技术这么差,在TA看来可不一定,TA按照自己的想法表达出来了,只是你审视的视角不一样而已。
按照上面的分析我们再来看一下整数值的减法,我用一个LONG_MAX-LONG_MIN
如果还是用long
类型去看待这个结果的话,那肯定是不对的,那如果我用unsigned long
来看待,结果就对了。
比如下面的代码:
long b = 0x7FFFFFFFFFFFFFFF;
long c = 0x8000000000000000;
cout <<"b="<< b << " c=" << c << endl;
cout <<"(unsigned long)(b - c)=" << (unsigned long)(b-c) << endl;
bitset<64> bb((long)b);
bitset<64> bc((long)c);
bitset<64> bd((unsigned long)(b-c));
cout <<"bitset b=" << bb << endl;
cout <<"bitset c=" << bc << endl;
cout <<"bitset b-c=" << bd << endl;
结果是:
b=9223372036854775807 c=-9223372036854775808
(unsigned long)(b - c)=18446744073709551615
bitset b=0111111111111111111111111111111111111111111111111111111111111111
bitset c=1000000000000000000000000000000000000000000000000000000000000000
bitset b-c=1111111111111111111111111111111111111111111111111111111111111111 // b-c = b + (-c) = b + (c取反+1)




