time_after那些事儿

来源:互联网 发布:梦里花落知多少百度云 编辑:程序博客网 时间:2024/04/29 23:29
time_after那些事儿
//这是很trivial的一个小问题,个人觉得挺有趣的就写下来了,阅读时请不要有太多的期待。

《Linux内核设计与实现第三版》P172中,介绍了jiffies的回绕问题。简单来说,考虑如下程序:

unsigned long jiffies;unsigned long timeout = jiffies + HZ/2;//......if (timeout > jiffies) {//没有超时,很好}else {//超时了,发生错误}

不妨假设当前机器的unsigned long类型变量长度为4字节。
其中jiffies是个不断在增大的unsigned long,timeout可以看作比jiffies“大不了多少”的unsigned long。当jiffies变得比2^32-1还要大的时候会发生溢出,“回绕”(wrap around)到0附近。此时,判断语句为真,虽然实际上超时了,但是判断为没有超时。

---分割线---

以上为问题背景。为了防止发生溢出,Linux内核提供了一组宏解决这个问题。其中宏time_after(a, b)是考虑可能的溢出情况后判断时间a是否在时间b之后(即“b < a”)。书中给出了time_after的一个简化版本:
#define time_after(a, b) ((long)(b) - (long)(a) < 0)
代入a = jiffies, b = timeout, 上面的宏展开为:
time_after(jiffies, timeout) ((long)(timeout) - (long)(jiffies) < 0)
上面程序已经交代jiffies和timeout均为unsigned long类型,宏做的事情很简单:分别将它们强转换为(signed )long类型,然后相减判断时候小于零。下面讲述为什么这段宏能够绕开溢出的情况。
首先要祭出CSAPP第二章最有价值的一张图,这张图完美展示了符号数和无符号数之间的关系,没有半句废话【P47图2-17】:





考虑两种极端情况,配合上图分析。其中esp代表一个很小的数字:

1. 当timeout = 2^32-1-esp1,jiffies = esp2,即timeout是个很大接近unsigned long上限的数字,jiffies发生了溢出时,此时timeout强转后变为-esp1(<0),jiffies强转后不变为esp2(>0),
(long)(timeout) - (long)(jiffies) = -esp1 - esp2 <0, 说明发生了溢出,判断正确。
2. 当timeout = 2^31-1,jiffies = 2^31+esp,即timeout刚好是signed long上限的时候,timeout强转后不变为2^31-1,jiffies强转后为esp-2^31(<0)是个非常小的负数,注意两个long相减结果为long:
(long)(timeout) - (long)(jiffies) = (long)(2^31-1 - (esp-2^31)) = long(2^32-1-esp) = -1-esp <0,说明溢出,判断正确。

什么时候这个判断会发生错误?实际上,当a和b相差>=2^31时判断会失效,但是这个时候早已经不是“大不了多少”了。。

接下来当然要看看time_after实际长什么样了,内核3.13版本include/linux/jiffies.h, line 101给出了time_after的定义(其他内核版本应该没有太大变化):
#define time_after(a,b)         \(typecheck(unsigned long, a) && \typecheck(unsigned long, b) && \((long)((b) - (a)) < 0))
区别有二:多了两个宏typecheck判断;先强转后相减变成先相减后强转。
先看typecheck是什么东东,找到typecheck的定义:
#define typecheck(type,x) \({      type __dummy; \typeof(x) __dummy2; \(void)(&__dummy == &__dummy2); \1; \})
内核注释是这么写的:Check at compile time that something is of a particular type.Always evaluates to 1 so you may use it easily in comparisons.就是说,这段宏的作用是判断值x是否为类型type,若不是则会在编译期间提示错误。定义变量__dummy是type类型,变量__dummy2是x的类型,取它们的地址,若在判断两个地址是否相等,发现两个指针指向类型不一致时,在这一行会发生编译错误。否则到最后一行,整个代码段值为1(注意花括号,这是一个代码段,代码段的值为最后一个表达式的值)
写个test验证一下:
//test_tpyecheck#define typecheck(type,x) \({\type __dummy; \typeof(x) __dummy2; \(void)(&__dummy == &__dummy2); \1; \})int main() {long x;typecheck(unsigned long, x);return 0;}
gcc编译,返回错误:
test_type.c: In function `int main()':test_type.c:11: error: comparison between distinct pointer types `long unsigned int*' and `long int*' lacks a cast
这么高大上的类型检查见过么?反正我第一次见,看懂以后都不敢说自己会写C了。。。
time_after(a, b)先对a b作判断是否都为unsigned long类型,如果是再作下面的判断。
接下来讲讲第二个区别,先相减后强转。两种写法没有实际的区别,私以为“先相减后强转”更容易理解其正确性:回到上面的第二种极端情况,timeout = 2^31-1,jiffies = 2^31+esp,此时两个unsigned long相减为负数“溢出”为很大的正数,在强转后又变为负数-1-esp,理解起来更加简洁。

ps. C++里面有现成的类型判断运算符typeid,typecheck宏可以用一个语句代替:

#define typecheck_cpp(type,x) (typeid(x) == typeid(type))


0 0