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

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仅仅是在帮助编译器产生更优代码,而对真值的判断没有影响。读代码的时候记住这一点就可以了。

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

发表评论