Linux内核源码阅读系列(1)-可能令人迷惑的C语言语法

Posted by jcadam - 08/01/09 at 05:01 下午

Linux内核代码中使用了很多自定义的宏,如果初次开始读代码可能会感到迷惑。比如下面这个:

if (unlikely(!mm)) {
        next->active_mm = oldmm;
        atomic_inc(&oldmm->mm_count);
        enter_lazy_tlb(oldmm, next);
} else
        switch_mm(oldmm, mm, next);

unlikely?首先这个不是C语言的关键字,那就是宏定义了。事实上,内核代码中将likelyunlikely定义为两个帮助编译器优化的宏。宏定义如下:

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

__builtin_expect()宏是gcc(version>=2.9)引入的预先定义的宏,这个宏的主要作用是帮助编译器判断条件跳转的预期值。看看下面这个例子:

if (__builtin_expect (x, 0))
       foo ();

这里例子中,代码是在说预期x的值为“假”,并且不太期望程序执行foo()函数。而这段代码就等价于:

if (unlikely(x))
        foo ();

那么这个宏定义是如何影响编译器的行为的呢?看下面的一个例子(来自http://kerneltrap.org/node/4705):

[kedar@ashwamedha ~]$ cat abc.c
int
testfun(int x)
{
        if(__builtin_expect(x, 0)) {
                              ^^^--- 在这里我们告诉编译器, "else" 块的代码是我们比较期待的结果
                x = 5;
                x = x * x;
        } else {
                x = 6;
        }
        return x;
}

[kedar@ashwamedha ~]$ gcc -O2 -c abc.c
[kedar@ashwamedha ~]$ objdump  -d abc.o

abc.o:     file format elf32-i386

Disassembly of section .text:

00000000 :
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   8b 45 08                mov    0x8(%ebp),%eax
   6:   85 c0                   test   %eax,%eax
   8:   75 07                   jne    11 < testfun+0x11 >
                                ^^^ --- 编译器在这里为"if"块产生跳转代码但是却保持"else"块顺序执行
   a:   b8 06 00 00 00          mov    $0x6,%eax
   f:   c9                      leave
  10:   c3                      ret
  11:   b8 19 00 00 00          mov    $0x19,%eax
  16:   eb f7                   jmp    f < testfun+0xf >

如果将源代码中的unlikely改为likely,编译器生成的代码将会于上面的情况恰恰相反:

[kedar@ashwamedha ~]$ cat abc.c
int
testfun(int x)
{
        if(__builtin_expect(x, 1)) {
                              ^^^ --- 在这里我们告诉编译器, "if" 块的代码是我们比较期待的结果
                x = 5;
                x = x * x;
        } else {
                x = 6;
        }
        return x;
}

[kedar@ashwamedha ~]$ gcc -O2 -c abc.c
[kedar@ashwamedha ~]$ objdump  -d abc.o

abc.o:     file format elf32-i386

Disassembly of section .text:

00000000 :
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   8b 45 08                mov    0x8(%ebp),%eax
   6:   85 c0                   test   %eax,%eax
   8:   74 07                   je     11 < testfun+0x11 >
                                ^^^ --- 编译器在这里为"else"块产生跳转代码但是却保持"if"块顺序执行
   a:   b8 19 00 00 00          mov    $0x19,%eax
   f:   c9                      leave
  10:   c3                      ret
  11:   b8 06 00 00 00          mov    $0x6,%eax
  16:   eb f7                   jmp    f < testfun+0xf >

由此可见likelyunlikely仅仅是在帮助编译器产生更优代码,而对真值的判断没有影响。读代码的时候记住这一点就可以了。

相关日志

嘿~,如果您喜欢我的博客,您可以通过RSS.链接将本博客的最新文章传输到您喜欢的阅读器。 Subscribe with Google   订阅到鲜果

3 条回复 to “Linux内核源码阅读系列(1)-可能令人迷惑的C语言语法”

  1. Border says:
    一月 12th, 2009 at 17:56

    在内核中是这样定义的 include/linux/compiler.h :
    #define likely(x) __builtin_expect(!!(x), 1)
    #define unlikely(x) __builtin_expect(!!(x), 0)

    参考: http://kernelnewbies.org/FAQ/LikelyUnlikely

  2. jcadam says:
    一月 13th, 2009 at 15:06

    Thanks, Border.
    内核代码中对判断条件两次取非,这将保证这个x的值能够顺利的和1或者0比较。

  3. tek-life says:
    四月 5th, 2010 at 17:03

    “likely和unlikely仅仅是在帮助编译器产生更优代码,而对真值的判断没有影响”
    Right!

回复