(一)Linux内核构建

内核镜像的分类

我们经常能看到内核在编译完成后生产各种映像文件,如:Image 、zImage、bzImage等。

其实最开始出现的是 Image,就一个普通的内核镜像。后来为了节省空间,有了 zImage,进行了压缩可以节省空间。

那么uImage又是什么的?它是uboot专用的映像文件,它是在zImage之前加上一个长度为64字节的“头”,说明这个内核的版本、加载位置、生成时间、大小等信息;其0x40之后与zImage没区别。

几种linux内核文件的区别:

1、vmlinux :编译出来后未压缩最原始的内核文件

2、zImage :vmlinux经过gzip压缩后的文件。

3、bzImage:bz表示“big zImage”,是用bzip2压缩的。两者的不同之处在于,zImage解压缩内核到低端内存(第一个640K),bzImage解压缩内核到高端内存(1M以上)。如果内核比较小,那么采用zImage或bzImage都行,如果比较大应该用bzImage。

4、uImage : U-boot专用的映像文件,它是在zImage之前加上一个长度为0x40的TAG。

5、vmlinuz: bzImage/zImage文件的拷贝或指向bzImage/zImage的符号链接。

6、initrd ,: “initial ramdisk”的简写。一般被用来临时的引导硬件到实际内核vmlinuz能够接管并继续引导的状态

内核镜像bzImage的组成

(一)Linux内核构建_第1张图片

上图为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部分的代码来实现。

内核映像的构建

内核源码结构

Linux内核 – 内核源码结构 (cnblogs.com)

配置文件

make menuconfig

Linux内核配置以及Make menuconfig过程分析

linux内核的配置机制及其编译过程

Kbuild系统

参考:Linux内核Makefile分析

在Linux内核里,每个子目录都有一个makefile,它被称作Kbuilt-makefile,它将当前目录的文件编译成built-in.o、库文件模块文件。然后顶层Makefile里指定这些built-in.o的路径,将它们连接在一起,最后链接成一个vmlinux文件。

内核makefile.txt中将makefile分为 5部分,Kernel MakefileARCH MakefileKBuild 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部分。

编译内核

​ 在编译嵌入式系统使用的内核时,我们会在内核的根目录下面执行make uImage来生成内核镜像,或者make后面不接入任何目标。在没有接目标的时候,默认也是生成内核映像uImage。顶层Makefile会通过include指令,将架构相关的Makefile文件引入顶层Makefile中:

include $(srctree)/arch/$(SRCARCH)/Makefile

在如arm架构下的arch/arm/Makefile中:

# Convert bzImage to zImage
bzImage: zImage

zImage Image xipImage bootpImage uImage: vmlinux
    $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@

从第4行各种的依赖,我们可以看到无论是生成何种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的如何构建的:

# 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)

​ 在顶层Makefile中,我们可以看到vmlinux依赖于scripts/link-vmlinux.sh、$(vmlinux-deps)、FORCE。第7行处通过call引用if_changed函数:

# 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, @:)

​ if_changed函数定义在scripts/Kbuild.include里面,第6行处有一个$(cmd_$(1))的东西,我们展开后就成了cmd_link-vmlinux,就是我们刚刚在顶层Makefile面列出来的,于是我们回到顶层Makefile的cmd_link-vmlinux变量:

cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)

下面,我们先对 $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)进行分析:

  • CONFIG_SHELL:指定使用的shell解释器类型,这个不重要。

  • $<:表示第一个依赖,由于cmd_link-vmlinux在vmlinux的“规则体”下面,所以这个$<,翻译一下就是scripts/link-vmlinux.sh

  • LD:链接器类型,在顶层Makefile的前面,我们一般会配置使用的各种工具链,这个LD是作为参数传给link-vmlinux.sh脚本使用的,可以猜测编译最后会使用这个链接器来链接。

    # 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:链接使用到的一些参数,我们不关心。

变量cmd_link-vmlinux的值$(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)本质是执行scripts/link-vmlinux.sh脚本,而LD、LDFLAGS、LDFLAGS_vmlinux作为参数传递给link-vmlinux.sh脚本。

从名字我们就可以猜测该脚本用于链接生成vmlinux。下面我们研究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的源文件。

    # 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)
    export KBUILD_LDS          := arch/$(SRCARCH)/kernel/vmlinux.lds
    

    可以看到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文件,那么它们在如何生成的呢?

回到前面的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)=$@

从第9行来看,目标vmlinux-deps的构建规则,其“规则体”是空的,不过它依赖于vmlinux-dirs,于是我们继续追踪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中的规则进行编译,下面我们追踪进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涵盖了内核映像(KBUILD_BUILTIN)和模块(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.a。接下来,我们再在看看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就是将变量obj-y中记录的各个目标文件,通过LD链接为built-in.o。对于lib-target,则通过AR是将lib-y中各个目标文件链接为lib.a。

至此,我们就明白了生成vmlinux的lib.a以及built-in.o文件是如何产生的了。

总结一下,我们要生成vmlinux,就需要依赖init/、kernel/、drivers/等目录下的built-in.o文件,同时还需要架构相关的head.xxx.olib/lib.a文件。在编译生成vmlinux之前,Makefile的依赖关系会确保后面三者先产生,然后再链接生成vmlinux。

构建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'

我们可以看到,其依赖于我们前面构建的vmlinux,在第2行中,我们看到了objcopy,其在scripts/Makefile.lib中(if_changed前面分析过,不再赘述):

#scripts/Makefile.lib
cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@
  • OBJCOPY:是二进制工具objcopy,这个工具的目的是将ELF格式的文件转为裸二进制格式。
  • $<:代表第一个依赖,也就是我们前面构建的vmlinux
  • $@:表示规则的目标也就是Image

简言之,这个脚本片段就是我们构建生成的ELF格式的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内核映像。

构建总结

(一)Linux内核构建_第2张图片
上面就是arm架构的uImage的构建过程,其中head是uImage的64字节的头部,而下图是x86的bzImage的构建过程。
(一)Linux内核构建_第3张图片
从中我们可以看到,两者构成过程非常相识,对于x86的构建过程分析,可以参考王柏生的《深度探索Linux操作系统:系统构建与原理解析》一书。

你可能感兴趣的:(Linux内核,linux)