linux如何在jiffies中处理溢出?

时间:2011-11-21 02:52:46

标签: c linux linux-kernel overflow

假设我们有以下代码:

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)

此代码如何防止溢出问题?

3 个答案:

答案 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等功能:

     

include/linux/jiffies.h:93

 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_beforetime_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,但我们确实超时了。

time_before(jiffies,timeout)

这对值的差异进行了签名比较,而不是值本身,如果尚未达到超时,则再次返回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位,因此它需要更长的时间来换行,但如果机器运行的时间足够长,它仍然可以。 (并且通常它会在启动后不久进行换行,以便更快地捕获这些错误。)