firewood

linux内核编译学习笔记

 linux内核由于庞大的代码量和复杂的代码结构,使用通用的makefile形式不仅存在很大的工作量,而且内核的可配置性不好,每次裁剪模块都需要深入到每一层的目录结构修改makefile,并不现实。所以linux提供了一套configure和makefile体系,根据config中的配置操作生成各个子目录下的makefile,决定哪些文件参与编译。内核本身包含了顶层makefile文件,该文件指示了通用的框架。而各个子目录下的makefile文件也不像传统的makefile文件编写格式,它们是结合scripts/目录下的一系列规则文件使用的,只需要指明需要参与编译的子目录/文件即可。在学习了解内核编译体系结构时,因之前基础薄弱,而且对makefile的语法并不熟悉,所以感觉晦涩难懂。后来学习参考同行的博客,对整个脉络有了整体的思路,对makefile体系架构有了更深的了解。

本文重点记录已经学习到的知识,从最终生成的image文件反推,一步一步看如何生成了image文件。首先需要知道,make命令肯定是要执行顶层目录下的makefile文件。

一、makefile文件系统结构

1、scripts/目录下的makefile规则文件

scripts目录下包含makefile规则文件。这些文件相当于制定了一套规则,会解析子目录下的makefile文件。由顶层makefile文件、/sripts子目录下的makefile规则文件和子目录下的makefile文件共同形成了内核的makefile体系。

2、顶层Makefile文件

在顶层目录下,执行make命令生成内核镜像文件,必然是由顶层Makefile作为入口。对于linux来说,顶层Makefile定义的是通用的规则,与体系架构相关的都在各自目录/arch/*/的makefile中定义;顶层Makefile中生成的通用的中间文件是vmlinux,这个文件是将参与编译的所有源码(不包含镜像压缩后的解压缩代码)编译、链接后生成的目标文件;在/arch/arm/和/arch/x86/目录下,定义了由vmlinux生成最终镜像文件的过程,对于arm体系架构来说,要生成的是uImage,对于x86体系架构来说,要生成的内核镜像文件是bzImage。

3、子目录下的makefile文件

子目录下的makefile文件,基本(还是全部?)都是在顶层makfile中嵌套调用的,而且这些makefile文件不是直接执行的,而是通过/scripts/makefile.build文件对其解析后执行。

二、生成vmlinux

在顶层makefile中,直接搜索vmlinux,即可以vmlinux的生成规则。

 按照makefile的编译规则,vmlinux为目标文件,依赖于冒号后面跟的文件。生成由依赖文件生成目标文件的动作由后面的代码实现。

宏定义的部分暂不讨论,重点看call  if_changed,link-vmlinux 一句话。$(call if_changed,link-vmlinux)是调用了if_changed函数,并向其传入了link-vmlinux参数,其实现的功能是调用  cmd_link-vmlinux函数。cmd_link-vmlinux函数的定义,将第一个语句展开:

/bin/bash  scripts/link-vmlinux.sh   ld -m elf_i386 --emit-relocs --build-id           (以i386架构为例)

所以调用了link-vmlinux.sh脚本文件执行,在link-vmlinux.sh中通过编译、链接,最终根据$(vmlinux-deps)变量生成了vmlinux。

 1 # Final link of vmlinux with optional arch pass after final link
 2 cmd_link-vmlinux =                                                 \
 3     $(CONFIG_SHELL) $< $(LD) $(KBUILD_LDFLAGS) $(LDFLAGS_vmlinux) ;    \
 4     $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)
 5 
 6 vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE
 7 ifdef CONFIG_HEADERS_CHECK
 8     $(Q)$(MAKE) -f $(srctree)/Makefile headers_check
 9 endif
10 ifdef CONFIG_GDB_SCRIPTS
11     $(Q)ln -fsn $(abspath $(srctree)/scripts/gdb/vmlinux-gdb.py)
12 endif
13     +$(call if_changed,link-vmlinux)

那么$(vmlinux-deps)变量又是什么呢?下面即对其进行分析。

1、$(init-y)  $(drivers-y) $(core-y)等变量

顶层Makefile中定义了$(init-y)  $(init-m)  $(core-y)  $(core-m)  $(drivers-y)  $(drivers-m)  $(net-y)  $(net-m)  $(libs-y)  $(libs-m)  $(virt-y) $(head-y)变量。由这些变量的名字可以推测它们是按照内核的各个组件定义的,包含了内核的各个组成部分。core-y定义了内核的核心内容,包括kernel、mm、fs、ipc、usr等目录下的内容,drivers-y定义了驱动相关的driver目录下的内容,net-y定义了网络相关的net目录下的内容。他们都是在顶层Makefile中定义的。

ifeq ($(KBUILD_EXTMOD),)
# 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/
virt-y        := virt/

core-y        += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/
endif # KBUILD_EXTMOD

在顶层Makefile中是没有定义$(head-y)的,因为这是与体系结构相关的内容。所以是在顶层makefile中包含的/arch/x86/Makefile文件定义的。另外,处理器架构相关的makefile文件中,还将处理器架构自有的内容补充到了相应的变量中。详见如下代码(/arch/x86/Makefile)。

head-y := arch/x86/kernel/head_$(BITS).o
head-y += arch/x86/kernel/head$(BITS).o
head-y += arch/x86/kernel/ebda.o
head-y += arch/x86/kernel/platform-quirks.o

libs-y  += arch/x86/lib/

# See arch/x86/Kbuild for content of core part of the kernel
core-y += arch/x86/

# drivers-y are linked after core-y
drivers-$(CONFIG_MATH_EMULATION) += arch/x86/math-emu/
drivers-$(CONFIG_PCI)            += arch/x86/pci/

# must be linked after kernel/
drivers-$(CONFIG_OPROFILE) += arch/x86/oprofile/

# suspend and hibernation support
drivers-$(CONFIG_PM) += arch/x86/power/

drivers-$(CONFIG_FB) += arch/x86/video/

  由上面的代码看出,这些变量中除了$(head-y)中直接指定.o文件,其余均是目录。显然vmlinux不会直接依赖于目录,还是要依赖于目录下的文件。所以又对这些目录进行了处理,在目录后面添加built-in.a。built-in.a文件是由当前目录及其子目录下所有参与编译的源文件共同生成的,这样才能覆盖到内核的所有源文件。patsubst命令,筛选出符合格式的参数,并用后面的内容替代。也就是对最后一个字符是'/'的目录,添加built-in.a,组成drivers/built-in.a  kernel/built-in.a等形式。

另外,注意再添加built-in.a之前,也就是$(init-y)等变量中还都是目录形式的时候,定义了vmlinux-dirs变量,而且将$(init-y)等变量中的目录的'/'删除,这样vmlinux-dirs中定义的就是init  kernel  drivers等格式的目录了。

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


init-y		:= $(patsubst %/, %/built-in.a, $(init-y))
core-y		:= $(patsubst %/, %/built-in.a, $(core-y))
drivers-y	:= $(patsubst %/, %/built-in.a, $(drivers-y))
net-y		:= $(patsubst %/, %/built-in.a, $(net-y))
libs-y1		:= $(patsubst %/, %/lib.a, $(libs-y))
libs-y2		:= $(patsubst %/, %/built-in.a, $(filter-out %.a, $(libs-y)))
virt-y		:= $(patsubst %/, %/built-in.a, $(virt-y))

  2、$(vmlinux-deps)变量

 vmlinux-deps通过KBUILD_VMLINUX-INIT/  MAIN/  LIBS/  LDS变量,又指向了 $(head-y) $(init-y) $(core-y)  $(libs-y) $(drivers-y)  $(net-y) $(virt-y)变量,以及相应体系架构下的链接文件vmlinux.lds。

# Externally visible symbols (used by link-vmlinux.sh)
export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y)
export KBUILD_VMLINUX_LIBS := $(libs-y1)
export KBUILD_LDS          := arch/$(SRCARCH)/kernel/vmlinux.lds
export LDFLAGS_vmlinux
# used by scripts/package/Makefile
export KBUILD_ALLDIRS := $(sort $(filter-out arch/%,$(vmlinux-alldirs)) arch Documentation include samples scripts tools)
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS)

目前的分析来看,vmlinux依赖的次顶层目录下的built-in.a(/arch/目录下与处理器体系架构相关的是单独添加的,并不包含/arch/built-in.a)已经解释清楚了。这些次顶层目录下的built-in.a需要覆盖其所在目录下所有的源文件(包括子目录下的源文件)才可以。因为linux源码的层级目录复杂,次顶层目录drivers  mm等下面都还有多层目录结构,那么如何覆盖所有子目录下的文件,会是一项值得研究的工作。

3、编译生成$(vmlinux-deps)

直观的思路呢,可以在每个次顶层目录下编写一个makefile,令其built-in.a依赖于当前目录下的所有文件及子目录下的文件。但是这些维护起来比较复杂,首先次顶层目录下包含的文件非常多,会令makefile的维护难度增大,而且内核的配置项繁多,定制化的内核经常需要裁剪或者添加某个文件,文件的增删都会很复杂。

还有一种方式,为每个目录及其子目录都编写makefile文件,令其当前目录下的built-in.a包含当前目录下的源文件对应的.o文件和子目录下的built-in.a文件;这样层层传递,可以覆盖内核源码中的每个文件。这样实现,每一层的makefile都会比较单纯,只需要考虑当前目录下的文件和子目录。要增删某个文件,只需要到其所在目录下修改makefile文件即可。这样看呢,每层目录的makefile都有相同的实现方式,就是根据当前目录下的文件及子目录的built-in.a生成当前目录下的built-in.a。从可维护性考虑,linux将这些相同的编译规则提炼出来,放到了/scripts目录下的makefile.build文件中,在编译时对每层目录都是用makefile.build文件作为入口,将待编译的目录路径作为参数传入,形成了makefile.build(待编译目录)的方式。而每个目录下仍然有makefile文件,只不过这些文件中只需要定义哪些文件参与编译就可以,他们都会经过makefile.build处理并生成相应的built-in.a。

在顶层Makefile中,linux定义了次顶层目录built-in.a的生成规则,其依赖于$(vmlinux-dirs);  而$(vmlinux-dirs)的生成规则参见如下代码,它是通过调用

“$(Q)$(MAKE) $(build)=$@ need-builtin=1”语句生成的。

$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;

PHONY += $(vmlinux-dirs)
$(vmlinux-dirs): prepare scripts
	$(Q)$(MAKE) $(build)=$@ need-builtin=1

 $(build)变量是在/scritps/build.include文件中定义的。

###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj

将$(build)变量展开后,相当于

make -f $(srctree)/scripts/makefile.build obj=$@

而$@符号所指的目标文件就是$(vmlinux-dirs)变量,前面讲过,这个变量中包含的就是所有编译的目录,init   kernel   drivers等等目录。make命令的参数-f是用于指定专门的makefile文件,-f指定了使用的makefile文件为scripts目录下的makefile.build,   然后传入了obj=$@。这一行代码相当于调用makefile.build分别处理init  kernel  drivers等等目录。如何生成这些目录下的built-in.a文件,则需要对makefile.build文件进行具体分析。

三、makefile.build文件解析

 scripts/makefile.build文件的内容比较复杂,其不仅实现了我们上面说到的主线工作,还会涉及内核模块的编译规则定义等内容。本身Makefile的语法也晦涩难懂,所以此处主要讲我们上面分析到的主线内容。

Makefile.build是通过$(build)变量定义的,经常使用的语法如下(compressed为示例的子目录名称,可以为当前目录下的任意子目录):

make $(build)=$(obj)/compressed

展开后,相当于

make -f  $(srctree)/scripts/makefile.build   obj=$(obj)/compressed

  make命令如果不指定-f参数,那么默认使用当前目录下的makefile。如果使用-f参数,则使用后面指定的makefile文件,此处就是scripts目录下的makefile.build。而且make命令还可以向makefile文件中传入参数,传入的参数obj就是待编译的子目录。而当前的$(obj)又是由编译当前目录的上一级目录传入,是当前目录的路径。所以使用$(obj)/compressed就表示了当前目录下的compressed子目录。

按照上文的梳理,makefile.build应该具备两个功能,1)定义当前$(obj)/built-in.a文件的生成规则;2)通过makefile.build处理子目录,相当于makefile.build的嵌套调用(递归操作)。下文继续梳理这两个功能相关的脉络。

1、生成$(obj)/built-in.a

1)包含$(obj)下的makefile文件   

首先需要知道,在调用makefile.build的同时向其传递了参数$(obj)=待处理的目录。因为参与编译的文件都是在$(obj)目录的makefile文件中定义的,所以在makefile.build中需要包含$(obj)目录下的makefile,这样才能获取源文件列表。

makefile.build文件中定义了新变量src,并且src=$(obj)。下面代码首先判断传入的路径是否是绝对路径,如果不是则将其修改为绝对路径。然后包含obj目录下的kbuild文件或者Makefile文件。

kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)

一般来说,各级目录中的makefile中只需要将所有编译到内核中的文件或者目录添加到obj-y变量中,以及将需要编译成模块的文件或者目录添加到obj-m变量中:

obj-y += time/
obj-$(CONFIG_FUTEX) += futex.o

2)生成built-in.a

 在makefile.build中,定义了生成目标builtin-target := $(obj)/built-in.a。

  built-in.a是依赖于$(real-obj-y)变量的,将$(real-obj-y)变量链接生成了built-in.a。这个变量显然是需要与各目录下的makefile定义的$(obj-y)变量串联起来的。

quiet_cmd_ar_builtin = AR      $@
      cmd_ar_builtin = rm -f $@; \
                     $(AR) rcSTP$(KBUILD_ARFLAGS) $@ $(filter $(real-obj-y), $^)

$(builtin-target): $(real-obj-y) FORCE
	$(call if_changed,ar_builtin) 

$(obj-y)变量只是定义了当前目录下的二进制文件和子目录,需要将其转变为$(real-obj-y),首先要将子目录转变为子目录下的built-in.a文件。然后还要精简这些文件,用strip命令删除这些二进制文件中的符号表和调试信息。这部分工作是在makefile.build包含的makefile.lib文件中实现的。(注意,include $(obj)/makefile一定要在include makefile.lib之前,因为makefile.lib中需要用到$(obj)/makefile中定义的变量)。梳理出的代码如下。

第一行代码是将obj-y中的子目录替换为“子目录/built-in.a”。第二行代码是利用strip命令精简二进制文件。第三行代码为依赖文件添加父目录的路径,将路径完整化,这样在源码顶层目录执行的make命令可以找到这些文件。最终,kernel目录的real-obj-y= (kernel/futex.o   kernel/time/built-in.a  等等文件)。

obj-y		:= $(patsubst %/, %/built-in.a, $(obj-y))

real-obj-y := $(foreach m, $(obj-y), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y))),$($(m:.o=-objs)) $($(m:.o=-y)),$(m)))

real-obj-y	:= $(addprefix $(obj)/,$(real-obj-y))

  2、通过makefile.build递归处理子目录

在makefile.build处理某个目录时,还需要递归调用makefile.build处理当前目录下的所有子目录。这样只要使用makefile.build处理一个目录,就可以实现对其内部所有文件(包括所有子目录下的文件)的编译。

子目录的定义在subdir-ym变量中,是在makefile.lib中定义的,根据$(obj-y) 和 $(obj-m)变量生成的所有子目录列表。如下代码所示,__subdir-y首先筛选出obj-y下的所有目录,并且删除其后面的'/'。比如kernel目录下的time子目录,在kernel/Makefile文件中添加到obj-y变量中 obj-y += time/,在subdir-y中删除了最后的'/'符号为time,在subidr-ym最终添加父目录路径后为kernel/time.

__subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y	+= $(__subdir-y)
__subdir-m	:= $(patsubst %/,%,$(filter %/, $(obj-m)))
subdir-m	+= $(__subdir-m)

# Subdirectories we need to descend into
subdir-ym	:= $(sort $(subdir-y) $(subdir-m))

subdir-ym := $(addprefix $(obj)/,$(subdir-ym))

  首先看__build的定义,按这个格式说明__build是需要生成的目标文件。按照make体系的编译规则,如果没有显式指定其make生成的目标文件,那么默认生成makefile文件内定义的第一个目标文件。在makefile.build中定义的第一个目标文件是__build。它后面依赖的$(builtin-target)(即 $(obj)/built-in.a)、$(subdir-ym)等也会间接成为目标文件。

<makefile.build> Line70:
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
	 $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
	 $(subdir-ym) $(always)

从subidr-ym的定义可知$(subdir-ym)中定义的只是目录,下面定义了$(subdir-ym)的生成规则,这段代码并不是一定会生成$(subdir-ym)这些文件夹,只是当makefile认为需要$(subdir-ym)时,会执行下面的语句。至于执行了什么操作,生成了什么文件,都是由下面的语句而定。此处即相当于递归处理了$(subdir-ym)中的子目录。这样,只需要在顶层makefile中,通过调用makefile.build,并传入需要编译的目录($(vmlinux-deps)),这样makefile.build会自动编译相应目录下所有的文件(递归编译所有子目录)生成built-in.a。

<makefile.build>  Line515:
PHONY += $(subdir-ym)
$(subdir-ym):
	$(Q)$(MAKE) $(build)=$@ need-builtin=$(if $(findstring $@,$(subdir-obj-y)),1)  

 内核通过Makefile.build文件,将源码目录树下的所有文件串联起来,生成统一的目标文件vmlinux。

  四、生成bzImage

        第二章中,在顶层Makefile中生成了通用的内核目标文件vmlinux。至于最终的镜像文件,则是由各个体系架构自己定义的格式和规则来生成的,所以从vmlinux到最终的image文件,就需要到arch/$(SRCARCH)目录下研究。本章将采用反推的方式,从bzImage开始,一步一步看bzImage和vmlinux之间是如何建立联系的。

        顶层Makefile中包含了对应体系架构的makefile,执行make命令自然也会自动执行包含的makefile。

include  arch/$(SRCARCH)/Makefile

  在arch/x86/Makefile中,有如下代码:  

# KBUILD_IMAGE specify target image being built
KBUILD_IMAGE := $(boot)/bzImage

bzImage: vmlinux
ifeq ($(CONFIG_X86_DECODER_SELFTEST),y)
	$(Q)$(MAKE) $(build)=arch/x86/tools posttest
endif
	$(Q)$(MAKE) $(build)=$(boot) $(KBUILD_IMAGE)
	$(Q)mkdir -p $(objtree)/arch/$(UTS_MACHINE)/boot
	$(Q)ln -fsn ../../x86/boot/bzImage $(objtree)/arch/$(UTS_MACHINE)/boot/$@

当我们执行make bzImage命令时,编译器就会找到该文件中对bzImage的定义。显然,bzImage依赖于vmlinux文件。通过第一个语句make $(build)=$(boot) $(KBUILD_IMAGE),生成/arch/x86/boot/bzImage.   后两句是在最终的目录下创建bzImage的链接文件,将其链接到/arch/x86/boot/bzImage。

解析第一句的语法,其中$(build)的定义为:

scripts/build.include文件: 

###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj

  展开第一个语句,即make    -f    $(srctree)/scripts/Makefile.build       dir=.     obj=arch/x86/boot      arch/x86/boot/bzImage

make命令的参数-f是用于指定专门的makefile文件,-f指定了使用的makefile文件为scripts目录下的makefile.build,   然后传入了dir和obj两个参数,  生成的是/arch/x86/boot下的zImage。makefile.build文件的分析请参照第三节。

因为此处的make命令显示制定了生成的文件/arch/x86/boot/zImage,那么就先找到在哪里定义了bzImage的生成规则。显然不会在通用规则文件里,那么就只能是在的/arch/x86/boot目录(传入的$obj参数)的makefile文件中了。

此处的$(obj)即是/arch/x86/boot目录。该目录下的目标文件是bzImage,依赖于setup.bin  vmlinux.bin 和 tools/build工具,调用cmd_image函数生成目标文件bzImage。cmd_image中调用tools/build工具,根据 /arch/x86/boot/目录下的setup.bin   vmlinux.bin 和 zoffset.h 生成bzImge。

quiet_cmd_image = BUILD   $@
silent_redirect_image = >/dev/null
cmd_image = $(obj)/tools/build $(obj)/setup.bin $(obj)/vmlinux.bin \
                   $(obj)/zoffset.h $@ $($(quiet)redirect_image)

$(obj)/bzImage: $(obj)/setup.bin $(obj)/vmlinux.bin $(obj)/tools/build FORCE
    $(call if_changed,image)
    @$(kecho) 'Kernel: $@ is ready' ' (#'`cat .version`')'

 那接下来就要依次去看这几个文件是如何生成的?

首先是核心的内核文件vmlinux.bin,它是由$(obj)/compressed/vmlinux文件生成的。call  if_changed,objcopy  调用了cmd_objcopy函数,该函数在Makefile.lib中定义,就是调用了GNU的objcopy工具,将$(obj)/compressed目录下的vmlinux生成了$(obj)/vmlinux.bin。

$(obj)/vmlinux.bin: $(obj)/compressed/vmlinux FORCE
    $(call if_changed,objcopy)

arch/x86/boot/compressed/vmlinux如何生成?自然是去compressed目录下的Makefile。在Makefile.build的规则中已经提到,通过Makefile.build传入/arch/x86/boot路径后,会自动include /arch/x86/boot路径下的Makefile,同时对/arch/x86/boot路径下的子目录执行Makefile.build脚本,自然会处理子目录compressed下的makefile文件。

顾名思义,compressed文件夹是定义了内核文件压缩的相关内容。所以compressed目录下必然会有对内核文件的压缩动作、添加解压缩的相关代码等工作。compressed目录下的vmlinux文件是依赖于$(vmlinux-objs-y)变量的,$(vmlinux-objs-y)变量包含的文件链接生成了vmlinux。

# We need to run two commands under "if_changed", so merge them into a
# single invocation.
quiet_cmd_check-and-link-vmlinux = LD      $@
      cmd_check-and-link-vmlinux = $(cmd_check_data_rel); $(cmd_ld)

$(obj)/vmlinux: $(vmlinux-objs-y) FORCE
    $(call if_changed,check-and-link-vmlinux)

$(vmlinux-objs-y)变量是在当前的Makefile定义的,下面的代码为$(vmlinux-objs-y)的一部分内容,会根据CONFIG配置来决定是否包含某些文件。

vmlinux-objs-y := $(obj)/vmlinux.lds $(obj)/head_$(BITS).o $(obj)/misc.o \
    $(obj)/string.o $(obj)/cmdline.o $(obj)/error.o \
    $(obj)/piggy.o $(obj)/cpuflags.o

vmlinux-objs-$(CONFIG_EARLY_PRINTK) += $(obj)/early_serial_console.o
vmlinux-objs-$(CONFIG_RANDOMIZE_BASE) += $(obj)/kaslr.o
ifdef CONFIG_X86_64
    vmlinux-objs-$(CONFIG_RANDOMIZE_BASE) += $(obj)/kaslr_64.o
    vmlinux-objs-y += $(obj)/mem_encrypt.o
    vmlinux-objs-y += $(obj)/pgtable_64.o
endif

正常来说,$(vmlinux-objs-y)肯定是需要包含根目录下通用makefile生成的vmlinux文件的,但是并没有看到根目录下vmlinux的显示定义。为什么呢????其实,根目录下的vmlinux是隐式包含在了piggy.o中。而piggy.s也很神奇,它并不是compressed文件夹下自带的文件,在下载的内核源码中是找不到piggy.s文件的,它是在Makefile在编译过程中动态生成的。在compressed/Makefile的末尾,定义了piggy.s文件:

quiet_cmd_mkpiggy = MKPIGGY $@
      cmd_mkpiggy = $(obj)/mkpiggy $< > $@ || ( rm -f $@ ; false )

targets += piggy.S
$(obj)/piggy.S: $(obj)/vmlinux.bin.$(suffix-y) $(obj)/mkpiggy FORCE
    $(call if_changed,mkpiggy)

可见,piggy.s文件依赖于arch/x86/boot/compressed/vmlinux.bin.$(suffix-y)文件,通过cmd_mkpiggy函数,展开该函数如下

/arch/x86/boot/compressed/mkpiggy  /arch/x86/boot/compressed/vmlinux.bin.$(suffix-y)  >  piggy.S  || (rm -f piggy.S ; false)

其中$(suffix-y)是通过config定义的压缩形式,包括gz/ bz2/ xz等,此处以gz为例;

suffix-$(CONFIG_KERNEL_GZIP)	:= gz

所以通过调用mkpiggy程序,传入/arch/x86/boot/compressed/vmlinux.bin.gz)参数,打印消息生成了piggy.S。而mkpiggy也是在make过程中由源码文件mkpiggy.c生成的,mkpiggy.c中的打印消息,就是生成的piggy.S内容。

其中的.incbin  "arch/x86/boot/compressed/vmlinux.bin.gz"就是采用包含二进制的方式,直接添加vmlinux.bin.gz文件。也就是在生成的piggy.o中会包含vmlinux.bin.gz的全部内容。

    printf(".section \".rodata..compressed\",\"a\",@progbits\n");
    printf(".globl z_input_len\n");
    printf("z_input_len = %lu\n", ilen);
    printf(".globl z_output_len\n");
    printf("z_output_len = %lu\n", (unsigned long)olen);

    printf(".globl input_data, input_data_end\n");
    printf("input_data:\n");
    printf(".incbin \"%s\"\n", argv[1]);
    printf("input_data_end:\n");

那么再看如何生成了arch/x86/boot/compressed/vmlinux.bin.gz文件,同样在compressed目录的Makefile中给出了定义。就是调用gzip工具,将arch/x86/boot/compressed/vmlinux.bin压缩成了vmlinux.bin.gz。

vmlinux.bin.all-y := $(obj)/vmlinux.bin
vmlinux.bin.all-$(CONFIG_X86_NEED_RELOCS) += $(obj)/vmlinux.relocs

$(obj)/vmlinux.bin.gz: $(vmlinux.bin.all-y) FORCE
    $(call if_changed,gzip)

那么arch/x86/boot/compressed/vmlinux.bin是如何生成呢,也是在compressed目录的Makefile中定义。利用GNU的objcopy工具处理vmlinux文件,生成了$(obj)/vmlinux.bin文件。注意,vmlinux是没有添加目录前缀的,也是顶层目录下的vmlinux。

$(obj)/vmlinux.bin: vmlinux FORCE
	$(call if_changed,objcopy)

  至此,从顶层目录vmlinux到bzImage的创建过程分析完成。

       最后,正向梳理一遍vmlinux到bzImage的创建过程。

Vmlinux--------->$(boot)/compressed/vmlinux.bin--------->$(boot)/compressed/vmlinux.bin.gz --------->$(boot)/compressed/piggy.o -----------                > $(boot)/compressed/vmlinux    ---------------> $(boot)/vmlinux.bin + $(boot)/setup.bin  +  $(boot)/zoffset.h  --------------->$(boot)/bzImage

posted on 2019-10-09 18:38  firewood  阅读(1404)  评论(0编辑  收藏  举报

导航