Linux內核剖析(五)Linux內核的構建過程


參考
一次實驗引發的故事 – kernel build system探索—vmlinux是如何煉成的– kernel makefile

深度探索Linux操作系統:系統構建和原理解析.pdf

問題


在前面的博文中,我們先是為自己的Ubuntu安裝了一套內核源碼樹,然后為了方便進行嵌入式交叉編譯,我們又為arm板子構建了一套源碼樹。
那么現在我們已經知道如何自己的電腦上去構建、安裝一個定制化的Linux內核,但是我們還是要在嘮叨一些。
當你在內核源碼路徑里敲下make時究竟發生什么
這里寫圖片描述

當我們剛剛開始接觸內核代碼時,毫無頭緒,這時候Makefile是往往是我們打開的第一個文件,這個makefile是Linux內核代碼的根makefile,內核構建就始於此處。是的,它的內容很多,但是如果你已經讀過內核源代碼,你就會發現每個包含代碼的目錄都有一個自己的Makefile。當然了,我們不會去描述每個代碼文件是怎么編譯鏈接的,所以我們將只會挑選一些通用的例子來說明問題。而你不會在這里找到構建內核的文檔、如何整潔內核代碼、tags的生成和交叉編譯相關的說明,等等。

我們僅僅將從make開始,使用標准的內核配置文件,一直到生成了內核鏡像bzImage或者zImage結束。
當然在着之前我們需要了解,我們make究竟是要構建一個什么樣的目標,我想這個

構建的目標vmlinux,vmlinuz,bzImage,zImage


對於Linux內核,編譯可以生成不同格式的映像文件,例如:

make zImag make uImage

 

zImage是ARM Linux常用的一種壓縮映像文件,uImage是U-boot專用的映像文件,它是在zImage之前加上一個長度為0x40的“頭”,說明這個映像文件的類型、加載位置、生成時間、大小等信息。換句話說,如果直接從uImage的0x40位置開始執行,zImage和uImage沒有任何區別。另外,Linux2.4內核不支持uImage,Linux2.6內核加入了很多對嵌入式系統的支持,但是uImage的生成也需要設置。

幾種linux內核文件的區別:

1、vmlinux 編譯出來的最原始的內核文件,未壓縮。
2、zImage 是vmlinux經過gzip壓縮后的文件。適用於小內核
3、bzImage bz表示“big zImage”,不是用bzip2壓縮的。兩者的不同之處在於,zImage解壓縮內核到低端內存(第一個640K),bzImage解壓縮內核到高端內存(1M以上)。如果內核比較小,那么采用zImage或bzImage都行,如果比較大應該用bzImage。適用於大內核
4、uImage U-boot專用的映像文件,它是在zImage之前加上一個長度為0x40的tag。
5、vmlinuz 是bzImage/zImage文件的拷貝或指向bzImage/zImage的鏈接。
6、initrd 是“initial ramdisk”的簡寫。一般被用來臨時的引導硬件到實際內核vmlinuz能夠接管並繼續引導的狀態。

vmlinux


vmlinux是未壓縮的內核,是make工作編譯出的原始內核,vmlinuz是vmlinux的壓縮文件。

vmlinux 是ELF文件,即編譯出來的最原始的文件。

zImage, bzImage和vmlinuz


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-x.x.x.img


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 的,哪種類型,加載內存中的哪個位置, 入口點在內存的那個位置以及映象名是什么

uImage文件


  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。

make參數傳遞


  接下來我們會看到很多ifeq條件判斷語句,它們負責檢查傳遞給make的參數。內核的Makefile提供了一個特殊的編譯選項makehelp,這個選項可以生成所有的可用目標和一些能傳給make的有效的命令行參數。
  舉個例子,首先出現的就是-V ,那么make V=1會在構建過程中輸出詳細的編譯信息,第一個ifeq就是檢查傳遞給make的V=n選項。

參數-v在控制構建過程中輸出編譯信息

使用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

然后是-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

 

參數-O


  下一個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)。
  

選項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

選項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

 

設置objtree

緊接着系統檢查了變量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獲取系統架構

接着就是要獲取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

獲得SUBARCH之后就要設置SRCARCHhfr-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

如果沒有設置過代表內核配置文件路徑的變量KCONFIG_CONFIG,下一步系統會設置它,默認情況下就是.config,這個文件是不是很熟悉,它就是我們make menuconfig后的那個.config配置文件,里面寫入我們內核編譯的所有信息
使用cat -n Makefile | head -n 292 | tail -n +289 查看

KCONFIG_CONFIG  ?= .config
export KCONFIG_CONFIG

 

CONFIG_SHELL編譯內核過程中要用到的shell

使用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_編譯的目標

然后我們就看到變量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。

Kbuild

接着下一步操作是引入下面的文件:
使用查看 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,那我們就找找這個神奇的東西是怎么產生的吧。

終極目標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。

vmlinux-deps

同時我們可以發現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

 

vmlinux-dirs


內核中有這么多目錄,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


第一個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

查找archheaders

archheaders: $(Q)$(MAKE) $(build)=arch/x86/entry/syscalls all 

 

archscripts

接着是archscripts

查找archscripts

archscripts: scripts_basic $(Q)$(MAKE) $(build)=arch/x86/tools relocs 

 

scripts_basic

然后是scripts_basic
通過查找發現我們可以看到archscripts是依賴於根Makefile里的scripts_basic。
查找scrpits_basic
查找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

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就完成了它的工作。

scripts


vmlinux-dirs也依賴於第二個目標scripts,它會編譯接下來的幾個程序:filealias,mk_elfconfig,modpost等等。
這里寫圖片描述
與prepare類似,所以我們在這里就不細講了。

開始編譯vmlinux-dirs


之后,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


在最后的鏈接過程中,我們可以看到,幾乎所有的依賴條件中,都會生成一個built-in.o的文件。 那這個文件,是怎么生成的呢?
這里寫圖片描述

生成vmlinux


那么問題來了,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),真相正在一步步浮出水面。

我們查看一下這個命令
查找link-vmlinux

這個命令是cmd_link-vmlinux,就定義在主Makefile中第917行

cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)

 

(CONFIGSHELL)bashshell

< 表示第一個以來目標,那么在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
vmlinux和System.map生成在內核源碼樹根目錄下。

這就是全部了,vmlinux構建好了,下一步就是創建bzImage.
這里寫圖片描述

構建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/$@ 

 

setup.bin


在這里我們可以看到第一次為$(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

vmlinux.bin


下一個源碼文件是arch/x86/boot/header.S,這個是一個匯編文件
這里寫圖片描述

1. 列表內容

但是我們不能現在就編譯它,因為這個目標依賴於下面兩個頭文件:

$(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

 

生成bzImage


最后,我們編譯主機程序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連接到一起。最終我們會看到輸出結果,就和那些用源碼編譯過內核的同行的結果一樣:
aa


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM