假设我们有以下代码:
if (timeout > jiffies)
{
/* we did not time out, good ... */
}
else
{
/* we timed out, error ...*
}
当jiffies值不溢出时,此代码工作正常。 但是,当jiffies溢出并回绕到零时,此代码无法正常工作。
Linux显然提供了用于处理此溢出问题的宏
#define time_before(unknown, known) ((long)(unkown) - (long)(known) < 0)
以上代码在被这个宏替换时应该可以安全地防止溢出:
// SAFE AGAINST OVERFLOW
if (time_before(jiffies, timeout)
{
/* we did not time out, good ... */
}
else
{
/* we timed out, error ...*
}
但是,time_before(以及其他时间_宏)背后的理由是什么?
time_before(jiffies,timeout)将扩展为
((long)(jiffies) - (long)(timeout) < 0)
此代码如何防止溢出问题?
答案 0 :(得分:5)
让我们试一试:
#define time_before(unknown, known) ((long)(unkown) - (long)(known) < 0)
我会通过说long
只有两个字节来简化很多事情,所以在十六进制中它可以有[0, 0xFFFF]
范围内的值。
现在,它已经签名,因此范围[0,0xFFFF]可以分为两个单独的范围[0,0x7FFF],[0x8000,0xFFFF]。那些对应于值[0,32767],[ - 32768,-1]。这是一个图表:
[0x0 - - - 0xFFFF]
[0x0 0x7FFF][0x8000 0xFFFF]
[0 32,767][-32,768 -1]
说timeout
是32,000。我们想检查一下是否在我们的超时时间内,但事实上我们已经溢出,所以jiffies
是-31,000。因此,如果我们天真地尝试评估jiffies < timeout
,我们会得到True
。但是,插入值:
time_before(jiffies, offset)
== ((long)(jiffies) - (long)(offset) < 0)
== (-31000 - 32000 < 0) // WTF is this. Clearly NOT -63000
== (-31000 - 1768 - 1 - 30231 < 0) // simply expanded 32000
== (-32768 - 1 - 30232 < 0) // this -1 causes an underflow
== (32767 - 30232 < 0)
== (2535 < 0)
== False
jiffies
是4个字节,而不是2个字节,但同样的原则适用。这有帮助吗?
答案 1 :(得分:3)
例如,请参见:http://fixunix.com/kernel/266713-%5Bpatch-1-4%5D-fs-autofs-use-time_before-time_before_eq-etc.html
将一些固定的小常量检查溢出的代码转换为使用time_before。为什么?
我只是总结了与定义相关的评论 time_after等功能:
93 /*
94 * These inlines deal with timer wrapping correctly. You are
95 * strongly encouraged to use them
96 * 1. Because people otherwise forget
97 * 2. Because if the timer wrap changes in future you won't have to
98 * alter your driver code.
99 *
100 * time_after(a,b) returns true if the time a is after time b.
101 *
因此,time_before
和time_after
是处理溢出的更好方法。
您的测试用例更可能是timeout < jiffles
(没有溢出)而不是timeout > jiffles
(溢出):
unsigned long jiffies = 2147483658;
unsigned long timeout = 10;
如果您将超时更改为
unsigned long timeout = -2146483000;
什么是答案?
或者您可以从
更改支票printf("%d",time_before(jiffies,timeout));
到
printf("%d",time_before(jiffies,old_jiffles+timeout));
其中old_jiffles是在计时器开始时保存的jiffles值。
所以,我认为time_before的用法可以是:
old_jiffles=jiffles;
timeout=10; // or even 10*HZ for ten-seconds
do_a_long_work_or_wait();
//check is the timeout reached or not
if(time_before(jiffies,old_jiffles+timeout) ) {
do_another_long_work_or_wait();
} else {
printk("ERRROR: the timeout is reached; here is a problem");
panic();
}
答案 2 :(得分:0)
鉴于jiffies
是一个无符号值,一个简单的比较在一个环绕点(其中有符号值将从正跳转到负数)是安全的,但在另一个点上不安全(其中有符号值将从负数跳转)为正,无符号值从高到低跳转。它是对宏旨在解决的第二点的保护。
有一个基本假设,timeout
最初在某个最近时间点计算为jiffies + some_offset
- 具体来说,不到一半的范围变量。如果您尝试测量的时间超过此时间,那么事情就会中断,您将得到错误的答案。
如果我们假装jiffies
是16位宽,以方便解释(类似于其他答案):
timeout > jiffies
这是一个无符号比较,如果我们尚未达到超时,则返回true。一些例子:
timeout == 0x0300, jiffies == 0x0100
:结果是正确的,正如所料。timeout == 0x8100, jiffies == 0x7F00
:结果是正确的,正如所料。timeout == 0x0100, jiffies == 0xFF00
:哎呀,结果是假的,但我们没有真的达到超时,它只是把计数器包好了。timeout == 0x0100, jiffies == 0x0300
:结果是假的,正如所料。timeout == 0x7F00, jiffies == 0x8100
:结果是假的,正如所料。timeout == 0xFF00, jiffies == 0x0100
:oops,结果为true,但我们确实超时了。这对值的差异进行了签名比较,而不是值本身,如果尚未达到超时,则再次返回true。如果维持上述假设,则使用相同的示例:
timeout == 0x0300, jiffies == 0x0100
:结果是正确的,正如所料。timeout == 0x8100, jiffies == 0x7F00
:结果是正确的,正如所料。timeout == 0x0100, jiffies == 0xFF00
:结果是正确的,正如所料。timeout == 0x0100, jiffies == 0x0300
:结果是假的,正如所料。timeout == 0x7F00, jiffies == 0x8100
:结果是假的,正如所料。timeout == 0xFF00, jiffies == 0x0100
:结果是假的,正如所料。如果在计算timeout
时使用的偏移量太大,或者在计算timeout
后允许太多时间通过,则结果仍然可能是错误的。例如。如果你计算timeout
一次然后只是反复测试,那么time_before
最初会为真,然后在偏移时间过后变为假 - 然后在0x8000时间之后再次变回真已经过去了(不管多长时间;它取决于滴答率)。这就是为什么当你达到超时时,你应该记住这一点并停止检查时间(或重新计算新的超时)。
在真实内核中,jiffies
超过16位,因此它需要更长的时间来换行,但如果机器运行的时间足够长,它仍然可以。 (并且通常它会在启动后不久进行换行,以便更快地捕获这些错误。)