内核makefile详解

1. 内核镜像的分类

我们经常能看到内核在编译完成后生产各种映像文件,如: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能够接管并继续引导的状态

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过程分析

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部分。

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-targetlib-targetextra-ysubdir-ymalways。)

先看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操作系统:系统构建与原理解析》一书。

  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Makefile是一种用于自动化构建和管理软件项目的文件。它包含了一系列规则和指令,用于描述如何编译、链接和清理项目中的源代码和目标文件。Makefile中的规则和指令可以根据依赖关系自动地决定哪些文件需要重新编译和重新链接,从而提高项目的编译效率。 Makefile中的规则由两部分组成:目标和依赖。目标是需要生成的文件,而依赖是生成目标所需要的文件或者其他目标。当一个目标的依赖发生变化时,Make会自动执行相应的指令来重新生成目标。 在Makefile中,可以使用伪目标来声明一些不真实存在的目标。这些目标通常用于执行一些特殊的操作,比如清理生成的文件。通过将指令声明为伪目标,可以确保这些指令在每次执行时都被执行,而不管是否存在对应的依赖文件。 Makefile还支持使用函数和自动变量来简化规则的编写。函数可以用于对变量进行操作,比如获取文件的扩展名或目录名。自动变量则可以在规则中引用目标和依赖的文件名,从而避免重复写入文件名。 综上所述,Makefile是一个用于自动化构建和管理软件项目的文件,通过定义规则和指令来描述如何编译、链接和清理项目中的源代码和目标文件。它可以根据依赖关系自动地决定哪些文件需要重新编译和重新链接,并支持使用伪目标、函数和自动变量来简化规则的编写。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Makefile详解](https://blog.csdn.net/qq_34968572/article/details/102721670)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值