BUILD LINUX

1 構建 LINUX

這裡以 linux/ 做為 LINUX 原始碼根目錄的路徑名稱,以構建做為 BUILD 的翻譯。 LINUX 原始碼包裝已經完成了大部分的構建步驟,使用者不需要再執行 autogen、autoconfig 等步驟。 只要直接在解壓縮的原始碼根目錄執行命令 .configure,再執行 make,就可以產生安裝 LINUX 所需要的映像檔。

構建 LINUX 有四個最主要的編譯檔,linux/Makefile、linux/arch/x86/Makefile、linux/arch/x86/boot/Makefile、linux/arch/x86/boot/compressed/Makefile。
編譯檔 linux/Makefile 會產生 vmlinux。
編譯檔 linux/arch/x86/Makefile 會包含到 linux/Makefile,所以 linux/arch/x86/Makefile 的目標會在 linux/Makefile 一起被執行, 以至於能進入 linix/arch/x86/kernel 編譯核心映像檔 VMLINUX ,並進入 linix/arch/x86/boot 編譯設置程式 setup.bin 和 bzImage。 安裝 linux 時,bzImage 會被複製到 /boot 目錄,做為 linux 啟動時的作業系統核心映像,由 linux/arch/x86/boot/Makefile 的目標 install 所表示。

完成 LINUX 的構建後,就可以使用 BZIMAGE 製作開機片,或者將 BZIMAGE 安裝到 /boot 目錄,並命名為 vmlinux-xxx.xxx.xxx 做為作業系統核心映像檔。


1.1 檔案組織

從 LINUX 的根目錄可以大概地了解 LINUX 的輪廓。 這些目錄不一定是核心程式目錄,也有說明檔目錄、工具程式目錄、描述檔目錄等。 這裡只列出程式碼根目錄的子目錄,並簡單的說明目錄的功用。

LINUX 根目錄表:

目錄名稱說明
user使用者空間的早期檔案系統。
kernel核心程式。
mm記憶體管理程式。
fs檔案系統程式。
ipc程序間通訊函式庫。
security安全性程式。
crypto密碼學函式庫。
block區塊裝置函式庫。
drivers所有裝置驅動程式。
sound音效函式庫與驅動程式。
firmware韌體程式。
init作業系統初始化程式。
net網路函式庫。
lib作業系統函式庫。
tools工具程式。
scripts描述檔。
samples??。
include核心標頭檔。
Documentation說明文件檔。
arch架構相關程式。
virtKVM 虛擬機器程式。

構建 LINUX 時,頂層編譯檔會將程式碼分成幾個目的檔群,包括 core、driver、init、net、lib。
core 目的檔群,包括目錄 user、kernel、mm、fs、ipc、security、crypto、block。
driver 目的檔群,包括目錄 drivers、sound、firmware。
init 目的檔群,包括目錄 init。
net 目的檔群,包括目錄 net。
lib 目的檔群,包括目錄 lib。

以 I386 架構來說,linux/arch/x86 中的子目錄也都會加入到 core、driver、net、lib,一同編譯成核心映像。


1.2 構建 VMLINUX

在 LINUX 構建過程中,會產生好幾個不同位置的 vmlinux 檔案。 這裡的 vmlinux 是指在頂層目錄的 linux/vmlinux,是最初形成的核心映像。 linux/vmlinux 會經過好幾到手續,成為 bzImage。

下表是核心映像的演化表,體積值並不是固定的值,只是用來說明各檔案之間的差異。

檔案位址說明
linux/vmlinux.o13993621 位元組。ELF 格式目的檔,匯集所有的目的檔群。
linux/vmlinux13739054 位元組。將 linux/vmlinux.o 依照連結檔 vmlinux.lds ,將目的檔群依照某一秩序連結成執行檔。
linux/arch/x86/boot/compressd/vmlinux.bin9259456 位元組。這是將 linux/vmlinux 剔除不必要的區段與符號而形成的核心映像,。
linux/arch/x86/boot/compressd/vmlinux.bin.gz4769350 位元組。從 vmlinux.bin 壓縮而成,。
linux/arch/x86/boot/compressd/vmlinux4794024 位元組。將 vmlinux.bin.gz 和解壓縮程式碼連結成一個自我解壓縮核心映像。
linux/arch/x86/boot/vmlinux.bin4786596 位元組。將 .../compressd/vmlinux 的不需要區段資料剔除,大約省了 7KB 的體積。本質上還是一個自我解壓縮核心映像檔。
linux/arch/x86/boot/bzImage4799920 位元組。將 setup.bin 和 vmlinux.bin 連接成一個大的核心映像檔。這個映像檔就是用來安裝並啟動 LINUX 作業系統的核心映像檔。

在 LINUX 原始碼根目錄,執行編譯執行器(make),編譯執行器會讀取頂層編譯檔 linux/Makefile,開始構建 vmlinux。 vmlinux 主要是是由 vmlinux-init 和 vmlinux-main 所包含的目的檔建構起來的。 大部分是核心樹的頂層目錄群的 build-in.o 的檔案,其他則是在 arch/x86/Makefile 中指定。 目的檔的連結次序很重要,第一個連結的必須是 vmlinux-init,而 vmlinux-init 內的第一個目的檔必須是 head_32.o。 head_32.o 是整個 vmlinux 映像的入口點,其原始碼為 linux\arch\x86\kernel\head_32.S,是核心起頭程式。

這裡說明頂層編譯檔 linux/Makefile 的編譯思路。

VMLINUX

頂層編譯檔的目標是 vmlinux,只要得到 vmlinux 頂層編譯檔的工作就算完成,而這個 vmlinux 只是核心映像的初步結果。
構建的目標不是只有 vmlinux,也包含由頂層編譯檔引入的 linux/arch/x86/Makefile 所包含的最終目標 bzImage。

頂層編譯檔的目標描述。

linux/Makefile
00 all: vmlinux

產生 vmlinux 的依賴檔描述。

linux/Makefile
01 vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
02 	$(call vmlinux-modpost)
03 	$(call if_changed_rule,vmlinux__)
04 	$(Q)rm -f .old_version

行號說明
01生成 vmlinux 的依賴描述。
02執行 cmd_vmlinux-modpost,檢驗模組群是否合於規格。
03執行 rule_vmlinux__,生成 vmlinux 並檢驗 vmlinux 的內容。
04剔除區段 .old_version。

規則 rule_vmlinux__ 執行 $(call cmd,vmlinux__),描述 $(call cmd,vmlinux__) 會轉譯成命令 cmd_vmlinux__。
rule_vmlinux__ 描述如下。

linux/Makefile
01 define rule_vmlinux__
02 	:
03 	$(if $(CONFIG_KALLSYMS),,+$(call cmd,vmlinux_version))
04 	$(call cmd,vmlinux__)
05 	$(Q)echo 'cmd_$@ := $(cmd_vmlinux__)' > $(@D)/.$(@F).cmd
06 	$(Q)$(if $($(quiet)cmd_sysmap),                                      \
07 	  echo '  $($(quiet)cmd_sysmap)  System.map' &&)                     \
08 	$(cmd_sysmap) $@ System.map;                                         \
09 	if [ $$? -ne 0 ]; then                                               \
10 		rm -f $@;                                                    \
11 		/bin/false;                                                  \
12 	fi;
13 	$(verify_kallsyms)
14 endef

行號說明
03取得核心版本字串,。
04生成 vmlinux 映像,。
05~07輸出命令訊息,。
08從 vmlinux 產生 System.map,。
09~12失敗的話,移除 vmlinux,??。
13驗證 vmlinux。

命令 cmd_vmlinux__ 使用連結器,將 vmlinux-init 和 vmlinux-main 連結成 vmlinux。
cmd_vmlinux__ 描述如下。

linux/Makefile
01 cmd_vmlinux__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) -o $@         \
02 	-T $(vmlinux-lds) $(vmlinux-init)                          \
03 	--start-group $(vmlinux-main) --end-group                  \
04 	$(filter-out $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o FORCE ,$^)

行號說明
01~04依照連結檔 linux/arch/x86/kernel/vmlinux.lds,連結核心映像檔 vmlinux。這是一個 ELF 格式的可執行檔。

1.2.1 相依檔案

從 vmlinux 的生成描述,得知要生成 vmlinux 必須先取得 vmlinux-lds、vmlinux-init、vmlinux-main、vmlinux.o、kallsyms.o。

linux/Makefile
01 vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
02 	$(call vmlinux-modpost)
03 	$(call if_changed_rule,vmlinux__)
04 	$(Q)rm -f .old_version

vmlinux-lds 是 arch/x86/kernel/vmlinux.lds。
vmlinux-init 是 $(head-y) $(init-y)。
vmlinux-main 是 $(core-y) $(libs-y) $(drivers-y) $(net-y)。
$(kallsyms.o) 是 .tmp_kallsyms2.o。

頂層 Makefile 的 include $(srctree)/arch/$(SRCARCH)/Makefile,即 linux/arch/x86/Makefile 定義 head-y。
head-y 的設定如下,包含有 head_32.o、head32.o、head.o、init_task.o。

linux/arch/x86/Makefile
01 head-y := arch/x86/kernel/head_$(BITS).o
02 head-y += arch/x86/kernel/head$(BITS).o
03 head-y += arch/x86/kernel/head.o
04 head-y += arch/x86/kernel/init_task.o

init-y 的設定如下,表示會取用目錄 init 的 built-in.o。

linux/Makefile
00 init-y	:= init/
00 init-y	:= $(patsubst %/, %/built-in.o, $(init-y))

core-y 的設定如下,表示會取用目錄 usr 等八個目錄的 built-in.o。

linux/Makefile
00 core-y	:= usr/
00 core-y	+= kernel/ mm/ fs/ ipc/ security/ crypto/ block/
00 core-y	:= $(patsubst %/, %/built-in.o, $(core-y))

libs-y 的設定如下,表示會取用目錄 lib 的 built-in.o 和 lib.a。

linux/Makefile
00 libs-y	:= lib/
00 libs-y1	:= $(patsubst %/, %/lib.a, $(libs-y))
00 libs-y2	:= $(patsubst %/, %/built-in.o, $(libs-y))
00 libs-y	:= $(libs-y1) $(libs-y2)

drivers-y 的設定如下,表示會取用目錄 drivers、sound、firmware 的 built-in.o。

linux/Makefile
00 drivers-y:= drivers/ sound/ firmware/
00 drivers-y:= $(patsubst %/, %/built-in.o, $(drivers-y))

net-y 的設定如下,表示會取用目錄 net 的 built-in.o。

linux/Makefile
00 net-y	:= net/
00 net-y	:= $(patsubst %/, %/built-in.o, $(net-y))


1.2.2 vmlinux.o

vmlinux.o 匯集所有目的檔,包括 head-y、init-y、core-y、libs-y、drivers-y、net-y。
生成 vmlinux.o 的依賴檔是 modpost-init、vmlinux-main。 其中,modpost-init 是 $(filter-out init/built-in.o, $(vmlinux-init)),即濾除 init/built-in.o 後的 vmlinux-init。
生成 vmlinux.o 的規則是 rule_vmlinux-modpost,功能類似 rule_vmlinux__,只是模組的內容做了一些改變。 除了 vmlinux-init 和 vmlinux-main 之外,再加上 modpost-init 但不重複的目的檔,由 $(filter-out $(vmlinux-init) $(vmlinux-main) FORCE ,$^) 所表示。 筆者認為 $(filter-out $(vmlinux-init) $(vmlinux-main) FORCE ,$^) 的內容是空的,因為 modpost-init 是 vmlinux-init 的子集合。

linux/Makefile
01 modpost-init := $(filter-out init/built-in.o, $(vmlinux-init))
02 vmlinux.o: $(modpost-init) $(vmlinux-main) FORCE
03 	$(call if_changed_rule,vmlinux-modpost)

行號說明
01modpost-init 表示取用濾除了 init/built-in.o 的 vmlinux-init。
03執行規則 rule_vmlinux-modpost,生成 vmlinux.o。

$(call if_changed_rule,vmlinux-modpost) 會轉譯成 rule_vmlinux-modpost。
rule_vmlinux-modpost 的 $(call cmd,vmlinux-modpost) 會轉譯成 cmd_vmlinux-modpost,編譯 vmlinux.o。

linux/Makefile
00 define rule_vmlinux-modpost
00 	:
00 	+$(call cmd,vmlinux-modpost)
00 	$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost $@
00 	$(Q)echo 'cmd_$@ := $(cmd_vmlinux-modpost)' > $(dot-target).cmd
00 endef

命令 cmd_vmlinux-modpost 執行 vmlinux.o 的連結工作,將 modpost-init、vmlinux-main 連結到 vmlinux.o。

linux/Makefile
00 cmd_vmlinux-modpost = $(LD) $(LDFLAGS) -r -o $@             \
00  $(vmlinux-init) --start-group $(vmlinux-main) --end-group  \
00  $(filter-out $(vmlinux-init) $(vmlinux-main) FORCE ,$^)


1.2.3 kallsyms.o

kallsyms.o 是 vmlinux 的全部符號表,最後會加入到 vmlinux 映像檔,成為核心映像的符號表。

linux/Makefile
00 kallsyms.o := .tmp_kallsyms$(last_kallsyms).o

其中,last_kallsyms 是 2,所以 kallsyms.o 的內容會是 .tmp_kallsyms2.o。
.tmp_kallsyms2.o 的產生方式是先產生 .tmp_kallsyms1.o。
再將 .tmp_kallsyms1.o 套入 vmlinux 產生規則 cmd_vmlinux__,產生 .tmp_vmlinux2。
再用 .tmp_vmlinux2 產生方式 .tmp_kallsyms2.S,再產生.tmp_kallsyms2.o。
為什麼要經過這麼多種反覆的手續的原因是為了產生可以用來檢驗 vmlinux 的所有符號的符號表區段。


1.2.3.1 .tmp_vmlinux1

.tmp_vmlinux1 是 vmlinux 的暫時映像檔,這個映像檔和最正式的映像檔的差異是沒有全部符號表。 為什麼產生這個檔案的原因是想藉由這個映像檔,經過連結器,產生全部符號表 .tmp_kallsyms1.S。

生成 .tmp_vmlinux1:
如果相依檔有任何改變,執行 rule_ksym_ld。

linux/Makefile
00 .tmp_vmlinux1: $(vmlinux-lds) $(vmlinux-all) FORCE
00 	$(call if_changed_rule,ksym_ld)

規則 rule_ksym_ld 的定義:

linux/Makefile
00 define rule_ksym_ld
00 	: 
00 	+$(call cmd,vmlinux_version)
00 	$(call cmd,vmlinux__)
00 	$(Q)echo 'cmd_$@ := $(cmd_vmlinux__)' > $(@D)/.$(@F).cmd
00 endef

其中 $(call cmd,vmlinux_version) 是執行 cmd_vmlinux_version
$(call cmd,vmlinux__) 是執行 cmd_vmlinux__

cmd_vmlinux_version 的定義如下。

linux/Makefile
00 cmd_vmlinux_version = set -e;                           \
00 	if [ ! -r .version ]; then			\
00 	  rm -f .version;				\
00 	  echo 1 >.version;				\
00 	else						\
00 	  mv .version .old_version;			\
00 	  expr 0$$(cat .old_version) + 1 >.version;	\
00 	fi;						\
00 	$(MAKE) $(build)=init


1.2.3.2 .tmp_kallsyms1.S

生成 .tmp_kallsyms1.S:
從 .tmp_vmlinux1 產生符號檔 .tmp_kallsyms1.S,再從符號檔 .tmp_kallsyms1.S 產生目的檔 .tmp_kallsyms1.o。
目的檔 .tmp_kallsyms1.o 提供一個正確的符號表資訊,用以建立 .tmp_vmlinux2。
所以 .tmp_vmlinux2 的體積會比 .tmp_vmlinux1 大,因為多了一個區段 __kallsyms。

linux/Makefile
00 .tmp_kallsyms%.S: .tmp_vmlinux% $(KALLSYMS)
00 	$(call cmd,kallsyms)

生成 .tmp_kallsyms1.o:

linux/Makefile
00 .tmp_kallsyms1.o .tmp_kallsyms2.o .tmp_kallsyms3.o: %.o: %.S scripts FORCE
00 	$(call if_changed_dep,as_o_S)

命令描述 cmd_as_o_S:

linux/Makefile
00 cmd_as_o_S       = $(CC) $(a_flags) -c -o $@ $<


1.2.3.3 .tmp_vmlinux2

.tmp_vmlinux2 是 vmlinux 的暫時映像檔,和之前的 .tmp_vmlinux1 的差異是 .tmp_vmlinux2 有全部符號表。 這個全部符號表是從 .tmp_vmlinux1 產生的 .tmp_kallsyms1.S。

生成 .tmp_vmlinux2:
描述 $(call if_changed,vmlinux__) 表示依賴檔有變更時,執行 cmd_vmlinux__。

linux/Makefile
00 .tmp_vmlinux2: $(vmlinux-lds) $(vmlinux-all) .tmp_kallsyms1.o FORCE
00 	$(call if_changed,vmlinux__)

從 .tmp_vmlinux2 生成 .tmp_kallsyms2.S:

linux/Makefile
00 .tmp_kallsyms%.S: .tmp_vmlinux% $(KALLSYMS)
00 	$(call cmd,kallsyms)

從 .tmp_kallsyms2.S 生成 .tmp_kallsyms2.o:

linux/Makefile
00 .tmp_kallsyms1.o .tmp_kallsyms2.o .tmp_kallsyms3.o: %.o: %.S scripts FORCE
00 	$(call if_changed_dep,as_o_S)

.tmp_vmlinux1 的體積是 12739 KB,而 .tmp_vmlinux2 的體積是 13418 KB。
從 .tmp_vmlinux1 產生符號檔 .tmp_kallsyms1.S。
從 .tmp_vmlinux2 產生符號檔 .tmp_kallsyms2.S。
這是差在 .tmp_vmlinux1 沒有 KALLSYM 區段,而 .tmp_vmlinux2 因為 .tmp_kallsyms1.o 的關係有 KALLSYM 區段。

從 .tmp_vmlinux2 產生 .tmp_System.map 用來驗證 vmlinux 的 System.Map。

生成 vmlinux 的規則,呼叫 verify_kallsyms 用以驗證所生成的 vmlinux 是正確的。

linux/Makefile
00 define rule_vmlinux__
00 	:
00 	$(if $(CONFIG_KALLSYMS),,+$(call cmd,vmlinux_version))
00 	$(call cmd,vmlinux__)
00 	$(Q)echo 'cmd_$@ := $(cmd_vmlinux__)' > $(@D)/.$(@F).cmd
00 	$(Q)$(if $($(quiet)cmd_sysmap),                                      \
00 	  echo '  $($(quiet)cmd_sysmap)  System.map' &&)                     \
00 	$(cmd_sysmap) $@ System.map;                                         \
00 	if [ $$? -ne 0 ]; then                                               \
00 		rm -f $@;                                                    \
00 		/bin/false;                                                  \
00 	fi;
00 	$(verify_kallsyms)
00 endef

驗證規則 verify_kallsyms,用於驗證 vmlinux 和 .tmp_vmlinux2 的 system.Map 是相同的,藉以證明所生成的 vmlinux 是正確的。 這是生成 vmlinux 的最後一個步驟。

linux/Makefile
00 define verify_kallsyms
00 	$(Q)$(if $($(quiet)cmd_sysmap),                                      \
00 	  echo '  $($(quiet)cmd_sysmap)  .tmp_System.map' &&)                \
00 	  $(cmd_sysmap) .tmp_vmlinux$(last_kallsyms) .tmp_System.map
00 	$(Q)cmp -s System.map .tmp_System.map ||                             \
00 		(echo Inconsistent kallsyms data;                            \
00 		 echo Try setting CONFIG_KALLSYMS_EXTRA_PASS;                \
00 		 rm .tmp_kallsyms* ; /bin/false )
00 endef


1.2.4 編譯檔的規則群

前面的敘述強調檔案生成的關係,沒有詳細說明規則中每一行敘述的意義。 這裡說明 linux/Makefile 的所有規則。

規則一 rule_vmlinux__:
產生 vmlinuix 的規則 rule_vmlinux__ 如下。
冒號 : 是為了使加號 + 能用在描述中。 第一步是執行 cmd_vmlinux_version,產生版本檔案 .version。
第二步是執行 cmd_vmlinux__,產生 vmlinux 映像檔。
第三步是執行 cmd_sysmap,產生 System.Map 系統對映檔。
第四步是執行 verify_kallsyms,驗證 System.Map。

linux/Makefile
00 define rule_vmlinux__
00 	:
00 	$(if $(CONFIG_KALLSYMS),,+$(call cmd,vmlinux_version))
00 	$(call cmd,vmlinux__)
00 	$(Q)echo 'cmd_$@ := $(cmd_vmlinux__)' > $(@D)/.$(@F).cmd
00 	$(Q)$(if $($(quiet)cmd_sysmap),                                      \
00 	  echo '  $($(quiet)cmd_sysmap)  System.map' &&)                     \
00 	$(cmd_sysmap) $@ System.map;                                         \
00 	if [ $$? -ne 0 ]; then                                               \
00 		rm -f $@;                                                    \
00 		/bin/false;                                                  \
00 	fi;
00 	$(verify_kallsyms)
00 endef

規則二 rule_ksym_ld:
第一步執行 cmd_vmlinux_version,取得新的 .version,即版本號碼。
第二步執行 cmd_vmlinux__,產生 vmlinux 映像檔。
第三步執行 cmd_$@ := $(cmd_vmlinux__),並將輸出寫到檔案 $(@D)/.$(@F).cmd。
例如,編譯完成後,在 LINUX 根目錄會產生 ..tmp_vmlinux1.cmd。 檔案內容為,(換行斜線是筆者加上去的)

linux/Makefile
00 cmd_.tmp_vmlinux1 := ld -m elf_i386 --emit-relocs --build-id -o .tmp_vmlinux1 \
00 -T arch/x86/kernel/vmlinux.lds arch/x86/kernel/head_32.o arch/x86/kernel/head32.o \
00 arch/x86/kernel/head.o arch/x86/kernel/init_task.o  init/built-in.o --start-group  \
00 usr/built-in.o  arch/x86/built-in.o  kernel/built-in.o  mm/built-in.o  fs/built-in.o  \
00 ipc/built-in.o  security/built-in.o  crypto/built-in.o  block/built-in.o  lib/lib.a  \
00 arch/x86/lib/lib.a  lib/built-in.o  arch/x86/lib/built-in.o  drivers/built-in.o  \
00 sound/built-in.o  firmware/built-in.o  arch/x86/pci/built-in.o  arch/x86/power/built-in.o  \
00 arch/x86/video/built-in.o  net/built-in.o --end-group 

$(@D) 是指檔案路徑,$(@F)是檔案名稱 .tmp_vmlinux1。

linux/Makefile
00 define rule_ksym_ld
00 	: 
00 	+$(call cmd,vmlinux_version)
00 	$(call cmd,vmlinux__)
00 	$(Q)echo 'cmd_$@ := $(cmd_vmlinux__)' > $(@D)/.$(@F).cmd
00 endef

規則三 rule_vmlinux-modpost:
功能是 VMLINUX 模組後處理程序。
第一步執行 cmd_vmlinux-modpost,對一個已經連結好的 vmlinux 模組檔做修改,。
第二步執行 make,將 vmlinux 模組檔轉成核心模組檔。
第三步執行 cmd_$@ := $(cmd_vmlinux-modpost),並將輸出寫到檔案 $(dot-target).cmd。

linux/Makefile
00 define rule_vmlinux-modpost
00 : 00 +$(call cmd,vmlinux-modpost) 00 $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost $@ 00 $(Q)echo 'cmd_$@ := $(cmd_vmlinux-modpost)' > $(dot-target).cmd 00 endef

規則四 verify_kallsyms:
功能是驗證模組的 KALLSYM 區段內容。
第一步執行 sysmap,從 .tmp_vmlinux2 產生 .tmp_System.map。
第二步執行 cmp,比較 System.map 和 .tmp_System.map。藉此驗證 vmlinux 的符號對映是正確的。

linux/Makefile
00 define verify_kallsyms
00 	$(Q)$(if $($(quiet)cmd_sysmap),                                      \
00 	  echo '  $($(quiet)cmd_sysmap)  .tmp_System.map' &&)                \
00 	  $(cmd_sysmap) .tmp_vmlinux$(last_kallsyms) .tmp_System.map
00 	$(Q)cmp -s System.map .tmp_System.map ||                             \
00 		(echo Inconsistent kallsyms data;                            \
00 		 echo Try setting CONFIG_KALLSYMS_EXTRA_PASS;                \
00 		 rm .tmp_kallsyms* ; /bin/false )
00 endef


1.2.5 命令群

命令一 cmd_vmlinux__:

linux/Makefile
00 cmd_vmlinux__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) -o $@ \
00  -T $(vmlinux-lds) $(vmlinux-init)                         \
00  --start-group $(vmlinux-main) --end-group                 \
00  $(filter-out $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o FORCE ,$^)

命令二 cmd_vmlinux_version:

linux/Makefile
00 cmd_vmlinux_version = set -e;                           \
00 	if [ ! -r .version ]; then		        \
00 	  rm -f .version;				\
00 	  echo 1 >.version;				\
00 	else						\
00 	  mv .version .old_version;			\
00 	  expr 0$$(cat .old_version) + 1 >.version;	\
00 	fi;						\
00 	$(MAKE) $(build)=init

命令三 cmd_sysmap:
系統對映圖檔。

linux/Makefile
00 cmd_sysmap = $(CONFIG_SHELL) $(srctree)/scripts/mksysmap

命令四 cmd_ksym_ld:
功能等同於 cmd_vmlinux__。

linux/Makefile
00 cmd_ksym_ld = $(cmd_vmlinux__)

命令五 cmd_kallsyms:
從模組檔產生所有符號的對映圖檔。

linux/Makefile
00 cmd_kallsyms = $(NM) -n $< | $(KALLSYMS) $(if $(CONFIG_KALLSYMS_ALL),--all-symbols) > $@

命令六 cmd_vmlinux-modpost:
功能是再處理連結後的 vmlinux。

linux/Makefile
00 cmd_vmlinux-modpost = $(LD) $(LDFLAGS) -r -o $@             \
00  $(vmlinux-init) --start-group $(vmlinux-main) --end-group  \
00  $(filter-out $(vmlinux-init) $(vmlinux-main) FORCE ,$^)

$(LD) 表示執行連結器 ld.exe
$(LDFLAGS)
-r 表示不使用 built-in 隱含規則。
-o 表示輸出檔為 $@,即 vmlinux.o。
--start-group $(vmlinux-main) --end-group 表示反覆解析所有符號,確定所有符號都在檔案群中,避免使用到未定義的符號。
$(filter-out $(vmlinux-init) $(vmlinux-main) FORCE ,$^) 表示從相依檔 $(modpost-init) $(vmlinux-main) 中濾除 $(vmlinux-init) $(vmlinux-main),即僅加入不重疊的部分的目的檔群。

命令七 cmd_tags:
功能是對構建的環境變數產生標籤檔。

linux/Makefile
00 cmd_tags = $(CONFIG_SHELL) $(srctree)/scripts/tags.sh $@

命令八 cmd_rmdirs:
功能是移除目錄。

linux/Makefile
00 cmd_rmdirs = rm -rf $(rm-dirs)

命令九 cmd_depmod:
功能是取得依賴模組的資訊。

linux/Makefile
00 cmd_depmod = \
00 if [ -r System.map -a -x $(DEPMOD) ]; then                              \
00 	$(DEPMOD) -ae -F System.map                                     \
00 	$(if $(strip $(INSTALL_MOD_PATH)), -b $(INSTALL_MOD_PATH) )     \
00 	$(KERNELRELEASE);                                               \
00 fi

命令十 cmd_crmodverdir:
功能是建立放置模組版本的目錄。

linux/Makefile
00 cmd_crmodverdir = $(Q)mkdir -p $(MODVERDIR) $(if $(KBUILD_MODULES),; rm -f $(MODVERDIR)/*)

命令十一 cmd_as_o_S:
將輸入黨編譯成目的檔,這是用在編譯過程產生的組語檔案。

linux/Makefile
00 cmd_as_o_S = $(CC) $(a_flags) -c -o $@ $<

命令十二 cmd_files:

linux/Makefile
00 cmd_files := $(wildcard .*.cmd $(foreach f,$(targets),$(dir $(f)).$(notdir $(f)).cmd))


1.2.6 核心映像連結檔

從 vmlinux 的生成描述,得知要生成 vmlinux 必須先取得 vmlinux-lds。 vmlinux-lds 的介入時機是執行規則 rule_vmlinux__ 的 cmd_vmlinux__ 的時候。

linux/Makefile
01 vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
02 	$(call vmlinux-modpost)
03 	$(call if_changed_rule,vmlinux__)
04 	$(Q)rm -f .old_version

cmd_vmlinux__ 使用 vmlinux-lds 連結 $(vmlinux-init)、$(vmlinux-main)、以及 vmlinux.o 中不包含 vmlinux-init、vmlinux-main、vmlinux-lds 的區段資料,生成 vmlinux。 此時所生成的 vmlinux 就是一個 ELF 格式的可執行檔,執行檔的入口點由 vmlinux-lds 定義。

linux/Makefile
01 cmd_vmlinux__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) -o $@ \
02 	-T $(vmlinux-lds) $(vmlinux-init)                          \
03 	--start-group $(vmlinux-main) --end-group                  \
04 	$(filter-out $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o FORCE ,$^)

核心映像連結檔 vmlinux-lds 是 vmlinux 的連結描述檔,檔案位置是 linux/arch/x86/kernel/vmlinux.lds。

linux/arch/x86/kernel/vmlinux.lds
01 OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
02 OUTPUT_ARCH(i386)
03 ENTRY(phys_startup_32)
04 jiffies = jiffies_64;

行號說明
01設定輸出檔的格式,第一參數是內定格式,第二參數是 big endian 格式,第三參數是 little endian 格式,而他們都是 elf32-i386。
02設定機器架構為 I386。
03設定輸出檔的入口點為 phys_startup_32,入口點是第一個被執行的指令所在的位址。沒有指定入口點時,會使用標籤 start 或位址 0 代替。

linux/arch/x86/kernel/vmlinux.lds
01 PHDRS {
02  text PT_LOAD FLAGS(5);   
03  data PT_LOAD FLAGS(6);   
04  note PT_NOTE FLAGS(0);   
05 }

行號說明
02設定程式表 PROGRAM TABLE,共有三個程式表 text、data、note。
02程式 text,FLAGS(5) 表示此程式檔頭的 p_type 為 PT_SHLIB。定義在 elf_format.pdf,表示不確定的內容??。
03程式 data,FLAGS(6) 表示此 program header 的 p_type 為 PT_PHDR。定義在 elf_format.pdf,表示是 PHDRS 自己的體積??。
04程式 note,FLAGS(6) 表示此 program header 的 p_type 為 PT_NULL。定義在 elf_format.pdf,表示可以忽略而不載入。

linux/arch/x86/kernel/vmlinux.lds
01 SECTIONS /* elf_format.pdf ,P36*/
02 {
03  . = 0xC0000000 + ((0x1000000 + (0x1000000 - 1)) & ~(0x1000000 - 1));
04   phys_startup_32 = startup_32 - 0xC0000000; 2013/7/17 04:26下午
05  .text : AT(ADDR(.text) - 0xC0000000) {
06   *(.head.text)
07   . = ALIGN((1 << 12));
08   *(.text..page_aligned)
09   . = ALIGN(8);
10   _stext = .;
11   . = ALIGN(8); *(.text.hot) *(.text) *(.ref.text) *(.devinit.text) *(.devexit.text) *(.cpuinit.text) *(.cpuexit.text) *(.text.unlikely)
12   . = ALIGN(8); __sched_text_start = .; *(.sched.text) __sched_text_end = .;
13   . = ALIGN(8); __lock_text_start = .; *(.spinlock.text) __lock_text_end = .;
14   . = ALIGN(8); __kprobes_text_start = .; *(.kprobes.text) __kprobes_text_end = .;
15   . = ALIGN(8); __entry_text_start = .; *(.entry.text) __entry_text_end = .;
16   *(.fixup)
17   *(.gnu.warning)
18   /* End of text section */
19   _etext = .;
20  } :text = 0x9090

行號說明
01宣告區段,SECTIONS 定義在 ELF_FORMAT.PDF P36。
03這個算式的結果是 0xC1000000。0xC0000000 表示這個 ELF 可執行檔是執行於虛擬記憶體 3GB~4GB 之間,也就是說核心可用的記憶體空間小於 1GB。
04startup_32 是從 0xC0000000 開始算起,而我們要的是從 0 算起的位址偏移值 phys_startup_32。 phys_startup_32 會編入 setup.bin 的 hdr code32_start 欄位 ? 不會填入,會直接設定為 0x100000。至於為什麼有 0x100000 空出來 的原因是為了 kernel crash 時的 kernel recovery 程式使用。
05宣告程式區域 text,其位址開始於 ADDR(.text) - 0xC0000000,.text 原本是一個從 0xC0000000 開始算起的程式位址。ADDR(.text)表示取 .text 的虛擬記憶體位址(VMA)。
06PROGRAM .head.text 宣告在 linux/include/linux/init.h,所有以 __HEAD 宣告的組合語言函式都會放在這個程式段落。
07對齊 4KB 的邊界,這可能是有助於頁配置或頁釋放。
08這部分筆者尚不解,應該是放 .text..page_aligned 的資料。
09對齊八位元組的邊界,這可能是有助於頁配置或頁釋放。
10宣告連結變數 _stext,表示這是此段區域的起始點。
11先對齊八位元組,再將程式群放到此區域,包括 PROGRAM .text.hot、.text、.ref.text、.devinit.text、.devexit.text、.cpuinit.text、.cpuexit.text、.text.unlikely。
12先對齊八位元組,再將程式群放到此區域,包括 PROGRAM .sched.text。
13先對齊八位元組,再將程式群放到此區域,包括 PROGRAM .spinlock.text。
14先對齊八位元組,再將程式群放到此區域,包括 PROGRAM .kprobes.text。
15先對齊八位元組,再將程式群放到此區域,包括 PROGRAM .entry.text。
16將程式 .fixup 的內容放在此。
17將程式 .gnu.warning 的內容放在此。
18宣告連結變數 _etext,表示這是此程式區域的結束點。
19程式區域 text 結束,把區域內的空白的空間填入0x9090。

linux/arch/x86/kernel/vmlinux.lds
01  .notes : AT(ADDR(.notes) - 0xC0000000) { __start_notes = .; *(.note.*) __stop_notes = .; } :text :note 
02  . = ALIGN(16); 
03  __ex_table : AT(ADDR(__ex_table) - 0xC0000000) { __start___ex_table = .; *(__ex_table) __stop___ex_table = .; } :text = 0x9090
04  . = ALIGN((1 << 12));

行號說明
01宣告程式區域 .notes,其位址開始於 ADDR(.notes) - 0xC0000000,.notes 原本是一個從 0xC0000000 開始算起的程式位址。ADDR(.notes)表示取 .notes 的虛擬記憶體位址(VMA)。
02對齊十六位元組的記憶體邊界。
03宣告程式區域 __ex_table,其位址開始於 ADDR(__ex_table) - 0xC0000000,__ex_table 原本是一個從 0xC0000000 開始算起的程式位址。ADDR(__ex_table)表示取 .notes 的虛擬記憶體位址(VMA)。
04對齊 4KB 的邊界,這可能是有助於頁配置或頁釋放。

linux/arch/x86/kernel/vmlinux.lds
01  . = ALIGN(((1 << 12))); 
02  .rodata : AT(ADDR(.rodata) - 0xC0000000) { 
03  __start_rodata = .; *(.rodata) *(.rodata.*) *(__vermagic)  
04  . = ALIGN(8);
05  __start___tracepoints_ptrs = .; *(__tracepoints_ptrs) __stop___tracepoints_ptrs = .; 
06  *(__markers_strings) *(__tracepoints_strings) } 

行號說明
01對齊 4KB 的邊界,這可能是有助於頁配置或頁釋放。
02宣告程式區域 .rodata,其位址開始於 ADDR(.rodata) - 0xC0000000,.rodata 原本是一個從 0xC0000000 開始算起的程式位址。ADDR(.rodata)表示取 .notes 的虛擬記憶體位址(VMA)。
03放入三個 PROGRAM 的內容,包括.rodata、.rodata.*、__vermagic。
04對齊八位元組的邊界。
05放入 PROGRAM __tracepoints_ptrs 的內容。
06接著再放入 PROGRAM __markers_strings、__tracepoints_strings 的內容。

linux/arch/x86/kernel/vmlinux.lds
00 .rodata1 : AT(ADDR(.rodata1) - 0xC0000000) { *(.rodata1) } 
00 . = ALIGN(8); 
00  __bug_table : AT(ADDR(__bug_table) - 0xC0000000) { 
00  __start___bug_table = .; *(__bug_table) __stop___bug_table = .; } 

行號說明
02宣告程式區域 .rodata1,並放入 PROGRAM .rodata1 的內容。
04對齊八位元組的邊界。
02宣告程式區域 __bug_table,並放入 PROGRAM __bug_table 的內容。

linux/arch/x86/kernel/vmlinux.lds
01 .pci_fixup : AT(ADDR(.pci_fixup) - 0xC0000000) { 
02 __start_pci_fixups_early = .; *(.pci_fixup_early) __end_pci_fixups_early = .; 
03 __start_pci_fixups_header = .; *(.pci_fixup_header) __end_pci_fixups_header = .; 
04 __start_pci_fixups_final = .; *(.pci_fixup_final) __end_pci_fixups_final = .; 
05 __start_pci_fixups_enable = .; *(.pci_fixup_enable) __end_pci_fixups_enable = .; 
06 __start_pci_fixups_resume = .; *(.pci_fixup_resume) __end_pci_fixups_resume = .; 
07 __start_pci_fixups_resume_early = .; *(.pci_fixup_resume_early) __end_pci_fixups_resume_early = .; 
08 __start_pci_fixups_suspend = .; *(.pci_fixup_suspend) __end_pci_fixups_suspend = .; } 

行號說明
01宣告程式區域 .pci_fixup,此區域將包含下列的程式內容。
02放入PROGRAM .pci_fixup_early。
03放入PROGRAM .pci_fixup_header。
04放入PROGRAM .pci_fixup_final。
05放入PROGRAM .pci_fixup_enable。
06放入PROGRAM .pci_fixup_resume。
07放入PROGRAM .pci_fixup_resume_early。
08放入PROGRAM .pci_fixup_suspend。

linux/arch/x86/kernel/vmlinux.lds
01 .builtin_fw : AT(ADDR(.builtin_fw) - 0xC0000000) { 
02 __start_builtin_fw = .; *(.builtin_fw) __end_builtin_fw = .; } 
03 .rio_ops : AT(ADDR(.rio_ops) - 0xC0000000) 
04 { __start_rio_switch_ops = .; *(.rio_switch_ops) __end_rio_switch_ops = .; } 

行號說明
01宣告程式區域 .builtin_fw,包含 PROGRAM .builtin_fw。
01宣告程式區域 .rio_ops,包含 PROGRAM .rio_switch_ops。

linux/arch/x86/kernel/vmlinux.lds
01 . = ALIGN(4); 
02 .tracedata : AT(ADDR(.tracedata) - 0xC0000000) 
03             { __tracedata_start = .; *(.tracedata) __tracedata_end = .; } 
04 __ksymtab : AT(ADDR(__ksymtab) - 0xC0000000) 
05             { __start___ksymtab = .; *(SORT(___ksymtab+*)) __stop___ksymtab = .; } 
06 __ksymtab_gpl : AT(ADDR(__ksymtab_gpl) - 0xC0000000) 
07             { __start___ksymtab_gpl = .; *(SORT(___ksymtab_gpl+*)) __stop___ksymtab_gpl = .; } 
08 __ksymtab_unused : AT(ADDR(__ksymtab_unused) - 0xC0000000) 
09             { __start___ksymtab_unused = .; *(SORT(___ksymtab_unused+*)) __stop___ksymtab_unused = .; } 
10 __ksymtab_unused_gpl : AT(ADDR(__ksymtab_unused_gpl) - 0xC0000000) 
11             { __start___ksymtab_unused_gpl = .; *(SORT(___ksymtab_unused_gpl+*)) __stop___ksymtab_unused_gpl = .; } 
12 __ksymtab_gpl_future : AT(ADDR(__ksymtab_gpl_future) - 0xC0000000) 
13             { __start___ksymtab_gpl_future = .; *(SORT(___ksymtab_gpl_future+*)) __stop___ksymtab_gpl_future = .; } 
14 __kcrctab : AT(ADDR(__kcrctab) - 0xC0000000) 
15             { __start___kcrctab = .; *(SORT(___kcrctab+*)) __stop___kcrctab = .; } 
16 __kcrctab_gpl : AT(ADDR(__kcrctab_gpl) - 0xC0000000) 
17             { __start___kcrctab_gpl = .; *(SORT(___kcrctab_gpl+*)) __stop___kcrctab_gpl = .; } 
18 __kcrctab_unused : AT(ADDR(__kcrctab_unused) - 0xC0000000) 
19             { __start___kcrctab_unused = .; *(SORT(___kcrctab_unused+*)) __stop___kcrctab_unused = .; } 
20 __kcrctab_unused_gpl : AT(ADDR(__kcrctab_unused_gpl) - 0xC0000000) 
21             { __start___kcrctab_unused_gpl = .; *(SORT(___kcrctab_unused_gpl+*)) __stop___kcrctab_unused_gpl = .; } 
22 __kcrctab_gpl_future : AT(ADDR(__kcrctab_gpl_future) - 0xC0000000) 
23             { __start___kcrctab_gpl_future = .; *(SORT(___kcrctab_gpl_future+*)) __stop___kcrctab_gpl_future = .; } 
24 __ksymtab_strings : AT(ADDR(__ksymtab_strings) - 0xC0000000) { *(__ksymtab_strings) } 
25 __init_rodata : AT(ADDR(__init_rodata) - 0xC0000000) 
26             { *(.ref.rodata) *(.devinit.rodata) *(.devexit.rodata) *(.cpuinit.rodata) *(.cpuexit.rodata) } 
27 __param : AT(ADDR(__param) - 0xC0000000) { __start___param = .; *(__param) __stop___param = .; } 
28 __modver : AT(ADDR(__modver) - 0xC0000000) { __start___modver = .; *(__modver) __stop___modver = .; . = ALIGN(((1 << 12))); __end_rodata = .; } 
29 . = ALIGN(((1 << 12)));
30 

行號說明
01對齊四位元組的邊界。
02~03宣告程式區域 .tracedata,包含 PROGRAM .tracedata 。
04~05宣告程式區域 __ksymtab,包含 PROGRAM SORT(___ksymtab+*)。
06~07宣告程式區域 __ksymtab_gpl,包含 PROGRAM SORT(___ksymtab_gpl+*)。
08~09宣告程式區域 __ksymtab_unused,包含 PROGRAM SORT(___ksymtab_unused+*)。
10~11宣告程式區域 __ksymtab_unused_gpl,包含 PROGRAM SORT(___ksymtab_unused_gpl+*)。
12~13宣告程式區域 __ksymtab_gpl_future,包含 PROGRAM SORT(___ksymtab_gpl_future+*) 。
14~15宣告程式區域 __kcrctab,包含 PROGRAM SORT(___kcrctab+*)。
16~17宣告程式區域 __kcrctab_gpl,包含 PROGRAM SORT(___kcrctab_gpl+*) 。
18~19宣告程式區域 __kcrctab_unused,包含 PROGRAM SORT(___kcrctab_unused+*) 。
20~21宣告程式區域 __kcrctab_unused_gpl,包含 PROGRAM SORT(___kcrctab_unused_gpl+*) 。
22~23宣告程式區域 __kcrctab_gpl_future,包含 PROGRAM .tracedata 。
24~25宣告程式區域 __ksymtab_strings,包含 PROGRAM SORT(___kcrctab_gpl_future+*) 。
25~26宣告程式區域 __init_rodata,包含 PROGRAM .ref.rodata、.devinit.rodata、.devexit.rodata、.cpuinit.rodata、.cpuexit.rodata。
27宣告程式區域 __param,包含 PROGRAM __param 。
28宣告程式區域 __modver,包含 PROGRAM __modver 。
29對齊 4KB 的記憶體邊界。

linux/arch/x86/kernel/vmlinux.lds
01  .data : AT(ADDR(.data) - 0xC0000000) {
02  _sdata = .;
03  . = ALIGN(((1 << 12) << 1)); *(.data..init_task)
04  . = ALIGN((1 << 12)); __nosave_begin = .; *(.data..nosave) . = ALIGN((1 << 12)); __nosave_end = .;
05  . = ALIGN((1 << 12)); *(.data..page_aligned)
06  . = ALIGN((1 << (6))); *(.data..cacheline_aligned) *(.data) *(.ref.data) *(.data..shared_aligned) *(.devinit.data) 
07  *(.devexit.data) *(.cpuinit.data) *(.cpuexit.data) 
08  . = ALIGN(32); *(__tracepoints) 
09  . = ALIGN(8); __start___jump_table = .; *(__jump_table) __stop___jump_table = .; 
10  . = ALIGN(8); __start___verbose = .; *(__verbose) __stop___verbose = .; __start___trace_bprintk_fmt = .; 
11  *(__trace_printk_fmt) __stop___trace_bprintk_fmt = .;
12   CONSTRUCTORS
13   . = ALIGN((1 << 6)); *(.data..read_mostly) . = ALIGN((1 << 6));
14   _edata = .;
15  } :data

行號說明
01宣告程式區域 .data,包含 PROGRAM .tracedata 。
02宣告連結變數 _sdata,表示這是 .data 開始點,沒有用處。
03對齊 8KB 的記憶體邊界,放入 PROGRAM .data..init_task。
04對齊 4KB 的記憶體邊界,放入 PROGRAM .data..nosave。之後再對齊 4KB 的記憶體邊界。
05對齊 4KB 的記憶體邊界,放入 PROGRAM .data..page_aligned。
06~7對齊 64 位元組的記憶體邊界,放入 PROGRAM .data..cacheline_aligned、.data、.ref.data、.data..shared_aligned、.devinit.data、.devexit.data、.cpuinit.data、.cpuexit.data。
08對齊 32 的記憶體邊界,放入 PROGRAM __tracepoints。
09對齊八的記憶體邊界,並放入 PROGRAM __jump_table。
10~11對齊八的記憶體邊界,並放入 PROGRAM __verbose、__trace_printk_fmt。
12建構子群命令,用來放置 C++ 的建構子和除構子資訊。
13對齊 64 位元組的記憶體邊界,放入 PROGRAM .data..read_mostly。最後再對齊 64 位元組的記憶體邊界。
14宣告連結變數 _edata,表示這是 .data 結束點,沒有用處。
15程式區域 .data 的結束點。開始於 .data ,結束於 :data。

linux/arch/x86/kernel/vmlinux.lds
01  . = ALIGN((1 << 12));
02  .init.begin : AT(ADDR(.init.begin) - 0xC0000000) {__init_begin = .; }

行號說明
01對齊 4KB 的記憶體邊界。
02宣告程式區域 .init.begin 和連結變數 __init_begin。之後會有 __init_end 作為對映的結束點。

linux/arch/x86/kernel/vmlinux.lds
01  . = ALIGN((1 << 12)); 
02  .init.text : AT(ADDR(.init.text) - 0xC0000000) { _sinittext = .; *(.init.text) *(.meminit.text) _einittext = .; }

行號說明
01對齊 4KB 的記憶體邊界。
02宣告程式區域 .init.text,包含 PROGRAM .init.text、.meminit.text。

linux/arch/x86/kernel/vmlinux.lds
01  .init.data : AT(ADDR(.init.data) - 0xC0000000) { 
02  *(.init.data) *(.meminit.data) *(.init.rodata) 
03  . = ALIGN(8); 
04  __start_ftrace_events = .; *(_ftrace_events) __stop_ftrace_events = .; 
05  *(.meminit.rodata) 
06  . = ALIGN(32); __dtb_start = .; *(.dtb.init.rodata) __dtb_end = .; 
07  . = ALIGN(16); __setup_start = .; *(.init.setup) __setup_end = .; 
08  __initcall_start = .; *(.initcallearly.init) __early_initcall_end = .; 
09  *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) 
10  *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) 
11  *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) 
12  *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) 
13  *(.initcall7s.init) __initcall_end = .;
14  __con_initcall_start = .;  *(.con_initcall.init) __con_initcall_end = .;
15  __security_initcall_start = .;  *(.security_initcall.init) __security_initcall_end = .; 
16  . = ALIGN(4); __initramfs_start = .; *(.init.ramfs) 
17  . = ALIGN(8); *(.init.ramfs.info)
18 }

行號說明
01宣告程式區域 .init.data。
02放入 PROGRAM .init.data、.meminit.data、.init.rodata。
03對齊八位元組的記憶體邊界。
04放入 PROGRAM _ftrace_events。
05放入 PROGRAM .meminit.rodata。
06對齊32位元組的記憶體邊界,放入 PROGRAM .dtb.init.rodata。
07對齊16位元組的記憶體邊界,放入 PROGRAM .init.setup。
08放入 PROGRAM .initcallearly.init。
09~13放入 PROGRAM .initcall0.init、.initcall0s.init、.initcall0.init、.initcall0s.init、.initcall1.init、.initcall1s.init)、 .initcall2.init、.initcall2s.init、.initcall3.init、.initcall3s.init)、.initcall4.init、.initcall4s.init、.initcall5.init、.initcall5s.init)、 .initcallrootfs.init、.initcall6.init、.initcall6s.init、.initcall7.init、.initcall7s.init。
14放入 PROGRAM .con_initcall.init。
15放入 PROGRAM .security_initcall.init。
16對齊四位元組的記憶體邊界,放入 PROGRAM .init.ramfs。
17對齊八位元組的記憶體邊界,放入 PROGRAM .init.ramfs.info。

linux/arch/x86/kernel/vmlinux.lds
01  .x86_trampoline : AT(ADDR(.x86_trampoline) - 0xC0000000) {
02   x86_trampoline_start = .; *(.x86_trampoline) x86_trampoline_end = .;
03  }
04  .x86_cpu_dev.init : AT(ADDR(.x86_cpu_dev.init) - 0xC0000000) {
05   __x86_cpu_dev_start = .; *(.x86_cpu_dev.init)  __x86_cpu_dev_end = .;
06  }
07  . = ALIGN(8);
08  .parainstructions : AT(ADDR(.parainstructions) - 0xC0000000) {
09   __parainstructions = .; *(.parainstructions) __parainstructions_end = .;
10  }
11  . = ALIGN(8);
12  .altinstructions : AT(ADDR(.altinstructions) - 0xC0000000) {
13   __alt_instructions = .; *(.altinstructions)  __alt_instructions_end = .;
14  }
15  .altinstr_replacement : AT(ADDR(.altinstr_replacement) - 0xC0000000) {
16   *(.altinstr_replacement)
17  }
18  .iommu_table : AT(ADDR(.iommu_table) - 0xC0000000) {
19   __iommu_table = .; *(.iommu_table) __iommu_table_end = .;
20  }

行號說明
01~03宣告程式區域 .x86_trampoline,放入 PROGRAM .x86_trampoline。
04~06宣告程式區域 .x86_cpu_dev.init,放入 PROGRAM .x86_cpu_dev.init。
07~10對齊八位元組的記憶體邊界。宣告程式區域 .parainstructions,放入 PROGRAM .parainstructions。
11~14對齊八位元組的記憶體邊界。宣告程式區域 .altinstructions,放入 PROGRAM .altinstructions。
15~17宣告程式區域 .altinstr_replacement,放入 PROGRAM .altinstr_replacement。
18~20宣告程式區域 .iommu_table,放入 PROGRAM .iommu_table。

linux/arch/x86/kernel/vmlinux.lds
01  . = ALIGN(8);
02  .apicdrivers : AT(ADDR(.apicdrivers) - 0xC0000000) {
03   __apicdrivers = .; *(.apicdrivers); __apicdrivers_end = .;
04  }
05  . = ALIGN(8);
06  .exit.text : AT(ADDR(.exit.text) - 0xC0000000) {
07   *(.exit.text) *(.memexit.text)
08  }
09  .exit.data : AT(ADDR(.exit.data) - 0xC0000000) {
10   *(.exit.data) *(.memexit.data) *(.memexit.rodata)
11  }
12  . = ALIGN((1 << 12)); 

行號說明
01~04對齊八位元組的記憶體邊界。宣告程式區域 .apicdrivers,放入 PROGRAM .apicdrivers。
05~08對齊八位元組的記憶體邊界。宣告程式區域 .exit.text,放入 PROGRAM .exit.text、.memexit.text。
09~11宣告程式區域 .exit.data,放入 PROGRAM .exit.data、.memexit.data、.memexit.rodata。
12對齊 4KB 的記憶體邊界。

linux/arch/x86/kernel/vmlinux.lds
01 .data..percpu : AT(ADDR(.data..percpu) - 0xC0000000) {
02  __per_cpu_load = .; __per_cpu_start = .; *(.data..percpu..first) 
02  . = ALIGN((1 << 12)); *(.data..percpu..page_aligned) 
03  . = ALIGN((1 << 6)); *(.data..percpu..readmostly) 
04  . = ALIGN((1 << 6)); *(.data..percpu) *(.data..percpu..shared_aligned) __per_cpu_end = .; }
05  . = ALIGN((1 << 12));
07  .init.end : AT(ADDR(.init.end) - 0xC0000000) { __init_end = .; }

行號說明
01宣告程式區域 .data..percpu。
02放入 PROGRAM .data..percpu..first。
03對齊 4KB 的記憶體邊界。放入 PROGRAM .data..percpu..first。
04對齊 64 位元組的記憶體邊界。放入 PROGRAM .data..percpu..readmostly。
05對齊 64 位元組的記憶體邊界。放入 PROGRAM .data..percpu、.data..percpu..shared_aligned。
06對齊 4KB 的記憶體邊界。
07宣告程式區域 .init.end。對映於前面的 .init.begin,將二者中間的所有程式區域包裝起來。

linux/arch/x86/kernel/vmlinux.lds
01  . = ALIGN((1 << 12));
02  .smp_locks : AT(ADDR(.smp_locks) - 0xC0000000) {
03   __smp_locks = .; *(.smp_locks) . = ALIGN((1 << 12)); __smp_locks_end = .;
04  }
05  . = ALIGN((1 << 12));
06  .bss : AT(ADDR(.bss) - 0xC0000000) {
07   __bss_start = .; *(.bss..page_aligned) *(.bss) . = ALIGN((1 << 12)); __bss_stop = .;
08  }
09  . = ALIGN((1 << 12));
10  .brk : AT(ADDR(.brk) - 0xC0000000) {
11   __brk_base = .;
12   . += 64 * 1024;
13   *(.brk_reservation)
14   __brk_limit = .;
15  }

行號說明
01對齊 4KB 的記憶體邊界。
02~04宣告程式區域 .smp_locks。放入 PROGRAM .smp_locks。結尾對齊 4KB 的記憶體邊界。
05對齊 4KB 的記憶體邊界。
06~08宣告程式區域 .bss。放入 PROGRAM .bss..page_aligned、.bss。結尾對齊 4KB 的記憶體邊界。
09對齊 4KB 的記憶體邊界。
10~15宣告程式區域 .brk。將程式區域往後延伸 64KB,再放入 .brk_reservation,並標記為 __brk_limit。

linux/arch/x86/kernel/vmlinux.lds
01  _end = .;
02         .stab 0 : { *(.stab) } .stabstr 0 : { *(.stabstr) } .stab.excl 0 : { *(.stab.excl) } 
03         .stab.exclstr 0 : { *(.stab.exclstr) } .stab.index 0 : { *(.stab.index) } 
04         .stab.indexstr 0 : { *(.stab.indexstr) } .comment 0 : { *(.comment) }
05         .debug 0 : { *(.debug) } .line 0 : { *(.line) } .debug_srcinfo 0 : { *(.debug_srcinfo) } 
06         .debug_sfnames 0 : { *(.debug_sfnames) } .debug_aranges 0 : { *(.debug_aranges) } 
07         .debug_pubnames 0 : { *(.debug_pubnames) } .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } 
08         .debug_abbrev 0 : { *(.debug_abbrev) } .debug_line 0 : { *(.debug_line) } 
09         .debug_frame 0 : { *(.debug_frame) } .debug_str 0 : { *(.debug_str) } 
10         .debug_loc 0 : { *(.debug_loc) } .debug_macinfo 0 : { *(.debug_macinfo) } 
11         .debug_weaknames 0 : { *(.debug_weaknames) } .debug_funcnames 0 : { *(.debug_funcnames) } 
12         .debug_typenames 0 : { *(.debug_typenames) } .debug_varnames 0 : { *(.debug_varnames) }
13  /DISCARD/ : { *(.exit.text) *(.memexit.text) *(.exit.data) *(.memexit.data) *(.memexit.rodata) *(.exitcall.exit) *(.discard) *(.discard.*) }
14  /DISCARD/ : { *(.eh_frame) }
15 }

行號說明
01標記此處為 _end。
02放入 PROGRAM .stab、.stabstr、.stab.excl。
03放入 PROGRAM .stab.exclstr、.stab.index。
04放入 PROGRAM .stab.indexstr、.comment。
05放入 PROGRAM .debug、.line、.debug_srcinfo。
06放入 PROGRAM .debug_sfnames、.debug_aranges。
07放入 PROGRAM .debug_pubnames、.debug_info、.gnu.linkonce.wi.*。
08放入 PROGRAM .debug_abbrev、.debug_line。
09放入 PROGRAM .debug_frame、.debug_str。
10放入 PROGRAM .debug_loc、.debug_macinfo。
11放入 PROGRAM .debug_weaknames、.debug_funcnames。
12放入 PROGRAM .debug_typenames、.debug_varnames。
13DISCARD 表示要丟棄區段,定義在 ld.pdf 3.6.7 。要丟棄區段包括 PROGRAM .exit.text、.memexit.text、.exit.data、.memexit.data、.memexit.rodata、.exitcall.exit、.discard、.discard.、.eh_frame。
12這個右大括號對映於 SECTIONS 的左大括號。

linux/arch/x86/kernel/vmlinux.lds
01 . = ASSERT((_end - 0xC0000000 <= (512 * 1024 * 1024)),
02     "kernel image bigger than KERNEL_IMAGE_SIZE");
03 . = ASSERT(kexec_control_code_size <= 2048,
04            "kexec control code size is too big");

行號說明
01~02ASSERT 的敘述中,第一個參數是條件式,第二個參數是條件不成立時要顯示的訊息。當整個映像位址範圍大於 500MB,表示映像太大,顯示錯誤訊息。
03~04。當kexec_control_code_size大於 2048,表示 kexec 的體積太大,顯示錯誤訊息。

1.3 構建設置程式

建置程式 SETUP.BIN 是 VMLINUX 的先行程式,由 linux/arch/x86/boot/Makefile 生成。 建置程式的生成過程是先從目的檔連結成可執行檔,再從可執行檔轉換成二進位檔,由下面兩個規則所完成。

linux/arch/x86/boot/Makefile
01 $(obj)/setup.elf: $(src)/setup.ld $(SETUP_OBJS) FORCE
02 	$(call if_changed,ld)
03 
04 $(obj)/setup.bin: $(obj)/setup.elf FORCE
05 	$(call if_changed,objcopy)

設置程式的程式碼檔案有 25 個檔案,這 25 個檔案可能會再引入其他目錄的程式檔案,這些檔案是在目錄 linux/arch/x86/boot/。
設置程式的檔案內容如下。

linux/arch/x86/boot/Makefile
01 setup-y	+= a20.o bioscall.o cmdline.o copy.o cpu.o cpucheck.o
02 setup-y	+= early_serial_console.o edd.o header.o main.o mca.o memory.o
03 setup-y	+= pm.o pmjump.o printf.o regs.o string.o tty.o video.o
04 setup-y	+= video-mode.o version.o
05 setup-$(CONFIG_X86_APM_BOOT) += apm.o
06 setup-y	+= video-vga.o
07 setup-y	+= video-vesa.o
08 setup-y	+= video-bios.o


1.4 構建 BZIMAGE

bzImage 中包含 linux/arch/x86/boot/setup.bin 和 linux/arch/x86/boot/vmlinux.bin。 其中 vmlinux.bin 是從 linux/vmlinux 經過轉換來的核心自我解壓縮映像檔。

執行 linux/arch/x86/boot/compressed/vmlinux 的編譯程序,編譯執行器會讀取 linux/arch/x86/boot/compressed/Makefile,執行 linux/arch/x86/boot/compressed/vmlinux 的生成規則。

01 linux/arch/x86/boot/Makefile
02 $(obj)/compressed/vmlinux: FORCE
03 	$(Q)$(MAKE) $(build)=$(obj)/compressed $@


1.4.1 壓縮核心

簡化核心映像: 生成 linux/arch/x86/boot/compressed/vmlinux.bin 的規則,將體積為 13418KB 的 linux/vmlinux 生成體積為 9043KB 的 compressed/vmlinux.bin。 這是因為剔除了不需要的區段和符號,由 -R .comment -S 所表示。

compressed/vmlinux.bin 的生成規則。

linux/arch/x86/boot/compressed/Makefile
01 OBJCOPYFLAGS_vmlinux.bin :=  -R .comment -S
02 $(obj)/vmlinux.bin: vmlinux FORCE
03 	$(call if_changed,objcopy)

壓縮核心映像: 生成 linux/arch/x86/boot/compressed/vmlinux 的規則,生成體積為 4682KB。 此時的檔案 vmlinux 是一個由壓縮檔 vmlinux.bin.gz 所構成的 ELF 格式的映像檔,壓縮檔 vmlinux.bin.gz 會位於區段 .rodata..compressed。 vmlinux 的入口點是 startup_32,即 head_32.S 的程式入口點,負責 vmlinux 的自我解壓縮。

vmlinux.bin.gz 的生成規則。

linux/arch/x86/boot/compressed/Makefile
01 $(obj)/vmlinux.bin.gz: $(vmlinux.bin.all-y) FORCE
02 	$(call if_changed,gzip)

工具程式 mkpiggy 會讀取 vmlinux.bin.gz,並產生 piggy.S,描述 vmlinux.bin.gz 的相關資訊。

piggy.S 的生成規則。

linux/arch/x86/boot/compressed/Makefile
01 $(obj)/piggy.S: $(obj)/vmlinux.bin.$(suffix-y) $(obj)/mkpiggy FORCE
02 	$(call if_changed,mkpiggy)
03 
04 cmd_mkpiggy = $(obj)/mkpiggy $< > $@ || ( rm -f $@ ; false )

這裡我們並沒有看到 vmlinux.bin.gz,怎麼知道 vmlinux.bin.gz 有被加入到 vmlinux 呢? 答案是 piggy.o。 piggy.o 是由 piggy.S 生成,生成 piggy.S 前,會先生成 vmlinux.bin.gz,因為這是生成規則的依賴條件。 vmlinux.bin.gz 被包進了 piggy.o,也就是說 piggy.o(4659KB) 就是 vmlinux.bin.gz(4658KB),來自於 compressed/vmlinux.bin(9043KB),也來自於 linux/vmlinux.bin(13418KB)。

piggy.S 的內容。

linux/arch/x86/boot/compressed/piggy.S
01 .section ".rodata..compressed","a",@progbits
02 .globl z_input_len
03 z_input_len = 4769350
04 .globl z_output_len
05 z_output_len = 9996376
06 .globl z_extract_offset
07 z_extract_offset = 0x50d000
08 .globl z_extract_offset_negative
09 z_extract_offset_negative = -0x50d000
10 .globl input_data, input_data_end
11 input_data:
12 .incbin "arch/x86/boot/compressed/vmlinux.bin.gz"
13 input_data_end:

自我解壓縮核心映像: 核心映像 linux/vmlinux 會被製作成 linux/arch/x86/boot/compressed/vmlinux,這是一個可以自我解壓縮的 ELF 格式的可執行檔,這裡以 compressed/vmlinux 簡稱之。 compressed/vmlinux 是由目錄 linux/arch/x86/boot/compressed 的檔案 head_32.o、misc.c、string.c、cmdline.c、early_serial_console.c、piggy.o等程式所組成。 其中,piggy.o 是用 vmlinux.bin.gz 製作成的 ELF 格式資料映像檔,vmlinux.bin.gz 是使用工具程式 gzip 壓縮的核心映像壓縮檔,原始檔案為 compressed/vmlinux.bin。

linux/arch/x86/boot/compressed/Makefile
01 $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/head_$(BITS).o $(obj)/misc.o $(obj)/string.o $(obj)/cmdline.o $(obj)/early_serial_console.o $(obj)/piggy.o FORCE
02 	$(call if_changed,ld)
03 	@:


1.4.2 包裝核心

完成核心壓縮後,接著要將 setup.bin 和自我解壓縮核心接駁在一起,包裝成 bzImage。

得到 linux/arch/x86/boot/compressed/vmlinux 後,接下來就是移除不需要的區段(-R .note),並剝除所有符號(-S)。這樣可以縮減壓縮映像檔體積。

linux/arch/x86/boot/Makefile
01 OBJCOPYFLAGS_vmlinux.bin := -O binary -R .note -R .comment -S
02 $(obj)/vmlinux.bin: $(obj)/compressed/vmlinux FORCE
03 	$(call if_changed,objcopy)

linux/Makefile 引入 include $(srctree)/scripts/Kbuild.include
scripts/Kbuild.include 引入 scripts/Makefile.build
scripts/Makefile.build 引入 scripts/Kbuild.include 和 scripts/Makefile.lib

linux/script/Makefile.lib
01 quiet_cmd_objcopy = OBJCOPY $@
01 cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@

將 setup.bin 和 vmlinux.bin 包裝成一個檔案 bzImage。 bzImage 的生成規則中,$(call if_changed,image) 會轉譯成 cmd_image,使用工具程式 build,將 setup.bin 和 vmlinux.bin 接駁成一個非 ELF 格式的映像檔 bzImage。

linux/arch/x86/boot/Makefile
01 $(obj)/bzImage: $(obj)/setup.bin $(obj)/vmlinux.bin $(obj)/tools/build FORCE
02 	$(call if_changed,image)
03 	@echo 'Kernel: $@ is ready' ' (#'`cat .version`')'
04 	
05 cmd_image = $(obj)/tools/build $(obj)/setup.bin $(obj)/vmlinux.bin \
06 	$(ROOT_DEV) > $@


1.4.3 核心包裝程式

核心包裝程式 build.c 的程式碼不長,基本的想法是,將兩個檔案前後接在一起,先是 setup.bin,後是 vmlinux.bin。 過程中會計算整個檔案的 CRC32 錯誤驗證碼值,透過 C 函式標準輸出入介面,將映像內容導引到 bzImage。

變數宣告,包括整數變數、檔案指標等。

linux/arch/x86/boot/tools/build.c
01 int main(int argc, char ** argv){
02     unsigned int i, sz, setup_sectors;
03     int c;
04     u32 sys_size;
05     u8 major_root, minor_root;
06     struct stat sb;
07     FILE *file;
08     int fd;
09     void *kernel;
10     u32 crc = 0xffffffffUL;
11     ....
12 }

設定啟動作業系統的根裝置資訊。

linux/arch/x86/boot/tools/build.c
01 int main(int argc, char ** argv){
02     ....
03     if ((argc < 3) || (argc > 4)) usage();
04     if (argc > 3) {
05         if (!strcmp(argv[3], "CURRENT")) {
06             if (stat("/", &sb)) {
07                 perror("/");
08                 die("Couldn't stat /");
09             }
10             major_root = major(sb.st_dev);
11             minor_root = minor(sb.st_dev);
12         } 
13         else if (strcmp(argv[3], "FLOPPY")) {
14             if (stat(argv[3], &sb)) {
15                 perror(argv[3]);
16                 die("Couldn't stat root device.");
17             }
18             major_root = major(sb.st_rdev);
19             minor_root = minor(sb.st_rdev);
20         } 
21         else {
22             major_root = 0;
23             minor_root = 0;
24         }
25     } 
26     else {
27         major_root = DEFAULT_MAJOR_ROOT;
28         minor_root = DEFAULT_MINOR_ROOT;
29     }
30     fprintf(stderr, "Root device is (%d, %d)\n", major_root, minor_root);
31     ....
32 }

行號說明
03輸入的參數數目必須等於三或四,參數一是 build 本身,參數二是 setup.bin,參數三是 vmlinux.bin,參數四是啟動磁碟。
04~25當參數四存在,表示有啟動磁碟的參數。
05~12當參數四是 CURRENT,讀取目前的根裝置資訊,並設定到啟動磁碟的參數位置。
13~20當參數四是 FLOPPY,讀取 FLOPPY 裝置資訊,並設定到啟動磁碟的參數位置。
21~24當參數四不是 CURRENT 或 FLOPPY,將啟動磁碟參數設定為 0,0。
26~29當沒有參數四,設定啟動磁碟為內定根裝置,即 0,0。
30顯示根裝置資訊,。

讀取設置程式的內容,。

linux/arch/x86/boot/tools/build.c
01 int main(int argc, char ** argv){
02     ....
03     file = fopen(argv[1], "r");
04     if (!file) die("Unable to open `%s': %m", argv[1]);
05     c = fread(buf, 1, sizeof(buf), file);
06     if (ferror(file)) die("read-error on `setup'");
07     if (c < 1024) die("The setup must be at least 1024 bytes");
08     if (buf[510] != 0x55 || buf[511] != 0xaa) die("Boot block hasn't got boot flag (0xAA55)");
09     fclose(file);
10     setup_sectors = (c + 511) / 512;
11     if (setup_sectors < SETUP_SECT_MIN) setup_sectors = SETUP_SECT_MIN;
12     i = setup_sectors*512;
13     memset(buf+c, 0, i-c);
14     buf[508] = minor_root;
15     buf[509] = major_root;
16     fprintf(stderr, "Setup is %d bytes (padded to %d bytes).\n", c, i);
17     ....
18 }

行號說明
03~04開啟設置程式,應該是 linux/arch/x86/boot/setup.bin。開啟失敗,終止函式執行。
05讀取設置程式,最大體積是 32 KB。
06當檔案錯誤,終止函式執行。
07當設置程式體積小於 1024 位元組,表示設置程式體積太小,終止函式執行。
08當設置程式的特徵值不是 0xaa55,表示設置程式內容出錯,終止函式執行。
09關閉設置程式,。
10計算設置程式體積,單位是磁區。
11當設置程式體積值小於五,將設置程式的體積值設定為五。
12計算設置程式的新體積值,這是以磁區數計算出來的值,不是最原始的體積值。
13將原始體積值和新體積值之間的緩衝區資料清除為 0。
14設定根裝置的次參數,。
15設定根裝置的主參數,。
16顯示設置程式的體積值,。

讀取核心映像的資訊,並寫入設置程式的緩衝區。

linux/arch/x86/boot/tools/build.c
01 int main(int argc, char ** argv){
02     ....
03     fd = open(argv[2], O_RDONLY);
04     if (fd < 0) die("Unable to open `%s': %m", argv[2]);
05     if (fstat(fd, &sb)) die("Unable to stat `%s': %m", argv[2]);
06     sz = sb.st_size;
07     fprintf (stderr, "System is %d kB\n", (sz+1023)/1024);
08     kernel = mmap(NULL, sz, PROT_READ, MAP_SHARED, fd, 0);
09     if (kernel == MAP_FAILED) die("Unable to mmap '%s': %m", argv[2]);
10     sys_size = (sz + 15 + 4) / 16;
11     buf[0x1f1] = setup_sectors-1;
12     buf[0x1f4] = sys_size;
13     buf[0x1f5] = sys_size >> 8;
14     buf[0x1f6] = sys_size >> 16;
15     buf[0x1f7] = sys_size >> 24;
16     ....
17 }

行號說明
03~04開啟 LINUX 核心映像檔,應該是 linux/arch/x85/boot/vmlinux.bin。開啟失敗,終止函式執行。
05取得檔案資訊,主要是取得檔案體積。檔案資訊取得失敗,終止函式執行。
06取得檔案體積資訊,。
07顯示檔案體積資訊,。
08~09檢驗核心記憶體對映,??。記憶體對映失敗,終止函式執行。
10將核心體積轉換成 16 位元組為單位的體積值。比較好的寫法是 (sz + 4 + 15),4 表示 CRC32的四位元組,15 是為了對齊十六位元組而加上去的值。
11設定設置程式的體積,單位是磁區 512 位元組。
12~15設定核心體積值,以 LSB 次序擺放。

計算 CRC,CRC 循環沉餘錯誤檢查碼。LINUX 是用 CRC32 保護核心映像 bzImage。

linux/arch/x86/boot/tools/build.c
01 int main(int argc, char ** argv){
02     ....
03     crc = partial_crc32(buf, i, crc);
04     if (fwrite(buf, 1, i, stdout) != i) die("Writing setup failed");
05     crc = partial_crc32(kernel, sz, crc);
06     if (fwrite(kernel, 1, sz, stdout) != sz) die("Writing kernel failed");
07     while (sz++ < (sys_size*16) - 4) {
08         crc = partial_crc32_one('\0', crc);
09         if (fwrite("\0", 1, 1, stdout) != 1)
10             die("Writing padding failed");
11     }
12     fprintf(stderr, "CRC %lx\n", crc);
13     if (fwrite(&crc, 1, 4, stdout) != 4) die("Writing CRC failed");
14     close(fd);
15     return 0;
16 }

行號說明
03計算設置程式部分的 CRC32 值,。
04將設置程式的內容寫到輸出檔,stdout 會透過編譯檔的描述,導向檔案 bzImage。
05計算 LINUX 核心部分的 CRC32 值,。
06將設置程式的內容寫到輸出檔,stdout 會透過編譯檔的描述,導向檔案 bzImage。
07~11將映像體積對齊十六位元組的邊界,不過這裡只對齊到十六位元組減四,因為還有四位元組的 CRC32 值要填入。
12顯示 CRC32 值,。
13將 CRC32 的內容寫到輸出檔,stdout 會透過編譯檔的描述,導向檔案 bzImage。
14關閉 LINUX 核心映像檔檔,。
15函士回返,此時 bzImage 已經完成。

1.5 編譯紀錄

當編譯 LINUX 時,編譯執行器(make.exe)會從頂端編譯檔 linux/Makefile 開始執行編譯程序。 編譯執行器解析編譯檔 linux/Makefile,知道要完成的目標是 vmlinux,並且知道要先取得 vmlinux-lds、vmlinux-init、vmlinux-main、vmlinux.o、kallsyms.o。 於是編譯執行器建立一份關係樹,由樹的最下層開始編譯,直到樹的頂端 VMLINUX 完成為止。
vmlinux-lds 可以很快取得,vmlinux.o 和 kallsyms.o 最晚取得,大部分的時間都花在編譯 vmlinux-init、vmlinux-main。 因為 vmlinux 的目的檔群主要是由 vmlinux-init、vmlinux-main 構成。

VMLINUX 的依賴關係: vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE

vmlinux-init 是 $(head-y) $(init-y)。
vmlinux-main 是 $(core-y) $(libs-y) $(drivers-y) $(net-y)。

head-y 是來自 linux/arch/x86/。 core 函式庫,包括目錄 user、kernel、mm、fs、ipc、security、crypto、block。
driver 函式庫,包括目錄 drivers、sound、firmware。
init 函式庫,包括目錄 init。
net 函式庫,包括目錄 net。
libs 函式庫,包括目錄 lib。

編譯執行器會到 vmlinux-init 和 vmlinux-main 所指示的子目錄群建立 built-in.o。 每個子目錄都有自己的 Makefile 用來指示該目錄要編譯的檔案和目錄。 子目錄底下還有子目錄,所以整個關係樹會很大,甚至複雜,不過這一切就交給編譯執行器去搞定。 我們需要了解的是構建系統的基本思路,即可。

筆者將編譯的過程結果的後段抓下來,有按照時間順序,但不一定是連續的資料。 從資料中可以看得出來,前面是在各子目錄內建立 built-in.o。 所有目的檔都建立完成後,連結成 vmlinux.o,再進行 vmlinux 的後處理,最後生成 bzImage。


1.5.1 編譯核心映像

這部分的動作單純,所花的時間最久,因為要探索 vmlinux-init、vmlinux-main 所包含的目的檔群。這將編譯上千個檔案,並在每個目錄中形成 built-in.o。 這裡筆者只取幾個象徵性的檔案,除了 lib.a 之外,其他的都是 built-in.o。

01   ....
02   LD      drivers/net/wireless/built-in.o 
03   ....
04   LD      drivers/net/built-in.o 
05   ....
06   LD      drivers/nfc/built-in.o 
07   ....
08   LD      drivers/pci/built-in.o 
09   ....
10   LD      drivers/built-in.o 
11   ....
12   LD      sound/built-in.o 
13   AR      arch/x86/lib/lib.a
14   LD      vmlinux.o
15   MODPOST vmlinux.o


1.5.2 編譯 vmlinux

編譯 vmlinux 就是執行 rule_vmlinux__,產生 vmlinux 並驗證 vmlinux。

linux/Makefile
$(call if_changed_rule,vmlinux__)

rule_vmlinux__ 的內容。

linux/Makefile
define rule_vmlinux__
	:
	$(if $(CONFIG_KALLSYMS),,+$(call cmd,vmlinux_version))

	$(call cmd,vmlinux__)
	$(Q)echo 'cmd_$@ := $(cmd_vmlinux__)' > $(@D)/.$(@F).cmd

	$(Q)$(if $($(quiet)cmd_sysmap),                                      \
	  echo '  $($(quiet)cmd_sysmap)  System.map' &&)                     \
	$(cmd_sysmap) $@ System.map;                                         \
	if [ $$? -ne 0 ]; then                                               \
		rm -f $@;                                                    \
		/bin/false;                                                  \
	fi;
	$(verify_kallsyms)
endef

01   GEN     .version
02   CHK     include/generated/compile.h
03   UPD     include/generated/compile.h
04   CC      init/version.o
05   LD      init/built-in.o
06   LD      .tmp_vmlinux1
07   KSYM    .tmp_kallsyms1.S
08   AS      .tmp_kallsyms1.o
09   LD      .tmp_vmlinux2
10   KSYM    .tmp_kallsyms2.S
11   AS      .tmp_kallsyms2.o
12   LD      vmlinux
13   SYSMAP  System.map
14   SYSMAP  .tmp_System.map


1.5.3 生成 bzImage

生成 bzimage 的描述是在 linux/arch/x86/boot/Makefile。 bzimage 是結合 setup.bin 和 vmlinux.bin 的映像檔。 setup.bin 是 I386 架構的設置程式,由 arch/x86/boot/ 目錄下的程式碼生成。 arch/x86/boot/vmlinux.bin 是從 linux/vmlinux 輾轉轉換過來,包含 linux/vmlinux 的壓縮區段的 ELF 格式映像檔。 bzimage 是 LINUX 執行時的核心映像,可以安裝到 /boot 目錄,用來啟動 LINUX。

01   CC      arch/x86/boot/a20.o
02   AS      arch/x86/boot/bioscall.o
03   CC      arch/x86/boot/cmdline.o
04   ...
05   CC      arch/x86/boot/video-bios.o
06   LD      arch/x86/boot/setup.elf
07   OBJCOPY arch/x86/boot/setup.bin
08   OBJCOPY arch/x86/boot/vmlinux.bin
09   HOSTCC  arch/x86/boot/tools/build
10   BUILD   arch/x86/boot/bzImage
11 Root device is (8, 7)
12 Setup is 13148 bytes (padded to 13312 bytes).
13 System is 4675 kB
14 CRC 88602def
15 Kernel: arch/x86/boot/bzImage is ready
16   Building modules, stage 2.
17   MODPOST 3 modules
18   CC      arch/x86/kernel/test_nx.mod.o
19   LD [M]  arch/x86/kernel/test_nx.ko
20   CC      drivers/scsi/scsi_wait_scan.mod.o
21   LD [M]  drivers/scsi/scsi_wait_scan.ko
22   CC      net/netfilter/xt_mark.mod.o
23   LD [M]  net/netfilter/xt_mark.ko
24 [root@localhost linux-3.0]#


1.5.4 總結

linux 編譯過程會先生成 boot/vmlinux.bin,再和 boot/setup.bin 合併成 bzImage。
boot/vmlinux.bin 是由top Makefile 生成 vmlinux.bin,在複製到 arch/x86/boot/compressed,壓縮製成 vmlinux.bin.gz,再生成具有自我解壓縮能力的 elf 格式的 vmlinux,再剝除不必要的區段並生成 vmlinux.bin。
在我的作業系統中,不需要經過這麼多階段的處理。只要將 setup.bin 和 top Makefile 生成的 vmlinux.bin 直接結合成作業系統映像檔。
因此後續工作是追蹤 linux 生成 vmlinux.bin 的過程。

vmlinux.bin 的生成是從 arch/x86 開始編譯核心相關的程式碼,包括 arch/x86/kernel、arch/x86/mm、arch/x86/video 等等。
由 arch/x86/Makefile 的 core-y、head-y、libs-y、drivers-y 的內容所表示。這是當 top Makefile 引入 $(srctree)/arch/$(SRC_ARCH)/Makefile 時,所執行的工作。

   linux/Makefile
00 ifeq ($(config-targets),1)
00 include $(srctree)/arch/$(SRCARCH)/Makefile
00 ...

之後,回到根目錄,探索並編譯其他原始碼目錄,由 top Makefile 的 init-y、drivers-y、net-y、libs-y、core-y 所表示。
當探索完所有的目錄後,就會將各個目錄的 build_in.o 合併成一個 elf 格式映像檔 linux/vmlinux.bin,體積約 150MB。
linux/vmlinux 會由 arch/x86/boot/compressed/Makefile 讀取並生成 arch/x86/boot/compressed/vmlinux.bin,體積約 8.2 MB。

   arch/x86/boot/compressed/Makefile
00 OBJCOPYFLAGS_vmlinux.bin :=  -R .comment -S
00 $(obj)/vmlinux.bin: vmlinux FORCE
00 	$(call if_changed,objcopy)

編譯過程會用到其他組態檔與工具程式,另述。