首先我们都知道,Linux
内核如果用O0
编译,是无法编译过的,Linux
的内核编译,要么是O2
,要么是Os
,这点从Linux
的Makefile
里面可以看出:
当选择了
CONFIG_CC_OPTIMIZE_FOR_SIZE
它会是Os
,否则就是O2
。
其实O2
和Os
,都是一些优化选项的集合:
gcc -c -Q -O2 --help=optimizers > /tmp/O2-opts
gcc -c -Q -Os --help=optimizers > /tmp/Os-opts
前者倾向于基于速度的优化,后者倾向于基于size
更小的优化。对比二者的开关选项:
meld /tmp/O2-opts /tmp/Os-opts
发现差异小的可怜:
O2
和Os
都使能了inline small
函数和called once
的函数,
但是O2
里面-finline-functions
是关闭的,而Os
里面是开的。
O2
里面optimize-strlen
是开的,Os
里面这个选项是关闭的。
相关选项的含义可以通过"man gcc"
看出(有问题,找男人),譬如man gcc
后检索inline-functions
:
从O0
到O1
,O2
,O3
,是一个开启的优化选项逐步加大的过程:
kernel
用O0
编译不过,是因为kernel
本身也没有想用O0
能够编译过,它的设计里面包含了编译会优化的假想。下面我们用一个简单的例子来说明。
$ gcc -O0 cc.c
cc.c:1:13: warning: ‘f’ used but never defined [enabled by default]
void f(void);
^
/tmp/ccTwwtHG.o: In function `main':
cc.c:(.text+0x19): undefined reference to `f'
collect2: error: ld returned 1 exit status
但是用O2
编译,则没有问题:
$ gcc -O2 cc.c
原因在于,O2
编译,它意识到a==1
,所以if(a>2)
,它不会成立,所以f()
没有定义也没有关系。
把代码稍微改一下后:
O2
这个时候也不行了:
$ gcc -O2 cc.c
/tmp/ccXiyBHn.o: In function `main':
cc.c:(.text.startup+0x7): undefined reference to `f'
collect2: error: ld returned 1 exit status
所以,通过这个例子,大家可以看出来为什么同样的代码,用O2
就可以过,用O0
就过不了。内核里面有许多类似设想编译器会进行优化的代码。
由于编译的优化,有些函数(比如小函数和全工程里面只被一个人调用的函数)虽然没有显示地写成inline
,
但是编译器优化为inline
了,这给调试造成了一些麻烦,因为找不到这个函数对应的symbol
了。
这个时候,我们可以显示地写明某些函数我们不想inline
:
否则,上面2
个函数,即便你代码里面没有写inline
,由于O2
和Os
使能了相关的inline
选项,也可能被编译器自动inline
掉,如果我们想拒绝inline
,可以通过noline
来标识。
在全局已经使能O1
,O2
,O3
, Os
的情况下,某个单独的函数我们不想做任何的优化,可以用__attribute__((optimize("O0")))
来修饰这个函数,比如我们把上述用O2
可以编译过的代码进行如下修改:
重新用O2
编译:
$ gcc -O2 cc.c
/tmp/cc8M338p.o: In function `main':
cc.c:(.text+0x19): undefined reference to `f'
collect2: error: ld returned 1 exit status
下面给几条实践指南:
O0
去编译内核,这不符合真实的工程实践,也不太被主流Linux
社区所支持;内核依赖O2/Os
去做较多的优化;O2
的情况下,仍然是正确的,代码要经得起编译优化;比如O0
工作正常,而O2
不正常,应该尽可能从自身找原因,分析汇编;noinline
,__attribute__((optimize("O0")))
等进行外科手术式地调整。