3.2.7 内核映像构建过程总结

前面我们简要讨论了内核映像的构建,内核映像的构建过程大体上可以概括为“三次编译链接,一次组合”,如图3-2所示。

3.2.7 内核映像构建过程总结 - 图1

图 3-2 内核映像构建过程

(1)第一次编译链接

kbuild分别编译各个子目录下的目标文件,如built-in.o、lib.a(如果有)等,然后将他们链接为ELF格式的vmlinux,并存放在顶层目录中。这一步相当于构建有效载荷。

(2)第二次编译链接

kbuild使用工具objcopy,将顶层目录的vmlinux复制到arch/x86/boot/compressed目录下,去掉其中的符号信息、重定位信息,删除段".comment",并命名为vmlinux.bin。然后,kbuild将其压缩为vmlinux.bin.gz(假设内核采用核默认的gzip压缩方式),封装到piggy.S中,并调用汇编器将其编译为piggy.o,这一步是对有效载荷进行了压缩。

同时,kbuild也调用编译器编译arch/x86/boot/compressed目录下的head_32.c、misc.c等作为内核的非压缩部分,这一步相当于构建二级推进系统。

然后,kbuild调用链接器将压缩的有效载荷和二级推进系统链接为vmlinux。注意这里文件名虽然也是vmlinux,但是不要与顶层目录下的vmlinux混淆,arch/x86/boot/compressed目录下的vmlinux是二级推进系统和有效载荷的组合,与顶层目录下的vmlinux是包含的关系。

最后,kbuild调用objcopy将arch/x86/boot/compressed目录下的vmlinux复制到arch/x86/boot目录下,同时将其转换为裸二进制格式,并命名为vmlinux.bin,为在arch/x86/boot目录下进行的最后的组装做好准备。

(3)第三次编译链接

kbuild将arch/x86/boot下的a20.o、bioscall.o等目标文件链接为setup.elf,使用objcopy将其转换为裸二进制格式,并命名为setup.bin。这一步,相当于构建一级推进系统。

(4)一次组合

最后,kbuild调用内核自带的程序build,将vmlinux.bin和setup.bin合并为bzImage。至此,航天器的一级推进系统和包含有效载荷的二级推进系统装配完毕。

在3.1节,我们曾粗略讨论了内核映像的组成。在了解了内核的构建过程后,让我们近距离的再观察一下bzImage。以下是bzImage的链接脚本:

3.2.7 内核映像构建过程总结 - 图2

3.2.7 内核映像构建过程总结 - 图3

首先来看链接脚本中的段".head.text",其中宏HEAD_TEXT的定义为:

3.2.7 内核映像构建过程总结 - 图4

而结合文件head_32.S:

3.2.7 内核映像构建过程总结 - 图5

3.2.7 内核映像构建过程总结 - 图6

以及宏__HEAD的定义:

3.2.7 内核映像构建过程总结 - 图7

可见,在head_32.S中,函数startup_32通过宏__HEAD明确要求链接器将函数startup_32链接到段".head.text"。而根据bzImage的链接脚本,段".head.text"被安排在了内核映像的起始位置,也就是说,函数startup_32被链接到了内核映像的开头。

接下来的段".rodata..compressed",想必读者一定猜出来了,这里就是放置内核的压缩映像部分。根据piggy.S的内容即可见这一点:

3.2.7 内核映像构建过程总结 - 图8

在piggy.S中,明确定义了内核压缩部分所在的段为".rodata..compressed"。

接下来的".text"、".data"等段就是保存内核非压缩部分的代码和数据了,包括misc.o、string.o、cmdline.o、early_serial_console.o以及head_32.o中的不属于段".head.text"的部分。因为内核非压缩部分被编译为位置无关(PIC)代码,所以我们看到其包含got表。

综上所述,bzImage的布局如图3-3所示。

3.2.7 内核映像构建过程总结 - 图9

图 3-3 内核映像bzImage的布局