1. 内核镜像的分类
我们经常能看到内核在编译完成后生产各种映像文件,如:Image 、zImage、bzImage等。
其实最开始出现的是 Image,就一个普通的内核镜像。后来为了节省空间,有了 zImage,进行了压缩可以节省空间。
那么uImage又是什么的?它是uboot专用的映像文件,它是在zImage之前加上一个长度为64字节的“头”,说明这个内核的版本、加载位置、生成时间、大小等信息;其0x40之后与zImage没区别。
几种linux内核文件的区别:
-
vmlinux:编译出来后未压缩最原始的内核文件
-
zImage:vmlinux经过gzip压缩后的文件。
-
bzImage:bz表示“big zImage”,不是用bzip2压缩的。两者的不同之处在于,zImage解压缩内核到低端内存(第一个640K),bzImage解压缩内核到高端内存(1M以上)。如果内核比较小,那么采用zImage或bzImage都行,如果比较大应该用bzImage。
-
uImage:U-boot专用的映像文件,它是在zImage之前加上一个长度为0x40的TAG。
-
vmlinuz:bzImage/zImage文件的拷贝或指向bzImage/zImage的链接。
-
initrd:“initial ramdisk”的简写。一般被用来临时的引导硬件到实际内核vmlinuz能够接管并继续引导的状态
2. 内核镜像bzImage的组成(x86架构)
上图为bzImage的组成,其由压缩后的内核镜像vmlinux.bin.gz,加上未压缩部分uncompressed和setup.bin三部分组成,下面分别对这三部分进行详细的说明。
setup.bin
在以前,在进行内核初始化时,需要一些信息,如:显示信息、内存信息等,这些工作就由setup.bin 在实模式通过BIOS来获取,然后保存在内核中的变量boot_params 中,变量boot_params 是结构体boot_params 的一个实例。
在完成信息收集之后,setup.bin 就会切换成保护模式,并跳转到内核保护模式部分执行。内核将setup.bin 收集保存在setup.bin 的数据段变量boot_params 复制到vmlinux 的数据段中。
现在,随着新的BIOS标准的出现,尤其是EFI的出现,为了支持这些新标准,开发者们制定了32位启动协议(32-bit boot protocol)。在32位启动协议下,由Bootloader 实现收集这些信息的功能,内核启动时不再需要首先运行实模式部分(即setup.bin),而是直接跳转到内核的保护模式部分。
不过setup.bin 也不完全就被淘汰了,其还有一个重要功能就是负责在内核和Bootloader 之间传递信息。32位启动协议规定在setup.bin 中分配一块空间用来承载这些信息(内核是否可重定位、内核的对齐要求、内核建议的加载地址等),在构建映像时,内核构建系统需要将这些写到setup.bin的这块空间中,所以setup.bin 已经失去了过去的作用,不过仍然作为内核和Bootloader 之间的桥梁。
uncompressed
内核中的保护模式部分是经过压缩的,因此运行前需要进行解压。Bootloader在加载内核映像后跳转至外围的这段非压缩部分,由这部分非压缩指令,负责解压内核的压缩部分。
除此之外,非压缩部分还需要负责内核的重定位,在编译内核进行链接时,为了方便链接,会指定一个虚假(虚拟)地址,然后以这个虚拟地址为参考,为各个符号分配运行时地址,如果加载的地址和链接时指定的地址不同,就需要对符号的地址进行重新修订,即内核重定位。
vmlinux
在编译时,kbuild分别构建内核各个子目录中的目标文件,然后将它们链接为vmlinux。kbuild会删除vmlinux中一些不必要的信息,并将其命名为vmlinux.bin,最后将其压缩为vmlinux.bin.gz。
不同的镜像可能会有不同的映像组成形式,不过大体上都是相似的,后面我会尝试从源码角度分析uImage 映像的组成,会与x86的下的bzImage 映像进行对比。
内核映像的格式
setup.bin、vmlinux.bin等都是裸二进制格式的文件,并不是操作系统中程序所使用的ELF格式,原因是操作系统内核本身是工作在"freestanding enviroment"环境下。操作系统不能强制要求Bootloader也提供ELF加载器,而且操作系统镜像也没必要使用ELF格式来组织。
不过在Linux2.6.26版本后,内核压缩部分,使用了ELF格式。而这个解析ELF的工作,也交给了uncompressed部分的代码来实现。
3. 内核映像的构建(基于ARM架构)
3.1 内核源码结构
Linux内核 – 内核源码结构 (cnblogs.com)
3.2 配置文件
make menuconfig
Linux内核配置以及Make menuconfig过程分析
Kbuild系统
在Linux内核里,每个子目录都有一个makefile,它被称作Kbuilt-makefile,它将当前目录的文件编译成built-in.o、库文件和模块文件。然后顶层Makefile里指定这些built-in.o的路径,将它们连接在一起,最后链接成一个vmlinux文件。
内核makefile.txt中将makefile分为 5部分,Kernel Makefile、ARCH Makefile、KBuild Makefile、.config文件以及scripts/Makefile.*
- Kernel Makefile
Kernel Makefile 位于Linux 内核源代码的顶层目录,也叫 Top Makefile 。它用于指定编译Linux Kernel 目标文件(vmlinux)和模块(module)路径。它根据.config文件决定了内核根目录下那些文件、子目录被编译进内核。对于内核或驱动开发人员来说,这个文件几乎不用任何修改。
- ARCH Makefile
ARCH Makefile位于ARCH/$(ARCH)/Makefile ,是系统对应平台的Makefile 。Kernel Top Makefile会包含这个文件来指定平台相关信息。ARCH Makefile同样根据.config文件,决定了ARCH/$(ARCH) 目录下哪些文件、子目录会被编译进内核,只有平台开发人员会关心这个文件。
- Kbuild Makefile
从Linux 内核2.6 开始,Linux 内核的编译采用Kbuild系统 ,这同过去的编译系统有很大的不同,Kbuild系统使用Kbuild Makefile来编译内核或模块。当Kernel Makefile被解析完成后,Kbuild会读取相关的Kbuild Makefile进行内核或模块的编译。Kbuild Makefile有特定的语法指定哪些编译进内核中、哪些编译为模块、及对应的源文件是什么等。内核及驱动开发人员需要编写这个Kbuild Makefile文件。
-
scripts/Makefile.* 通用规则
Makefile.build
被顶层Makefile所调用,与各级子目录的Makefile合起来构成一个完整的Makefile文件,定义built-in.o、.lib以及目标文件.o的生成规则。这个Makefile文件生成了子目录的.lib、built-in.o以及目标文件.o
Makefile.clean
被顶层Makefile所调用,用来删除目标文件等
Makefile.lib
被Makefile.build所调用,主要是对一些变量的处理,比如说在obj-y前边加上obj目录
Kbuild.include
被Makefile.build所调用,定义了一些函数,如if_changed、if_changed_rule、echo-cmd -
.config文件详细参考make menuconfig部分。
3.3 编译内核
在编译嵌入式系统使用的内核时,我们会在内核的根目录下面执行make uImage
来生成内核镜像,或者make后面不接入任何目标。在没有接目标的时候,默认也是生成内核映像uImage。
顶层Makefile会通过include指令,将架构相关的Makefile文件引入顶层Makefile中:
include $(srctree)/arch/$(SRCARCH)/Makefile
在如arm架构,就会将arch/arm/Makefile包含进顶层的Makefile文件。arch/arm/Makefile中定义就具体的镜像文件生成规则,如:
# Convert bzImage to zImage
bzImage: zImage
zImage Image xipImage bootpImage uImage: vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
在第4行处,zImage、Image等各种镜像都会依赖于vmlinux。
在前面我们介绍了x86使用的bzImage是带压缩的,那么我们平时使用的uImage是压缩的呢?在arch/arm/boot/Makefile文件里,有一段下面的规则:
$(obj)/uImage: $(obj)/zImage FORCE @$(check_for_multiple_loadaddr) $(call if_changed,uimage) @$(kecho) ' Image $@ is ready'
我们可以看到uImage是依赖于zImage的。而zImage是一种带压缩格式的映像,由此uImage也算是带压缩的,后面会进行详细分析。
构建vmlinux(如果没有特殊标明,说明代码来源于顶层Makefile文件)
在上面我们看到vmlinux是很关键的依赖,所有镜像的生成都依赖于它。由此,我们先尝试对vmlinux进行分析,看它是如何构建的。等分析出了vmlinux后,再往上追踪uImage是如何构建的。
vmlinux的生成规则在顶层Mafefile文件中,如下:
# Final link of vmlinux
cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) # 后面会使用到
# Include targets which we want to
# execute if the rest of the kernel build went well.
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
+$(call if_changed,link-vmlinux)
我们可以看到vmlinux的生成依赖于scripts/link-vmlinux.sh脚本,由名字可以看出该脚本起链接的作用。
第7行处通过call 引用if_changed函数,if_change函数定义如下(位于scripts/Kbuild.include目录下):
# scripts/Kbuild.include(对于非顶层Makefile的代码片段,会以这种形式进行注明,未注明则为顶层Makefile的片段)
# Execute command if command has changed or prerequisite(s) are updated.
if_changed = $(if $(strip $(any-prereq) $(arg-check)), \
@set -e; \
$(echo-cmd) $(cmd_$(1)); \
printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)
第6行处有一个$(cmd_$(1))
的东西,将$(call if_changed,link-vmlinux)
待进入展开后就变成了cmd_link-vmlinux
。就是刚刚在顶层Makefile 面列出来的cmd_link-vmlinux
,于是我们回到顶层Makefile文件:
cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)
进行分析:
-
CONFIG_SHELL:指定使用的shell 解释器类型(这个不重要)。
-
$<:表示第一个依赖,由于cmd_link-vmlinux 在vmlinux 的“规则体”下面,所以这个$<,翻译一下就是scripts/link-vmlinux.sh
-
LD:链接器类型。在顶层Makefile文件的前部分,配置了编译时需要使用的各种工具链,这个LD会作为参数传给link-vmlinux.sh 脚本使用,最后会使用这个链接器来链接生成vmlinux。
# Make variables (CC, etc...) AS = $(CROSS_COMPILE)as LD = $(CROSS_COMPILE)ld CC = $(CROSS_COMPILE)gcc CPP = $(CC) -E AR = $(CROSS_COMPILE)ar NM = $(CROSS_COMPILE)nm STRIP = $(CROSS_COMPILE)strip OBJCOPY = $(CROSS_COMPILE)objcopy OBJDUMP = $(CROSS_COMPILE)objdump
-
LDFLAGS、LDFLAGS_vmlinux:链接使用到的一些参数。
分析后可知$(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)
本质是执行scripts/link-vmlinux.sh 脚本,而LD、LDFLAGS、LDFLAGS_vmlinux 作为参数传递给link-vmlinux.sh脚本。
scripts/link-vmlinux.sh脚本核心内容如下:
# scripts/link-vmlinux.sh
# Link of vmlinux
# ${1} - optional extra .o files
# ${2} - output file
vmlinux_link()
{
local lds="${objtree}/${KBUILD_LDS}"
if [ "${SRCARCH}" != "um" ]; then
${LD} ${LDFLAGS} ${LDFLAGS_vmlinux} -o ${2} \
-T ${lds} ${KBUILD_VMLINUX_INIT} \
--start-group ${KBUILD_VMLINUX_MAIN} --end-group ${1}
else
...省略...
在第11行处,调用了${LD}
链接器进行链接,输出文件为\${2}
。
-
${2}:link-vmlinux.sh 中会调用
vmlinux_link()
函数,以vmlinux_link "${kallsymso}" vmlinux
的形式调用,其最终会生成文件名为第二参数的vmlinux文件。 -
${KBUILD_VMLINUX_INIT}、${KBUILD_VMLINUX_MAIN}:指明了要被链接成vmlinux的源文件。它们定义在顶层Makefile文件中。
# Objects we will link into vmlinux / subdirs we need to visit init-y := init/ drivers-y := drivers/ sound/ firmware/ net-y := net/ libs-y := lib/ core-y := usr/ 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) # Externally visible symbols (used by link-vmlinux.sh) export KBUILD_VMLINUX_INIT := $(head-y) $(init-y) export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y)
KBUILD_VMLINUX_INIT依赖于
$(init-y)
,init-y
是来自于init/目录下的built-in.o
。KBUILD_VMLINUX_MAIN依赖于
$(core-y) $(libs-y) $(drivers-y) $(net-y)
,而它们分别又来自于usr/、lib/、drivers/ sound/、firmware/、net/等目录下的built-in.o
文件。自此我们就知道了,vmlinux 需要各个子目录下的built-in.o 文件和部分目录下的lib.a(也就是lib/目录),以及head-y(其在架构相关的Makefile文件下定义,定义为
head-y := arch/arm/kernel/head$(MMUEXT).o
,也就是各种命名为head.xxx.o 的文件)。 -
${lds}:链接时使用的链接脚本,其定义在顶层Makefile 中,为变量KBUILD_LDS。
export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
-
LDFLAGS、LDFLAGS_vmlinux:顶层Makefile中传进来的一些链接参数,我们不关心。
至此,我们知道了,要链接生成vmlinux,我们需要lib目录下面的lib.a以及子目录下的built-in.o文件,那么它们在如何生成的呢?
built-in.o和lib.a的构建
回到前面的vmlinux 依赖的片段:
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
+$(call if_changed,link-vmlinux)
我们可以看到vmlinux 不仅依赖link-vmlinux.sh 脚本,还依赖$(vmlinux-deps)
变量。vmlinux-deps 依赖如下:
vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
$(core-y) $(core-m) $(drivers-y) $(drivers-m) \
$(net-y) $(net-m) $(libs-y) $(libs-m)))
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
# The actual objects are generated when descending,
# make sure no implicit rule kicks in
$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
PHONY += $(vmlinux-dirs)
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
vmlinux-deps
是一个变量,其内容为我们前面看到的哪些在各个子目录下的built-in.o 或者lib.a 文件的一个集合。
从第9行目标vmlinux-deps这个集合的构建规则来看,其“规则体”是空的,不过它依赖于vmlinux-dirs。
vmlinux-deps里面包含了全部的built-in.o 或者lib.a,通过一个sort进行排序后,它们都共同依赖于vmlinux-dirs。
看一下变量vmlinux-dirs 的值,该变量的赋值脚本,其中函数filter 是make 的内置函数,其功能是过滤输入文本中不以“/” 结尾的字符串。翻译最终结果等价于:
init: prepare scripts
$(Q)$(MAKE) $(build)=$@
kernel: prepare scripts
$(Q)$(MAKE) $(build)=$@
...
将$(Q)$(MAKE) $(build)=$@
展开后为:
make -f script/Makefile.build obj=$@
其通过script/Makefile.build 中的规则进行编译:
# scripts/Makefile.build
src := $(obj)
PHONY := __build
__build:
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
目标__build涵盖了内核映像(if $(KBUILD_BUILTIN)
)和模块(if $(KBUILD_MODULES)
)。(这里我们只关注内核映像的构建,不关注模块的构建)
(对于内核映像来说,其目标依赖有builtin-target
、lib-target
、extra-y
、subdir-ym
和always
。)
先看builtin-target 和lib-target:
# scripts/Makefile.build
ifneq ($(strip $(lib-y) $(lib-m) $(lib-n) $(lib-)),)
lib-target := $(obj)/lib.a
endif
ifneq ($(strip $(obj-y) $(obj-m) $(obj-n) $(obj-) $(subdir-m) $(lib-target)),)
builtin-target := $(obj)/built-in.o
endif
根据上面的脚本片断可见,builtin-target 代表子目录下的built-in.o、lib-target 代表的就是子目录下的lib.a。如果lib-y不为空就构建lib.a,如果obj-y不为空就构建built-in.o
再在看看lib.a 和built-in.o 是怎么构建的:
# scripts/Makefile.build
# Rule to compile a set of .o files into one .o file
ifdef builtin-target
quiet_cmd_link_o_target = LD $@
# If the list of objects to link is empty, just create an empty built-in.o
cmd_link_o_target = $(if $(strip $(obj-y)),\
$(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \
$(cmd_secanalysis),\
rm -f $@; $(AR) rcs$(KBUILD_ARFLAGS) $@)
$(builtin-target): $(obj-y) FORCE
$(call if_changed,link_o_target)
targets += $(builtin-target)
endif # builtin-target
#
# Rule to compile a set of .o files into one .a file
#
ifdef lib-target
quiet_cmd_link_l_target = AR $@
cmd_link_l_target = rm -f $@; $(AR) rcs$(KBUILD_ARFLAGS) $@ $(lib-y)
$(lib-target): $(lib-y) FORCE
$(call if_changed,link_l_target)
targets += $(lib-target)
endif
前面我们说明了if_changed 的使用。这里我们就可以很容易理解builtin-target (第12行),就是将变量obj-y(第7行)中记录的各个目标文件,通过LD 链接为built-in.o。对于lib-target(第25行),则通过AR(第22行)是将lib-y 中各个目标文件链接为lib.a。
obj-y:在配置内核时,内核中的文件通过KBuild系统被添加进Makefile中。
... obj-$(CONFIG_DRM_R128) += r128.o obj-$(CONFIG_DRM_I915) += i915.o ...
例如上面,如果CONFIG_DRM_R128被配置为编译进内核,此时CONFIG_DRM_R128就会被“y”替代,就作为了
obj-y
变量的一个元素。
至此,我们就明白了生成vmlinux 的lib.a 以及**built-in.o **文件是如何产生的了。
构建vmlinux.bin或Image
接着我们来研究,如何从vmlinux 一步一步的生成uImage。
在arm 架构目录下的Makefile 文件中,可以找到目标uImage,它依赖于zImage,而目标zImage 又依赖于compressed/vmlinux。再往下追踪,又可以看到zImage 依赖于Image:
#arch/arm/boot/Makefile
$(obj)/compressed/vmlinux: $(obj)/Image FORCE
$(Q)$(MAKE) $(build)=$(obj)/compressed $@
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
@$(kecho) ' Kernel: $@ is ready'
$(obj)/uImage: $(obj)/zImage FORCE
@$(check_for_multiple_loadaddr)
$(call if_changed,uimage)
@$(kecho) ' Image $@ is ready'
对比一下x86架构(第一张图),这个Image就等价于x86架构下的vmlinux.bin,可以把它们当做不同架构下是近似。
接着再对Image追踪下去:
$(obj)/Image: vmlinux FORCE
$(call if_changed,objcopy)
@$(kecho) ' Kernel: $@ is ready'
Image依赖于我们前面构建的vmlinux。在第2行中,出现了一个objcopy,其定义在scripts/Makefile.lib中(if_changed前面分析过,不再赘述,本质上是执行cmd_objcopy):
#scripts/Makefile.lib
cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@
- OBJCOPY:是二进制工具objcopy,这个工具的目的是将ELF格式的文件转为裸二进制格式。
- $<:代表第一个依赖,也就是我们前面构建的vmlinux
- $@:表示规则的目标也就是Image
简言之,这个脚本片段就是将我们构建生成的ELF格式的vmlinux转为裸二进制的vmlinux。
这个裸的vmlinux,在arm架构下称为Image,在x86架构被命名为vmlinux.bin。裸二进制格式去除掉了ELF头文件、Program Header Table、符号表、重定位表等(这些对内核来说是没有意义的,Bootloader加载内核时也不需要这些ELF文件中附加的信息),不过并不会删除保存具体内容的段。
构建压缩后的vmlinux
有了Image之后,我们就可以将焦点转回到为arch/arm/boot/compressed 目录下的vmlinux,其构建的规则如下:
#arch/arm/boot/Makefile
$(obj)/compressed/vmlinux: $(obj)/Image FORCE
$(Q)$(MAKE) $(build)=$(obj)/compressed $@
arch/arm/boot/compressed/vmlinux 的构建规则在arch/arm/boot/compressed 的Makefile文件中:
#arch/arm/boot/compressed/Makefile
$(obj)/piggy.$(suffix_y): $(obj)/../Image FORCE
$(call if_changed,$(suffix_y))
$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.$(suffix_y).o \
$(addprefix $(obj)/, $(OBJS)) $(lib1funcs) $(ashldi3) FORCE
@$(check_for_multiple_zreladdr)
$(call if_changed,ld)
@$(check_for_bad_syms)
在vmlinux 的依赖中有一个piggy.$(suffix_y).o,在编译之前我们使用ls查看arch/arm/boot/compressed 目录,会发现没有piggy 相关命名的文件,如piggy.c 或piggy.S 等。
binwatson@binwatson:~/share/kernel/linux-3.7.4/arch/arm/boot/compressed$ ls
Makefile head-shark.S head.S ofw-shark.c sdhi-sh7372.c
atags_to_fdt.c head-sharpsl.S libfdt_env.h piggy.gzip.S sdhi-shmobile.c
big-endian.S head-shmobile.S ll_char_wr.S piggy.lzma.S sdhi-shmobile.h
decompress.c head-vt8500.S misc.c piggy.lzo.S string.c
head-sa1100.S head-xscale.S mmcif-sh7372.c piggy.xzkern.S vmlinux.lds.in
在Makefile 中仔细查找,我们会发现一个piggy.$(suffix_y) 的文件:
#arch/arm/boot/compressed/Makefile
$(obj)/piggy.$(suffix_y): $(obj)/../Image FORCE
$(call if_changed,$(suffix_y))
$(obj)/piggy.$(suffix_y).o: $(obj)/piggy.$(suffix_y) FORCE
现在我们知道了,piggy.$(suffix_y).o 是由 $(obj)/piggy.$(suffix_y) 生成的。suffix_y 的类型由CONFIG_KERNEL_XXX 参数来决定,如果我们设置了 CONFIG_KERNEL_GZIP 为 y,那么suffix_y = gzip,即使用gzip 的压缩方式,默认就是使用gzip。
#arch/arm/boot/compressed/Makefile
suffix_$(CONFIG_KERNEL_GZIP) = gzip
suffix_$(CONFIG_KERNEL_LZO) = lzo
suffix_$(CONFIG_KERNEL_LZMA) = lzma
suffix_$(CONFIG_KERNEL_XZ) = xzkern
于是就会调用Makefile.lib中的cmd_gzip
对Image进行压缩:
#scripts/Makefile.lib
quiet_cmd_gzip = GZIP $@
cmd_gzip = (cat $(filter-out FORCE,$^) | gzip -n -f -9 > $@) || \
(rm -f $@ ; false)
最后生成piggy.gzip.o(假设我们使用了gzip压缩),再和compressed目录下的其它目标文件一起通过cmd_ld
链接生成新的vmlinux(压缩后的)。
构建zImage
接着我们回到boot目录下的Makefile,再来看看zImage是如何生成的,zImage的构建规则如下:
#arch/arm/boot/Makefile
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
@$(kecho) ' Kernel: $@ is ready'
也是通过调用cmd_objcopy
来生成的,前面链接时又生成了ELF格式的vmlinux,通过objcopy重新将其转为裸二进制文件。
构建uImage
最后是构建uImage,uImage的构建规则如下:
#arch/arm/boot/Makefile
$(obj)/uImage: $(obj)/zImage FORCE
@$(check_for_multiple_loadaddr)
$(call if_changed,uimage)
@$(kecho) ' Image $@ is ready'
#scripts/Makefile.lib
# U-Boot mkimage
# ---------------------------------------------------------------------------
MKIMAGE := $(srctree)/scripts/mkuboot.sh
# SRCARCH just happens to match slightly more than ARCH (on sparc), so reduces
# the number of overrides in arch makefiles
UIMAGE_ARCH ?= $(SRCARCH)
UIMAGE_COMPRESSION ?= $(if $(2),$(2),none)
UIMAGE_OPTS-y ?=
UIMAGE_TYPE ?= kernel
UIMAGE_LOADADDR ?= arch_must_set_this
UIMAGE_ENTRYADDR ?= $(UIMAGE_LOADADDR)
UIMAGE_NAME ?= 'Linux-$(KERNELRELEASE)'
UIMAGE_IN ?= $<
UIMAGE_OUT ?= $@
quiet_cmd_uimage = UIMAGE $(UIMAGE_OUT)
cmd_uimage = $(CONFIG_SHELL) $(MKIMAGE) -A $(UIMAGE_ARCH) -O linux \
-C $(UIMAGE_COMPRESSION) $(UIMAGE_OPTS-y) \
-T $(UIMAGE_TYPE) \
-a $(UIMAGE_LOADADDR) -e $(UIMAGE_ENTRYADDR) \
-n $(UIMAGE_NAME) -d $(UIMAGE_IN) $(UIMAGE_OUT)
通过$(srctree)/scripts
下的mkuboot.sh脚本来生成uImage内核映像。
构建总结
上面就是arm架构的uImage的构建过程,其中head是uImage的64字节的头部,而下图是x86的bzImage的构建过程。
从中我们可以看到,两者构成过程非常相识,对于x86的构建过程分析,可以参考王柏生的《深度探索Linux操作系统:系统构建与原理解析》一书。