前言
在嵌入式Linux的开发过程中,内核编译是一个永远也绕不开的话题。
对内核编译系统的清晰把握,至少可以:
- 了解整个内核的构造
- 节省编译时间
- 在编译报错时快速定位问题
- 进一步了解内核的启动
本文从Linux kernel中.o文件的编译探索kbuild机制。
日期 | 内核版本 | 架构 |
---|---|---|
2022-9-13 | Linux5.4.200 | arm |
实验
目标log展开
我们以page_alloc.o的编译为例开始本次实验。
1 | make mm/page_alloc.o |
在kernel根目录下的Makefile中有:
https://elixir.bootlin.com/linux/v5.4.200/source/Makefile#L1733
1 | # Single targets |
直接看不是很直观,kernel version 2.6的Makefile中语句较为清晰:
1 | %.o: %.c prepare scripts FORCE |
逻辑比较清晰,.o
的文件依赖于同名的.c
文件,然后执行一个命令来生成。
Tip:这里有个小技巧,注释掉构建语句,通过error log来逆向观察语句展开的结构
1 | $(build-dirs): prepare |
再次执行编译命令,则有:
1 | @make -f ./scripts/Makefile.build obj=mm \ |
从log可知,执行make mm/page_alloc.o
又调用了一次make,使用script/Makefile.build
这个规则文件,传入的参数是obj=mm。
build
由第一节的log我们知道$(build)
展开后是:
1 | -f ./scripts/Makefile.build obj |
在哪定义的呢?全局搜索后,答案是scripts/Kbuild.include
,类似头文件的东西。
https://elixir.bootlin.com/linux/v5.4.200/source/scripts/Kbuild.include#L160
1 | ### |
好,将注意力转移到scripts/Makefile.build
这个文件,寻找和编译.o
相关的语句。
1 | $(obj)/%.o: $(src)/%.c $(recordmcount_source) $$(objtool_dep) FORCE |
重点关注$(call if_changed_rule,cc_o_c)
,这里调用了if_changed_rull
变量,该变量同样定义在scripts/Kbuild.include
1 | # Usage: $(call if_changed_rule,foo) |
这是一条逻辑判断语句,当条件为真,执行逗号之前的动作。当条件为假,则执行后面的@:
。这里的@:
为的是减少一些log输出,具体可以看提交:kernel/git/torvalds/linux.git - Linux kernel source tree
而如果条件为真,rule_$(1)
展开就是rule_cc_o_c
。
好,现在回到scripts/Makefile.build
,寻找rule_cc_o_c
的定义:
1 | define rule_cc_o_c |
OK,离故事接近真相还差最后一步,$(call cmd_and_fixdep,cc_o_c)
语句是我们重点关注的。同样是call函数,所以在此回到头文件scripts/Kbuild.include
寻找定义:
1 | cmd_and_fixdep = \ |
根据经验,再次展开就是cmd_cc_o_c
了,它还是在scripts/Makefile.build文件定义。
1 | cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< |
终于,真相大白!
总结
.o
文件的编译一共分为如下四个步骤:
1 | Makefile |
这里重点强调下前面反复提到的两个文件:
scripts/Makefile.build
:包含了几乎所有的重要规则scripts/Kbuild.include
:类似于头文件,包含很多重要函数