关注”技术简说“,带你由浅入深学习linux内核源码。linux内核开发100讲免费教程,每天晚上九点准时更新,敬请收看。进我主页点”视频“栏目即可观看。
在之前的课程里,当我们在编译linux内核源码的时候,不知道大家会不会有一些疑问:
1.linux内核源码那么多(大概800M),编译的时候它(编译系统)怎么知道应该要编译哪些文件呢?
2.怎样保证源码的编译顺序?比如,先编译A模块,再编译B模块?
3.怎么样把这些编译出来的一个一个的目标文件,最终形成一个内核镜像文件?
所有这些,都是通过Makefile来完成的。
本文所用内核源码为 linux-4.9.229,ARCH=x86
linux内核源码里的Makefile分为三层:
- 顶层Makefile(源码根目录下)
- 体系相关的Makefile(arch/$(ARCH)/Makefile)
- 各级子目录下面的Makefile。
那我们就先来看顶层Makefile,先摘几段关键代码:
<code> 262 SRCARCH :=$(ARCH)
... 546include
arch/$(SRCARCH)
/Makefile/<code>
这样体系相关的Makefile就被include进来了。
而下面的代码则间接的把各级子目录下面的Makefile包含进来了。
<code>570
init-y := init/571
drivers-y := drivers/ sound/ firmware/572
net-y := net/573
libs-y := lib/574
core-y := usr/575
virt-y := virt//<code>
这样顶层的Makefile就把体系相关的Makefile和各个子目录的Makefile都包含进来了。
那接下来我们深入到某个具体的子目录的Makefile,看它又是如何编译这个子目录下面的源代码的呢?
具体到某个源码目录中,编译系统是通过obj-y, obj-m,obj-n和lib-m来组织的。
- obj-y用来定义哪些源文件被编进内核。在当前目录,这些被编译到内核里的.o文件,会被链接成一个built.o文件。大家可以看看,等内核编译完成后,是不是每个子目录下,都有一个built-in.o文件呢?
- obj-m用来定义哪些文件被编译成可加载模块,也就是驱动文件。
- obj-n表示当前源文件既不被编译到内核,也不会编译成驱动,也就是不做处理。
- lib-y用来定义哪些文件被编译成库文件。
- obj-y, obj-m,lib-y,lib-m还可以指定要包含的下一级目录。这样层层包含,保证能够编译到所有的内核源代码。
我以drivers/tty子目录为例,其Makefile的摘要如下:
<code>obj-$(CONFIG_TTY) += tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o \ tty_buffer.o tty_port.o tty_mutex.o tty_ldsem.o obj-$(CONFIG_HVC_DRIVER) += hvc//<code>
以上表示:
如果通过make menuconfig配置了CONFIG_TTY为y,那么 tty_io.c n_tty.c tty_ioctl.c tty_ldisc.c
tty_buffer.c tty_port.c tty_mutex.c tty_ldsem.c 这些c文件就会被编译到内核镜像里;
如果通过make menuconfig配置了CONFIG_HVC_DRIVER,那么还会进入到hvc子目录进一步执行它的Makefile。
所以,当某个子目录下的makefile执行一遍以后,所有的需要编译到内核里的.o文件就都罗列在obj-y这个变量里了,而所有需要编译成驱动的.o文件就都罗列在obj-m里了。
另外,某个子目录下obj-y变量里的所有 .o文件会被链接成built-in.o文件,并最终被链接到内核镜像文件。
下面代码是drivers/tty/built-in.o 生成的过程。
<code>ld -m elf_x86_64 -zmax
-page-size=0x200000
-r -o drivers/tty/built-in
.o drivers/tty/tty_io.o drivers/tty/n_tty.o drivers/tty/tty_ioctl.o drivers/tty/tty_ldisc.o drivers/tty/tty_buffer.o drivers/tty/tty_port.o drivers/tty/tty_mutex.o drivers/tty/tty_ldsem.o drivers/tty/pty.o drivers/tty/tty_audit.o drivers/tty/sysrq.o drivers/tty/vt/built-in
.o drivers/tty/serial/built-in
.o drivers/tty/ipwireless/built-in
.o /<code>
我们来看看内核镜像是如何生成的。
还是看顶层的Makefile:
<code>core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ vmlinux-dirs:
= $(patsubst%/,%,$(filter %/
, $(init-y) $(init-m) \ $(core-y) $(core-m) $(drivers-y) $(drivers-m) \ $(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y))) vmlinux-alldirs:
= $(sort $(vmlinux-dirs) $(patsubst%/,%,$(filter %/
, \ $(init-) $(core-) $(drivers-) $(net-) $(libs-) $(virt-)))) init-y:
= $(patsubst%/, %/
built-in
.o, $(init-y)) core-y:
= $(patsubst%/, %/
built-in
.o, $(core-y)) drivers-y:
= $(patsubst%/, %/
built-in
.o, $(drivers-y)) net-y:
= $(patsubst%/, %/
built-in
.o, $(net-y)) libs-y1:
= $(patsubst%/, %/
lib.a, $(libs-y)) libs-y2:
= $(patsubst%/, %/
built-in
.o, $(libs-y)) libs-y:
= $(libs-y1) $(libs-y2) virt-y:
= $(patsubst%/, %/
built-in
.o, $(virt-y)) export KBUILD_ALLDIRS:
= $(sort $(filter-out arch/%,$(vmlinux-alldirs)) arch Documentationinclude
samples>
patsubst和filter是Makefile的内置函数,说明如下:
$(patsubst %/, %/built-in.o, $(init-y))
寻找$(init-y)中符合格式“%/”的字符串,用“%/built-in.o”替换它们。在之前我们有定义init-y的值为init/,所以,经过这么转换,init-y的值就变成了init/built-init.o了。其他同理。
而 $(filter pattern..., text)表示返回在“text”中由空格隔开且匹配格式“pattern...”的字符串,去掉不符合格式“pattern...”的字符串。例如:
<code>$(filter
%.c
%.s, foo.c
bar.c
jin.s xin.h) 结果为:foo.c
bar.c
jin.s/<code>
根据以上的分析,最终vmlinux-dirs的值为:
<code>init
usr arch/x86 kernel certs mm fs ipc security crypto block drivers sound firmware \ arch/x86/pci arch/x86/power arch/x86/video arch/x86/ras net lib arch/x86/lib virt/<code>
最终vmlinux-alldirs的值为:
<code>arch/x86/lib arch/x86/math-emu arch/x86/oprofile arch/x86/pci \ arch/x86/power arch/x86/ras arch/x86/video block certs crypto drivers firmware \ fs init ipc kernel lib mm net security sound usr virt
/<code>
linux内核代码的链接脚本为:arch/$(SRCARCH)/kernel/vmlinux.lds。 对于x86,则位于
arch/x86/kernel/vmlinux.lds, 它是由
arch/x86/kernel/vmlinux.lds.S文件生成的。
在编译内核的时候指定:make V=1,则可以打印出具体的编译细节,最终编译出linux内核镜像的编译过程为:
<code>ld
-m elf_x86_64 -z max-page-size=0x200000 --build-id -o vmlinux \ -T ./arch/x86/kernel/vmlinux.lds arch/x86/kernel/head_64.o \
arch/x86/kernel/ebda.o \ arch/x86/kernel/platform-quirks.o init/built-in.o \ --start-group usr/built-in.o arch/x86/built-in.o \ kernel/built-in.o certs/built-in.o mm/built-in.o \ fs/built-in.o ipc/built-in.o security/built-in.o \ crypto/built-in.o block/built-in.o lib/lib.a \ arch/x86/lib/lib.a lib/built-in.o arch/x86/lib/built-in.o \ drivers/built-in.o sound/built-in.o firmware/built-in.o \ arch/x86/pci/built-in.o arch/x86/power/built-in.o \ arch/x86/video/built-in.o arch/x86/ras/built-in.o \ net/built-in.o virt/built-in.o --end-group .tmp_kallsyms2.o
/<code>
总结一下,linux内核源码的编译过程为:
- 顶层Makefile决定了linux内核源码各个目录和子目录的编译顺序
- make menuconfig通过对内核配置,生成了一系列CONFIG_XXX的配置
- 各层级的Makefile结合这些CONFIG_XXX配置来决定哪些文件被编译到内核、哪些文件被编译成驱动
- 顶层Makefile按照链接脚本链接所有的.o文件并最终打包成一个完整的内核镜像文件vmlinux。
关注”技术简说“,带你由浅入深学习linux内核源码。linux内核开发100讲免费教程,每天晚上九点准时更新,敬请收看。进我主页点”视频“栏目即可观看。