參考
一次實驗引起的故事 – kernel build system探索—vmlinux是如何煉成的– kernel makefilepython深度探索Linux操做系統:系統構建和原理解析.pdflinux
在前面的博文中,咱們先是爲本身的Ubuntu安裝了一套內核源碼樹,而後爲了方便進行嵌入式交叉編譯,咱們又爲arm板子構建了一套源碼樹。
那麼如今咱們已經知道如何本身的電腦上去構建、安裝一個定製化的Linux內核,可是咱們仍是要在嘮叨一些。
當你在內核源碼路徑裏敲下make時究竟發生什麼
ios
當咱們剛剛開始接觸內核代碼時,毫無頭緒,這時候Makefile是每每是咱們打開的第一個文件,這個makefile是Linux內核代碼的根makefile,內核構建就始於此處。是的,它的內容不少,可是若是你已經讀過內核源代碼,你就會發現每一個包含代碼的目錄都有一個本身的Makefile。固然了,咱們不會去描述每一個代碼文件是怎麼編譯連接的,因此咱們將只會挑選一些通用的例子來講明問題。而你不會在這裏找到構建內核的文檔、如何整潔內核代碼、tags的生成和交叉編譯相關的說明,等等。git
咱們僅僅將從make開始,使用標準的內核配置文件,一直到生成了內核鏡像bzImage或者zImage結束。
固然在着以前咱們須要瞭解,咱們make到底是要構建一個什麼樣的目標,我想這個github
對於Linux內核,編譯能夠生成不一樣格式的映像文件,例如:sql
make zImag make uImage
zImage是ARM Linux經常使用的一種壓縮映像文件,uImage是U-boot專用的映像文件,它是在zImage以前加上一個長度爲0x40的「頭」,說明這個映像文件的類型、加載位置、生成時間、大小等信息。換句話說,若是直接從uImage的0x40位置開始執行,zImage和uImage沒有任何區別。另外,Linux2.4內核不支持uImage,Linux2.6內核加入了不少對嵌入式系統的支持,可是uImage的生成也須要設置。shell
幾種linux內核文件的區別:api
一、vmlinux 編譯出來的最原始的內核文件,未壓縮。
二、zImage 是vmlinux通過gzip壓縮後的文件。適用於小內核
三、bzImage bz表示「big zImage」,不是用bzip2壓縮的。二者的不一樣之處在於,zImage解壓縮內核到低端內存(第一個640K),bzImage解壓縮內核到高端內存(1M以上)。若是內核比較小,那麼採用zImage或bzImage都行,若是比較大應該用bzImage。適用於大內核
四、uImage U-boot專用的映像文件,它是在zImage以前加上一個長度爲0x40的tag。
五、vmlinuz 是bzImage/zImage文件的拷貝或指向bzImage/zImage的連接。
六、initrd 是「initial ramdisk」的簡寫。通常被用來臨時的引導硬件到實際內核vmlinuz可以接管並繼續引導的狀態。安全
vmlinux是未壓縮的內核,是make工做編譯出的原始內核,vmlinuz是vmlinux的壓縮文件。ruby
vmlinux 是ELF文件,即編譯出來的最原始的文件。
vmlinuz是可引導的、壓縮的內核。「vm」表明「Virtual Memory」。Linux 支持虛擬內存,不像老的操做系統好比DOS有640KB內存的限制。Linux可以使用硬盤空間做爲虛擬內存,所以得名「vm」。vmlinuz是可執行的Linux內核,它位於/boot/vmlinuz,它通常是一個軟連接,是bzImage/zImage文件的拷貝或指向bzImage/zImage的連接。
vmlinuz的創建有兩種方式。
一是編譯內核時經過「make zImage」建立,而後經過:「cp /usr/src/linux-2.4/arch/i386/linux/boot/zImage /boot/vmlinuz」產生。zImage適用於小內核的狀況,它的存在是爲了向後的兼容性。
二是內核編譯時經過命令make bzImage建立,而後經過:「cp /usr/src/linux-2.4/arch/i386/linux/boot/bzImage /boot/vmlinuz」產生。bzImage是壓縮的內核映像,須要注意,bzImage不是用bzip2壓縮的,bzImage中的bz容易引發誤解,bz表示「big zImage」。 bzImage中的b是「big」意思。
zImage(vmlinuz)和bzImage(vmlinuz)都是用gzip壓縮的。它們不只是一個壓縮文件,並且在這兩個文件的開頭部份內嵌有gzip解壓縮代碼。因此你不能用gunzip 或 gzip –dc解包vmlinuz。
內核文件中包含一個微型的gzip用於解壓縮內核並引導它。二者的不一樣之處在於,老的zImage解壓縮內核到低端內存(第一個640K),bzImage解壓縮內核到高端內存(1M以上)。若是內核比較小,那麼能夠採用zImage 或bzImage之一,兩種方式引導的系統運行時是相同的。大的內核採用bzImage,不能採用zImage。
可是注意一般狀況下是不能用vmlinuz解壓縮獲得vmlinux的
initrd是「initial ramdisk」的簡寫。initrd通常被用來臨時的引導硬件到實際內核vmlinuz可以接管並繼續引導的狀態。
initrd 映象文件是使用mkinitrd建立的。mkinitrd實用程序可以建立initrd映象文件。這個命令是RedHat專有的。其它Linux發行版或許有相應的命令。這是個很方便的實用程序。具體狀況請看幫助:man mkinitrd下面的命令建立initrd映象文件。
最後生成的內核鏡象有兩種 zImage 以及 uImage 。其中 zImage 下載到目標板中後,能夠直接用 uboot 的命令 go 來進行直接跳轉。這時候內核直接解壓啓動。可是沒法掛載文件系統,由於 go 命令沒有將內核須要的相關的啓動參數傳遞給內核。傳遞啓動參數咱們必須使用命令 bootm 來進行跳轉。 Bootm 命令跳轉只處理 uImage 的鏡象。
uboot 源代碼的 tools/ 目錄下有 mkimage 工具,這個工具能夠用來製做不壓縮或者壓縮的多種可啓動映象文件。
mkimage 在製做映象文件的時候,是在原來的可執行映象文件的前面加上一個 0x40 字節的頭,記錄參數所指定的信息,這樣 uboot 才能識別這個映象是針對哪一個 CPU 體系結構的,哪一個 OS 的,哪一種類型,加載內存中的哪一個位置, 入口點在內存的那個位置以及映象名是什麼
vmlinux是內核文件,zImage是通常狀況下默認的壓縮內核映像文件,壓縮vmlinux,加上一段解壓啓動代碼獲得。而uImage 則是使用工具mkimage對普通的壓縮內核映像文件(zImage)加工而得。它是uboot專用的映像文件,它是在zImage以前加上一個長度爲 64字節的「頭」,說明這個內核的版本、加載位置、生成時間、大小等信息;其0x40以後與zImage沒區別。
其實就是一個自動跟手動的區別,有了uImage頭部的描述,u-boot就知道對應Image的信息,若是沒有頭部則須要本身手動去搞那些參數。
如何生成 uImage文件?首先在uboot的/tools目錄下尋找mkimage文件,把其copy到系統/usr/local/bin目錄下,這樣就完成制 做工具。而後在內核目錄下運行make uImage,若是成功,即可以在arch/arm/boot/目錄下發現uImage文件,其大小比 zImage多64個字節。
此外,平時調試用uImage,不用去管調整了哪些東西;zImage則是一切OK後直接燒0X0。開機就運行
在開始編譯前要進行不少準備工做。最主要的就是找到並配置好配置文件,make命令要使用到的參數都須要從這些配置文件獲取。如今就讓咱們深刻內核的根makefile吧
內核的根Makefile負責構建兩個主要的文件:vmlinux(內核鏡像可執行文件)和模塊文件moudles。
咱們先看看內核的Makefile開始的幾行head -n 6 Makefile
VERSION = 4 PATCHLEVEL = 2 SUBLEVEL = 3 EXTRAVERSION = NAME = Hurr durr I'ma sheep
Makefile在開始的時候定義了幾個變量,這些變量決定了當前內核的版本,而且被使用在不少不一樣的地方,好比同一個Makefile中的KERNELVERSION
關於版本號
Linux內核有三個不一樣的命名方案。
參見 https://zh.wikipedia.org/wiki/Linux%E5%86%85%E6%A0%B8
早期版本
第一種方式用於1.0版本以前(包括1.0)。第一個版本的內核是0.01。其次是0.02,0.03,0.10,0.11,0.12(第一GPL版本),0.95,0.96,0.97,0.98,0.99及1.0。從0.95版有許多的補丁發佈於主要版本版本之間。舊計劃
第二種方式用於1.0以後到2.6,版本的格式爲A.B.C,其中A,B,C表明:A大幅度轉變的內核。這是不多發生變化,只有當發生重大變化的代碼和核心發生纔會發生。在歷史上曾改變兩次的內核:1994年的1.0及1996年的2.0。
B是指一些重大修改的內核。內核使用了傳統的奇數次要版本號碼的軟件號碼系統(用偶數的次要版本號碼來表示穩定版本)。
C是指輕微修訂的內核。這個數字當有安全補丁,bug修復,新的功能或驅動程序,內核便會有變化。
這樣穩定版原本源於上一個測試版升級版本號,而一個穩定版本發展到徹底成熟後就再也不發展。自2.6.0(2003年12月)發佈後,人們認識到,更短的發佈週期將是有益的。自那時起,版本的格式爲A.B.C.D,其中A,B,C,D表明:
A和B是可有可無的
C是內核的版本新計劃
自3.0(2011年7月)發佈後,版本的格式爲3.A.B,其中A,B表明:A是內核的版本
B是安全補丁使用一種「time-based」的方式。3.0版本以前,是一種「A.B.C.D」的格式。七年裏,前兩個數字A.B即「2.6」保持不變,C隨着新版本的發佈而增長,D表明一些bug修復,安全更新,添加新特性和驅動的次數。
3.0版本以後是「A.B.C」格式,B隨着新版本的發佈而增長, C表明一些bug修復,安全更新,新特性和驅動的次數。第三種方式中再也不使用偶數表明穩定版,奇數表明開發版這樣的命名方式。舉個例子:3.7.0表明的不是開發版,而是穩定版!而4.0(2015年4月)發佈後,則延續3.A.B的命名格式,只是將主版號變動爲4。
接下來咱們會看到不少ifeq條件判斷語句,它們負責檢查傳遞給make的參數。內核的Makefile提供了一個特殊的編譯選項makehelp,這個選項能夠生成全部的可用目標和一些能傳給make的有效的命令行參數。
舉個例子,首先出現的就是-V
,那麼make V=1
會在構建過程當中輸出詳細的編譯信息,第一個ifeq就是檢查傳遞給make的V=n選項。
使用cat -n Makefile | head -n 83 | tail -n +23
查看
# Avoid interference with shell env settings unexport GREP_OPTIONS # We are using a recursive build, so we need to do a little thinking # to get the ordering right. # # Most importantly: sub-Makefiles should only ever modify files in # their own directory. If in some directory we have a dependency on # a file in another dir (which doesn't happen often, but it's often # unavoidable when linking the built-in.o targets which finally # turn into vmlinux), we will call a sub make in that other dir, and # after that we are sure that everything which is in that other dir # is now up to date. # # The only cases where we need to modify files which have global # effects are thus separated out and done before the recursive # descending is started. They are now explicitly listed as the # prepare rule. # Beautify output # --------------------------------------------------------------------------- # # Normally, we echo the whole command before executing it. By making # that echo $($(quiet)$(cmd)), we now have the possibility to set # $(quiet) to choose other forms of output instead, e.g. # # quiet_cmd_cc_o_c = Compiling $(RELDIR)/$@ # cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< # # If $(quiet) is empty, the whole command will be printed. # If it is set to "quiet_", only the short version will be printed. # If it is set to "silent_", nothing will be printed at all, since # the variable $(silent_cmd_cc_o_c) doesn't exist. # # A simple variant is to prefix commands with $(Q) - that's useful # for commands that shall be hidden in non-verbose mode. # # $(Q)ln $@ :< # # If KBUILD_VERBOSE equals 0 then the above command will be hidden. # If KBUILD_VERBOSE equals 1 then the above command is displayed. # # To put more focus on warnings, be less verbose as default # Use 'make V=1' to see the full commands ifeq ("$(origin V)", "command line") KBUILD_VERBOSE = $(V) endif ifndef KBUILD_VERBOSE KBUILD_VERBOSE = 0 endif ifeq ($(KBUILD_VERBOSE),1) quiet = Q = else quiet=quiet_ Q = @ endif
若是V=n這個選項傳給了make,系統就會給變量KBUILD_VERBOSE選項附上V的值,不然的話KBUILD_VERBOSE就會爲0。而後系統會檢查KBUILD_VERBOSE的值,以此來決定quiet和Q的值。符號@控制命令的輸出,若是它被放在一個命令以前,這條命令的輸出將會是CCscripts/mod/empty.o,而不是Compiling….scripts/mod/empty.o(LCTT譯註:CC在makefile中通常都是編譯命令)。在這段最後,系統導出了全部的變量。
而後是-s
的控制
# If the user is running make -s (silent mode), suppress echoing of # commands ifneq ($(filter 4.%,$(MAKE_VERSION)),) # make-4 ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),) quiet=silent_ endif else # make-3.8x ifneq ($(filter s% -s%,$(MAKEFLAGS)),) quiet=silent_ endif endif export quiet Q KBUILD_VERBOSE
下一個ifeq語句檢查的是傳遞給make的選項O=/dir,這個選項容許在指定的目錄dir輸出全部的結果文件
使用 cat -n Makefile | head -n 153 | tail -n +127
查看
# Cancel implicit rules on top Makefile
$(CURDIR)/Makefile Makefile: ; ifneq ($(KBUILD_OUTPUT),) # Invoke a second make in the output directory, passing relevant variables # check that the output directory actually exists saved-output := $(KBUILD_OUTPUT) KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \ && /bin/pwd) $(if $(KBUILD_OUTPUT),, \ $(error failed to create output directory "$(saved-output)")) PHONY += $(MAKECMDGOALS) sub-make $(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make @: sub-make: FORCE $(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \ -f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS)) # Leave processing to above invocation of make skip-makefile := 1 endif # ifneq ($(KBUILD_OUTPUT),) endif # ifeq ($(KBUILD_SRC),)
系統會檢查變量KBUILD_SRC,它表明內核代碼的頂層目錄,若是它是空的(第一次執行makefile時老是空的),咱們會設置變量KBUILD_OUTPUT爲傳遞給選項O的值(若是這個選項被傳進來了)。下一步會檢查變量KBUILD_OUTPUT,若是已經設置好,那麼接下來會作如下幾件事:
將變量KBUILD_OUTPUT的值保存到臨時變量saved-output;
嘗試建立給定的輸出目錄;
檢查建立的輸出目錄,若是失敗了就打印錯誤;
若是成功建立了輸出目錄,那麼就在新目錄從新執行make命令(參見選項-C)。
下一個ifeq語句會檢查傳遞給make的選項C
使用 cat -n Makefile | head -n 178 | tail -n +153
查看
# We process the rest of the Makefile if this is the final invocation of make
ifeq ($(skip-makefile),)
# Do not print "Entering directory ...", # but we want to display it when entering to the output directory # so that IDEs/editors are able to understand relative filenames. MAKEFLAGS += --no-print-directory # Call a source code checker (by default, "sparse") as part of the # C compilation. # # Use 'make C=1' to enable checking of only re-compiled files. # Use 'make C=2' to enable checking of *all* source files, regardless # of whether they are re-compiled or not. # # See the file "Documentation/sparse.txt" for more details, including # where to get the "sparse" utility. ifeq ("$(origin C)", "command line") KBUILD_CHECKSRC = $(C) endif ifndef KBUILD_CHECKSRC KBUILD_CHECKSRC = 0 endif
選項C會告訴makefile須要使用環境變量$CHECK提供的工具來檢查所有c代碼,默認狀況下會使用sparse。
咱們能夠看到以前先檢查了skip-makefile
,這個變量在選項O的時候被定義爲1
選項M會用來編譯外部模塊
使用cat -n Makefile | head -n 198 | tail -n +178
查看
# Use make M=dir to specify directory of external module to build # Old syntax make ... SUBDIRS=$PWD is still supported # Setting the environment variable KBUILD_EXTMOD take precedence ifdef SUBDIRS KBUILD_EXTMOD ?= $(SUBDIRS) endif ifeq ("$(origin M)", "command line") KBUILD_EXTMOD := $(M) endif # If building an external module we do not care about the all: rule # but instead _all depend on modules PHONY += all ifeq ($(KBUILD_EXTMOD),) _all: all else _all: modules endif
緊接着系統檢查了變量KBUILD_SRC
,若是KBUILD_SRC
沒有被設置,系統會設置變量srctree爲當前目錄./
, 使用cat -n Makefile | head -n 215 | tail -n +198
進行查看
ifeq ($(KBUILD_SRC),) # building in the source tree srctree := . else ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR))) # building in a subdirectory of the source tree srctree := .. else srctree := $(KBUILD_SRC) endif endif objtree := . src := $(srctree) obj := $(objtree) VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD)) export srctree objtree VPATH
這將會告訴Makefile內核的源碼樹就在執行make命令的目錄,而後要設置objtree和其餘變量爲這個目錄,而且將這些變量導出。
接着就是要獲取SUBARCH的值,這個變量表明瞭當前的系統架構(LCTT譯註:通常都指CPU架構):
使用cat Makefile | head -n 230 | tail -n +217
查看
# SUBARCH tells the usermode build what the underlying arch is. That is set # first, and if a usermode build is happening, the "ARCH=um" on the command # line overrides the setting of ARCH below. If a native build is happening, # then ARCH is assigned, getting whatever value it gets normally, and # SUBARCH is subsequently ignored. SUBARCH := $(shell uname -m | sed -e s/i.86/x86/ -e s/x86_64/x86/ \ -e s/sun4u/sparc64/ \ -e s/arm.*/arm/ -e s/sa110/arm/ \ -e s/s390x/s390/ -e s/parisc64/parisc/ \ -e s/ppc.*/powerpc/ -e s/mips.*/mips/ \ -e s/sh[234].*/sh/ -e s/aarch64.*/arm64/ )
它其實就是執行了以下的命令
uname -m | sed -e s/i.86/x86/ -e s/x86_64/x86/ -e s/sun4u/sparc64/ -e s/arm.*/arm/ -e s/sa110/arm/ -e s/s390x/s390/ -e s/parisc64/parisc/ -e s/ppc.*/powerpc/ -e s/mips.*/mips/ -e s/sh[234].*/sh/ -e s/aarch64.*/arm64/
個人機子是Ubuntu-Gnome14.04 LTS x86的(即x86架構)運行一下
獲得以下信息
如你所見,系統執行uname獲得機器、操做系統和架構的信息。由於咱們獲得的是uname的輸出,因此咱們須要作一些處理再賦給變量SUBARCH。
得到SUBARCH
以後就要設置SRCARCH
和hfr-arch
SRCARCH提供了硬件架構相關代碼的目錄
hfr-arch提供了相關頭文件的目錄
ARCH ?= $(SUBARCH) CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%) # Architecture as present in compile.h UTS_MACHINE := $(ARCH) SRCARCH := $(ARCH) # Additional ARCH settings for x86 ifeq ($(ARCH),i386) SRCARCH := x86 endif ifeq ($(ARCH),x86_64) SRCARCH := x86 endif # Additional ARCH settings for sparc ifeq ($(ARCH),sparc32) SRCARCH := sparc endif ifeq ($(ARCH),sparc64) SRCARCH := sparc endif # Additional ARCH settings for sh ifeq ($(ARCH),sh64) SRCARCH := sh endif # Additional ARCH settings for tile ifeq ($(ARCH),tilepro) SRCARCH := tile endif ifeq ($(ARCH),tilegx) SRCARCH := tile endif # Where to locate arch specific headers hdr-arch := $(SRCARCH)
注意:ARCH是SUBARCH的別名。
若是沒有設置過表明內核配置文件路徑的變量KCONFIG_CONFIG
,下一步系統會設置它,默認狀況下就是.config,這個文件是否是很熟悉,它就是咱們make menuconfig後的那個.config配置文件,裏面寫入咱們內核編譯的全部信息
使用cat -n Makefile | head -n 292 | tail -n +289
查看
KCONFIG_CONFIG ?= .config
export KCONFIG_CONFIG
使用cat -n Makefile | head -n 297 | tail -n +292
查看
# SHELL used by kbuild CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \ else if [ -x /bin/bash ]; then echo /bin/bash; \ else echo sh; fi ; fi)
接下來就要設置一組和編譯內核的編譯器相關的變量。咱們會設置主機的C和C++的編譯器及相關配置項
使用cat -n Makefile | head -n 307 | tail -n +297
查看
HOSTCC = gcc HOSTCXX = g++ HOSTCFLAGS = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89 HOSTCXXFLAGS = -O2 ifeq ($(shell $(HOSTCC) -v 2>&1 | grep -c "clang version"), 1) HOSTCFLAGS += -Wno-unused-value -Wno-unused-parameter \ -Wno-missing-field-initializers -fno-delete-null-pointer-checks endif
咱們能夠看到Makefile在這裏開始適配表明C/C++編譯器的變量CC和CXX
那爲何還要HOST*這些變量呢?這是由於CC是編譯內核過程當中要使用的目標架構的編譯器,可是HOSTCC是要被用來編譯一組host程序的(下面咱們就會看到)。
而後咱們就看到變量KBUILD_MODULES和KBUILD_BUILTIN的定義,這兩個變量決定了咱們要編譯什麼東西(內核、模塊或者二者都有):
使用cat -n Makefile | head -n 337 | tail -n +307
查看
# Decide whether to build built-in, modular, or both.
# Normally, just do built-in. KBUILD_MODULES := KBUILD_BUILTIN := 1 # If we have only "make modules", don't compile built-in objects. # When we're building modules with modversions, we need to consider # the built-in objects during the descend as well, in order to # make sure the checksums are up to date before we record them. ifeq ($(MAKECMDGOALS),modules) KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1) endif # If we have "make <whatever> modules", compile modules # in addition to whatever we do anyway. # Just "make" or "make all" shall build modules as well ifneq ($(filter all _all modules,$(MAKECMDGOALS)),) KBUILD_MODULES := 1 endif ifeq ($(MAKECMDGOALS),) KBUILD_MODULES := 1 endif export KBUILD_MODULES KBUILD_BUILTIN export KBUILD_CHECKSRC KBUILD_SRC KBUILD_EXTMOD
在這咱們能夠看到這些變量的定義,而且,若是們僅僅傳遞了modules給make,變量KBUILD_BUILTIN會依賴於內核配置選項CONFIG_MODVERSIONS。
接着下一步操做是引入下面的文件:
使用查看 cat Makefile | head -n 341 | tail -n +337
# We need some generic definitions (do not try to remake the file). scripts/Kbuild.include: ; include scripts/Kbuild.include
文件Kbuild或者又叫作KernelBuildSystem是一個用來管理構建內核及其模塊的特殊框架。kbuild文件的語法與makefile同樣。文件scripts/Kbuild.include爲kbuild系統提供了一些常規的定義。由於咱們包含了這個kbuild文件,咱們能夠看到和不一樣工具關聯的這些變量的定義,這些工具會在內核和模塊編譯過程當中被使用(好比連接器、編譯器、來自binutils的二進制工具包,等等):
# 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 AWK = awk GENKSYMS = scripts/genksyms/genksyms INSTALLKERNEL := installkernel DEPMOD = /sbin/depmod PERL = perl PYTHON = python CHECK = sparse CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \ -Wbitwise -Wno-return-void $(CF) CFLAGS_MODULE = AFLAGS_MODULE = LDFLAGS_MODULE = CFLAGS_KERNEL = AFLAGS_KERNEL = CFLAGS_GCOV = -fprofile-arcs -ftest-coverage
在這些定義好的變量後面,咱們又定義了兩個變量:USERINCLUDE和LINUXINCLUDE。他們包含了頭文件的路徑(第一個是給用戶用的,第二個是給內核用的),使用cat Makefile | head -n 387 | tail -n +369
查看
# Use USERINCLUDE when you must reference the UAPI directories only.
USERINCLUDE := \
-I$(srctree)/arch/$(hdr-arch)/include/uapi \ -Iarch/$(hdr-arch)/include/generated/uapi \ -I$(srctree)/include/uapi \ -Iinclude/generated/uapi \ -include $(srctree)/include/linux/kconfig.h # Use LINUXINCLUDE when you must reference the include/ directory. # Needed to be compatible with the O= option LINUXINCLUDE := \ -I$(srctree)/arch/$(hdr-arch)/include \ -Iarch/$(hdr-arch)/include/generated/uapi \ -Iarch/$(hdr-arch)/include/generated \ $(if $(KBUILD_SRC), -I$(srctree)/include) \ -Iinclude \ $(USERINCLUDE)
以及給C編譯器的標準標誌,使用cat Makefile | head -n 419 | tail -n +387
查看
KBUILD_CPPFLAGS := -D__KERNEL__ KBUILD_CFLAGS := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \ -fno-strict-aliasing -fno-common \ -Werror-implicit-function-declaration \ -Wno-format-security \ -std=gnu89 KBUILD_AFLAGS_KERNEL := KBUILD_CFLAGS_KERNEL := KBUILD_AFLAGS := -D__ASSEMBLY__ KBUILD_AFLAGS_MODULE := -DMODULE KBUILD_CFLAGS_MODULE := -DMODULE KBUILD_LDFLAGS_MODULE := -T $(srctree)/scripts/module-common.lds # Read KERNELRELEASE from include/config/kernel.release (if it exists) KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null) KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION) export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE KERNELVERSION export ARCH SRCARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC export CPP AR NM STRIP OBJCOPY OBJDUMP export MAKE AWK GENKSYMS INSTALLKERNEL PERL PYTHON UTS_MACHINE export HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE CFLAGS_GCOV CFLAGS_KASAN export KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULE export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_LDFLAGS_MODULE export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL export KBUILD_ARFLAGS
這並非最終肯定的編譯器標誌,它們還能夠在其餘makefile裏面更新(好比arch/裏面的kbuild)。變量定義完以後,所有會被導出供其餘makefile使用。
下面的兩個變量RCS_FIND_IGNORE和RCS_TAR_IGNORE包含了被版本控制系統忽略的文件,使用cat Makefile | head -n 432 | tail -n +419
查看
# When compiling out-of-tree modules, put MODVERDIR in the module # tree rather than in the kernel tree. The kernel tree might # even be read-only. export MODVERDIR := $(if $(KBUILD_EXTMOD),$(firstword $(KBUILD_EXTMOD))/).tmp_versions # Files to ignore in find ... statements export RCS_FIND_IGNORE := \( -name SCCS -o -name BitKeeper -o -name .svn -o \ -name CVS -o -name .pc -o -name .hg -o -name .git \) \ -prune -o export RCS_TAR_IGNORE := --exclude SCCS --exclude BitKeeper --exclude .svn \ --exclude CVS --exclude .pc --exclude .hg --exclude .git
而後後面的一大塊內容負責根據各類配置文件(make*.config)生成不一樣目標內核的
可使用cat Makefile | head -n 593 | tail -n +432
進行查看,內容較多,咱們在這裏就不一一列舉了。
下面讓我麼直接進入make構建的過程。
如今咱們已經完成了全部的配置工做,根makefile的下一步工做就是和編譯內核相關的了。
在這以前,咱們不會在終端看到make命令輸出的任何東西。
可是如今編譯的第一步開始了,好吧,咱們知道make後,最終的結果叫vmlinux,那咱們就找找這個神奇的東西是怎麼產生的吧。
這裏咱們須要從內核根makefile的594行開始,這裏能夠看到目標vmlinux的構建命令
使用 cat Makefile | head -n 606 | tail -n +594
查看
# The all: target is the default when no target is given on the # command line. # This allow a user to issue only 'make' to build a kernel including modules # Defaults to vmlinux, but the arch makefile usually adds further targets all: vmlinux # The arch Makefile can set ARCH_{CPP,A,C}FLAGS to override the default # values of the respective KBUILD_* variables ARCH_CPPFLAGS := ARCH_AFLAGS := ARCH_CFLAGS := include arch/$(SRCARCH)/Makefile
目標all:是在命令行若是不指定具體目標時默認使用的目標。
你能夠看到這裏包含了架構相關的makefile(在這裏就指的是arch/x86/Makefile)。從這一時刻起,咱們會從這個makefile繼續進行下去。
如咱們所見,目標all依賴於根makefile後面聲明的vmlinux,咱們可使用cat Makefile | head -n 922 | tail -n +920
來查看
# Include targets which we want to # execute if the rest of the kernel build went well. vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORC
vmlinux是linux內核的靜態連接可執行文件格式。腳本scripts/link-vmlinux.sh把不一樣的編譯好的子模塊連接到一塊兒造成了vmlinux。
同時咱們能夠發現vlinux依賴因而vmlinux-deps,咱們查找一下它cat -n Makefile | grep vmlinux-deps
發現它定義在914行,內容以下
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
它是由內核代碼下的每一個頂級目錄的built-in.o組成的。
以後咱們還會檢查內核全部的目錄,kbuild會編譯各個目錄下全部的對應$(obj-y)
的源文件。接着調用$(LD)-r
把這些文件合併到一個build-in.o
文件裏。固然此時咱們尚未vmlinux-deps
,因此目標vmlinux如今還不會被構建。對我而言vmlinux-deps包含下面的文件:
arch/x86/kernel/vmlinux.lds arch/x86/kernel/head_64.o arch/x86/kernel/head64.o arch/x86/kernel/head.o init/built-in.o usr/built-in.o arch/x86/built-in.o kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o lib/lib.a arch/x86/lib/lib.a lib/built-in.o arch/x86/lib/built-in.o drivers/built-in.o sound/built-in.o firmware/built-in.o arch/x86/pci/built-in.o arch/x86/power/built-in.o arch/x86/video/built-in.o net/built-in.o
內核中有這麼多目錄,Makefile是怎麼知道這些目錄的呢,讓咱們繼續往下看,使用cat -n Makefile | head -n 940 | tail -n +936
查看
# The actual objects are generated when descending, # make sure no implicit rule kicks in $(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
咱們會發現vmlinux-deps
是基於vmlinux-dirs
繼續往下,使用cat Makefile | head -n 950 | tail -n +940
查看
# Handle descending into subdirectories listed in $(vmlinux-dirs) # Preset locale variables to speed up the build process. Limit locale # tweaks to this spot to avoid wrong language settings when running # make menuconfig etc. # Error messages still appears in the original language PHONY += $(vmlinux-dirs) $(vmlinux-dirs): prepare scripts $(Q)$(MAKE) $(build)=$@
就像咱們看到的,vmlinux-dir依賴於兩部分:prepare和scripts。
第一個prepare定義在內核的根makefile中,準備工做分紅三個階段。
咱們繼續往下看,使用 cat -n Makefile | head -n 996 | tail -n +959
# Things we need to do before we recursively start building the kernel # or the modules are listed in "prepare". # A multi level approach is used. prepareN is processed before prepareN-1. # archprepare is used in arch Makefiles and when processed asm symlink, # version.h and scripts_basic is processed / created. # Listed in dependency order PHONY += prepare archprepare prepare0 prepare1 prepare2 prepare3 # prepare3 is used to check if we are building in a separate output directory, # and if so do: # 1) Check that make has not been executed in the kernel src $(srctree) prepare3: include/config/kernel.release ifneq ($(KBUILD_SRC),) @$(kecho) ' Using $(srctree) as source for kernel' $(Q)if [ -f $(srctree)/.config -o -d $(srctree)/include/config ]; then \ echo >&2 " $(srctree) is not clean, please run 'make mrproper'"; \ echo >&2 " in the '$(srctree)' directory.";\ /bin/false; \ fi; endif # prepare2 creates a makefile if using a separate output directory prepare2: prepare3 outputmakefile asm-generic prepare1: prepare2 $(version_h) include/generated/utsrelease.h \ include/config/auto.conf $(cmd_crmodverdir) archprepare: archheaders archscripts prepare1 scripts_basic prepare0: archprepare FORCE $(Q)$(MAKE) $(build)=. # All the preparing.. prepare: prepare0
第一個prepare0展開到archprepare,後者又展開到archheader和archscripts,這兩個變量定義在對應架構目錄下的Makefile,x86架構就是arch/x86讓咱們看看這個文件。
x86特定的makefile從變量定義開始,這些變量都是和特定架構的配置文件(defconfig,等等)有關聯。在定義了編譯16-bit代碼的編譯選項以後,根據變量BITS的值,若是是32,彙編代碼、連接器、以及其它不少東西(所有的定義均可以在arch/x86/Makefile找到)對應的參數就是i386,而64就對應的是x86_84。
首先是archheaders
archheaders: $(Q)$(MAKE) $(build)=arch/x86/entry/syscalls all
接着是archscripts
archscripts: scripts_basic $(Q)$(MAKE) $(build)=arch/x86/tools relocs
而後是scripts_basic
經過查找發現咱們能夠看到archscripts是依賴於根Makefile裏的scripts_basic。
使用cat Makefile | head -n 441 | tail -n +432
查看
#==================================================== # Rules shared between *config targets and build targets # Basic helpers built in scripts/ PHONY += scripts_basic scripts_basic: $(Q)$(MAKE) $(build)=scripts/basic $(Q)rm -f .tmp_quiet_recordmcount
首先咱們能夠看出scripts_basic是按照scripts/basic的makefile執行make的
下面咱們看看scripts/basic下的makefile都有什麼
scripts/basic/Makefile包含了編譯兩個主機程序fixdep和bin2的目標
第一個工具是fixdep:
用來優化gcc生成的依賴列表,而後在從新編譯源文件的時候告訴make。
第二個工具是bin2c,
它依賴於內核配置選項CONFIG_BUILD_BIN2C,而且它是一個用來將標準輸入接口(LCTT譯註:即stdin)收到的二進制流經過標準輸出接口(即:stdout)轉換成C頭文件的很是小的C程序。你可能注意到這裏有些奇怪的標誌,如hostprogs-y等。這個標誌用於全部的kbuild文件,更多的信息你能夠從documentation得到。
在咱們這裏,hostprogs-y告訴kbuild這裏有個名爲fixed的程序,這個程序會經過和Makefile相同目錄的fixdep.c編譯而來。
咱們make時執行make以後,終端的第一個輸出就是kbuild的結果:
如今scripts_basic
的工做完成了,如今archscripts
開始工做了,從新回到archscripts
的地方,
$(Q)$(MAKE) $(build)=arch/x86/tools relocs
當目標script_basic被執行,目標archscripts
就會make arch/x86/tools
下的makefile
和目標relocs
包含了重定位的信息的代碼relocs_32.c和relocs_64.c將會被編譯,這能夠在make的輸出中看到,下面仍然是make的工做
下面咱們繼續接着進行make
,咱們發如今編譯完relocs.c以後會檢查version.h
使用 cat Makefile | head -n 1021 | tail -n +1017
查看
$(version_h): $(srctree)/Makefile FORCE $(call filechk,version.h) $(Q)rm -f $(old_version_h)
以及在內核的根Makefiel使用arch/x86/include/generated/asm的目標asm-generic來構建generic彙編頭文件。
在目標asm-generic以後,archprepare就完成了,因此目標prepare0會接着被執行,如我上面所寫:
prepare0: archprepare FORCE $(Q)$(MAKE) $(build)=.
注意build,它是定義在文件scripts/Kbuild.include,內容是這樣的:
腳本scripts/Makefile.build經過參數obj給定的目錄找到Kbuild文件,而後引入kbuild文件
include $(kbuild-file)
並根據這個構建目標。咱們這裏.包含了生成kernel/bounds.s和arch/x86/kernel/asm-offsets.s的Kbuild文件。在此以後,目標prepare就完成了它的工做。
vmlinux-dirs也依賴於第二個目標scripts,它會編譯接下來的幾個程序:filealias,mk_elfconfig,modpost等等。
與prepare相似,因此咱們在這裏就不細講了。
以後,scripts/host-programs就能夠開始編譯咱們的目標vmlinux-dirs了。
首先,咱們先來理解一下vmlinux-dirs都包含了那些東西。在咱們的例子中它包含了下列內核目錄的路徑
咱們能夠在內核的根Makefile裏找到vmlinux-dirs的定義:
使用cat -n Makefile | head -n 905 | tail -n +891
查看vmlinux-dirs的定義
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-alldirs := $(sort $(vmlinux-dirs) $(patsubst %/,%,$(filter %/, \ $(init-) $(core-) $(drivers-) $(net-) $(libs-)))) 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)
前面咱們已經知道vmlinux-dir會依賴與prepare和scripts
$(vmlinux-dirs): prepare scripts $(Q)$(MAKE) $(build)=$@
符號$@在這裏表明了vmlinux-dirs,這就代表程序會遞歸遍歷從vmlinux-dirs以及它內部的所有目錄(依賴於配置),而且在對應的目錄下執行make命令。咱們能夠在輸出看到結果
在make的最後階段,當全部的目錄編譯結束後,每一個目錄下的源代碼將會被編譯而且連接到built-io.o裏。
在最後的連接過程當中,咱們能夠看到,幾乎全部的依賴條件中,都會生成一個built-in.o的文件。 那這個文件,是怎麼生成的呢?
那麼問題來了,makefile是怎麼把內核目錄中編譯生成的build-in.o連接在一塊兒生成vmlinux的呢?
如今咱們回到目標vmlinux上。你應該還記得,目標vmlinux是在內核的根makefile裏。在連接vmlinux以前,系統會構建samples,Documentation等等。
接着咱們使用cat -n Makefile | head -n 936 | tail -n +922
查看
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE ifdef CONFIG_HEADERS_CHECK $(Q)$(MAKE) -f $(srctree)/Makefile headers_check endif ifdef CONFIG_SAMPLES $(Q)$(MAKE) $(build)=samples endif ifdef CONFIG_BUILD_DOCSRC $(Q)$(MAKE) $(build)=Documentation endif ifdef CONFIG_GDB_SCRIPTS $(Q)ln -fsn `cd $(srctree) && /bin/pwd`/scripts/gdb/vmlinux-gdb.py endif +$(call if_changed,link-vmlinux)
咱們能夠看到vmlinux依賴於$(vmlinux-deps)
可是還須要一個shell腳本scripts/link-vmlinux.sh
這個腳本是用來幹嗎的,不急咱們慢慢來。
咱們直接看最後使用+$(call if_changed,link-vmlinux)
,真相正在一步步浮出水面。
咱們查看一下這個命令
這個命令是cmd_link-vmlinux
,就定義在主Makefile中第917行
cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)
(C
< 表示第一個以來目標,那麼在vmlinux目標中,第一個目標是 scripts/link-vmlinux.sh
那麼這個命令展開就成爲/bin/bash scripts/link-vmlinux.sh ld -m elf_i386 --emit-relocs --build-id
如今明晰了在這裏調用腳本scripts/link-vmlinux.sh的,把全部的built-in.o連接成一個靜態可執行文件vmlinux,和生成System.map。
那麼link-vmlinux.sh是怎麼作到得呢,使用該cat -n link-vmlinux.sh | head -n 239 | tail -n +229
查看這個腳本的信息
info LD vmlinux
vmlinux_link "${kallsymso}" vmlinux if [ -n "${CONFIG_BUILDTIME_EXTABLE_SORT}" ]; then info SORTEX vmlinux sortextable vmlinux fi info SYSMAP System.map mksysmap vmlinux System.map
使用了腳本中vmlinux_link這個函數來生成vmlinux,使用mksysmap生成System.map
下面是vmlinux_link
函數的定義,cat -n link-vmlinux.sh | head -n 69 | tail -n +51
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 ${CC} ${CFLAGS_vmlinux} -o ${2} \ -Wl,-T,${lds} ${KBUILD_VMLINUX_INIT} \ -Wl,--start-group \ ${KBUILD_VMLINUX_MAIN} \ -Wl,--end-group \ -lutil ${1} rm -f linux fi }
而後是mksysmap,使用cat -n link-vmlinux.sh | head -n 107 | tail -n +103
查看
mksysmap() { ${CONFIG_SHELL} "${srctree}/scripts/mksysmap" ${1} ${2} }
最後咱們來看看下面的輸出:
vmlinux和System.map生成在內核源碼樹根目錄下。
這就是所有了,vmlinux構建好了,下一步就是建立bzImage.
bzImage就是壓縮了的linux內核鏡像。咱們能夠在構建了vmlinux以後經過執行makebzImage得到bzImage。同時咱們能夠僅僅執行make而不帶任何參數也能夠生成bzImage,由於它是在arch/x86/kernel/Makefile裏預約義的、默認生成的鏡像。
咱們在makefile中查找一下
咱們能夠看到bzImage是依賴於vmlinux生成的,
咱們使用cat -n Makefile | head -n 237 | tail -n +215
查看其構建信息
#### # boot loader support. Several targets are kept for legacy purposes boot := arch/x86/boot BOOT_TARGETS = bzlilo bzdisk fdimage fdimage144 fdimage288 isoimage PHONY += bzImage $(BOOT_TARGETS) # Default kernel to build all: bzImage # 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/$@
在這裏咱們能夠看到第一次爲$(boot)==arch/x86/boot
目錄執行了make
操做
咱們進入這個目錄看看。這個makefile是如何工做生成bzImage的
咱們會發現bzImage依賴於setup.bin和vmlinux.bin
使用cat -n Makefile | head -n 112 | tail -n +107
咱們能夠查看到
$(obj)/setup.bin: $(obj)/setup.elf FORCE $(call if_changed,objcopy) $(obj)/compressed/vmlinux: FORCE $(Q)$(MAKE) $(build)=$(obj)/compressed $@
那麼咱們如今的主要目標是編譯目錄arch/x86/boot和arch/x86/boot/compressed的代碼,構建setup.bin和vmlinux.bin,最後用這兩個文件生成bzImage。
第一個目標是定義在arch/x86/boot/Makefile的$(obj)/setup.elf:
咱們接着使用cat -n Makefile | head -n 105 | tail -n +103
查看如何生成setup.elf
$(obj)/setup.elf: $(src)/setup.ld $(SETUP_OBJS) FORCE $(call if_changed,ld)
經過setup.ld來檢列全部的setup_objs的目標文件來生成setup.elf
下一個源碼文件是arch/x86/boot/header.S
,這個是一個彙編文件
可是咱們不能如今就編譯它,由於這個目標依賴於下面兩個頭文件:
$(obj)/header.o: $(obj)/voffset.h $(obj)/zoffset.h
第一個頭文件voffset.h是使用sed腳本生成的
包含用nm工具從vmlinux獲取的兩個地址:
#define VO__end 0xffffffff82ab0000 #define VO__text 0xffffffff81000000
這兩個地址是內核的起始和結束地址。
第二個頭文件zoffset.h在arch/x86/boot/compressed/Makefile能夠看出是依賴於目標vmlinux的
$(obj)/zoffset.h: $(obj)/compressed/vmlinux FORCE $(call if_changed,zoffset)
而後編譯目錄arch/x86/boot/compressed下的源代碼,而後生成vmlinux.bin、vmlinux.bin.bz2,和編譯工具mkpiggy。
vmlinux.bin是去掉了調試信息和註釋的vmlinux二進制文件,加上了佔用了u32(LCTT譯註:即4-Byte)的長度信息的vmlinux.bin.all壓縮後就是vmlinux.bin.bz2。其中vmlinux.bin.all包含了vmlinux.bin和vmlinux.relocs(LCTT譯註:vmlinux的重定位信息),其中vmlinux.relocs是vmlinux通過程序relocs處理以後的vmlinux鏡像(見上文所述)。
咱們如今已經獲取到了這些文件,彙編文件piggy.S將會被mkpiggy生成、而後編譯:
MKPIGGY arch/x86/boot/compressed/piggy.S AS arch/x86/boot/compressed/piggy.o
這個彙編文件會包含通過計算得來的、壓縮內核的偏移信息。處理完這個彙編文件,咱們就能夠看到zoffset生成了:
ZOFFSET arch/x86/boot/zoffset.h
如今zoffset.h和voffset.h已經生成了,arch/x86/boot裏的源文件能夠繼續編譯,直到
全部的源代碼會被編譯,他們最終會被連接到setup.elf
ld -m elf_x86_64 -T arch/x86/boot/setup.ld arch/x86/boot/a20.o arch/x86/boot/bioscall.o arch/x86/boot/cmdline.o arch/x86/boot/copy.o arch/x86/boot/cpu.o arch/x86/boot/cpuflags.o arch/x86/boot/cpucheck.o arch/x86/boot/early_serial_console.o arch/x86/boot/edd.o arch/x86/boot/header.o arch/x86/boot/main.o arch/x86/boot/mca.o arch/x86/boot/memory.o arch/x86/boot/pm.o arch/x86/boot/pmjump.o arch/x86/boot/printf.o arch/x86/boot/regs.o arch/x86/boot/string.o arch/x86/boot/tty.o arch/x86/boot/video.o arch/x86/boot/video-mode.o arch/x86/boot/version.o arch/x86/boot/video-vga.o arch/x86/boot/video-vesa.o arch/x86/boot/video-bios.o -o arch/x86/boot/setup.elf
最後的兩件事是建立包含目錄arch/x86/boot/*下的編譯過的代碼的setup.bin:
objcopy -O binary arch/x86/boot/setup.elf arch/x86/boot/setup.bin
以及從vmlinux生成vmlinux.bin:
objcopy -O binary -R .note -R .comment -S arch/x86/boot/compressed/vmlinux arch/x86/boot/vmlinux.bin
最後,咱們編譯主機程序arch/x86/boot/tools/build.c,它將會用來把setup.bin和vmlinux.bin打包成bzImage:
arch/x86/boot/tools/build arch/x86/boot/setup.bin arch/x86/boot/vmlinux.bin arch/x86/boot/zoffset.h arch/x86/boot/bzImage
實際上bzImage就是把setup.bin和vmlinux.bin鏈接到一塊兒。最終咱們會看到輸出結果,就和那些用源碼編譯過內核的同行的結果同樣: