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