你知道 Linux 內核是如何構建的嗎?
(題圖來自:adafruit.com)
編譯內核前的準備
在開始編譯前要進行很多準備工作。最主要的就是找到並配置好配置文件,make
命令要使用到的參數都需要從這些配置文件獲取。現在就讓我們深入內核的根 makefile
吧
內核的根 Makefile
負責構建兩個主要的文件:vmlinux (內核鏡像可執行文件)和模塊文件。內核的 Makefile 從定義如下變數開始:
VERSION = 4
PATCHLEVEL = 2
SUBLEVEL = 0
EXTRAVERSION = -rc3
NAME = Hurr durr I'ma sheep
這些變數決定了當前內核的版本,並且被使用在很多不同的地方,比如同一個 Makefile
中的 KERNELVERSION
:
KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)
接下來我們會看到很多ifeq
條件判斷語句,它們負責檢查傳遞給 make
的參數。內核的 Makefile
提供了一個特殊的編譯選項 make help
,這個選項可以生成所有的可用目標和一些能傳給 make
的有效的命令行參數。舉個例子,make V=1
會在構建過程中輸出詳細的編譯信息,第一個 ifeq
就是檢查傳遞給 make 的 V=n
選項。
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
export quiet Q KBUILD_VERBOSE
如果 V=n
這個選項傳給了 make
,系統就會給變數 KBUILD_VERBOSE
選項附上 V
的值,否則的話KBUILD_VERBOSE
就會為 0
。然後系統會檢查 KBUILD_VERBOSE
的值,以此來決定 quiet
和Q
的值。符號 @
控制命令的輸出,如果它被放在一個命令之前,這條命令的輸出將會是 CC scripts/mod/empty.o
,而不是Compiling .... scripts/mod/empty.o
(LCTT 譯註:CC 在 makefile 中一般都是編譯命令)。在這段最後,系統導出了所有的變數。
下一個 ifeq
語句檢查的是傳遞給 make
的選項 O=/dir
,這個選項允許在指定的目錄 dir
輸出所有的結果文件:
ifeq ($(KBUILD_SRC),)
ifeq ("$(origin O)", "command line")
KBUILD_OUTPUT := $(O)
endif
ifneq ($(KBUILD_OUTPUT),)
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)"))
sub-make: FORCE
$(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR)
-f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS))
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
和 M
:
ifeq ("$(origin C)", "command line")
KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
KBUILD_CHECKSRC = 0
endif
ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif
第一個選項 C
會告訴 makefile
需要使用環境變數 $CHECK
提供的工具來檢查全部 c
代碼,默認情況下會使用sparse。第二個選項 M
會用來編譯外部模塊(本文不做討論)。
系統還會檢查變數 KBUILD_SRC
,如果 KBUILD_SRC
沒有被設置,系統會設置變數 srctree
為.
:
ifeq ($(KBUILD_SRC),)
srctree := .
endif
objtree := .
src := $(srctree)
obj := $(objtree)
export srctree objtree VPATH
這將會告訴 Makefile
內核的源碼樹就在執行 make
命令的目錄,然後要設置 objtree
和其他變數為這個目錄,並且將這些變數導出。接著就是要獲取 SUBARCH
的值,這個變數代表了當前的系統架構(LCTT 譯註:一般都指CPU 架構):
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 得到機器、操作系統和架構的信息。因為我們得到的是 uname
的輸出,所以我們需要做一些處理再賦給變數 SUBARCH
。獲得 SUBARCH
之後就要設置SRCARCH
和 hfr-arch
,SRCARCH
提供了硬體架構相關代碼的目錄,hfr-arch
提供了相關頭文件的目錄:
ifeq ($(ARCH),i386)
SRCARCH := x86
endif
ifeq ($(ARCH),x86_64)
SRCARCH := x86
endif
hdr-arch := $(SRCARCH)
注意:ARCH
是 SUBARCH
的別名。如果沒有設置過代表內核配置文件路徑的變數 KCONFIG_CONFIG
,下一步系統會設置它,默認情況下就是 .config
:
KCONFIG_CONFIG ?= .config
export KCONFIG_CONFIG
以及編譯內核過程中要用到的 shell
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++
的編譯器及相關配置項:
HOSTCC = gcc
HOSTCXX = g++
HOSTCFLAGS = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89
HOSTCXXFLAGS = -O2
接下來會去適配代表編譯器的變數 CC
,那為什麼還要 HOST*
這些變數呢?這是因為 CC
是編譯內核過程中要使用的目標架構的編譯器,但是 HOSTCC
是要被用來編譯一組 host
程序的(下面我們就會看到)。
然後我們就看到變數 KBUILD_MODULES
和 KBUILD_BUILTIN
的定義,這兩個變數決定了我們要編譯什麼東西(內核、模塊或者兩者):
KBUILD_MODULES :=
KBUILD_BUILTIN := 1
ifeq ($(MAKECMDGOALS),modules)
KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1)
endif
在這我們可以看到這些變數的定義,並且,如果們僅僅傳遞了 modules
給 make
,變數 KBUILD_BUILTIN
會依賴於內核配置選項 CONFIG_MODVERSIONS
。
下一步操作是引入下面的文件:
include scripts/Kbuild.include
文件 Kbuild 或者又叫做 Kernel Build System
是一個用來管理構建內核及其模塊的特殊框架。kbuild
文件的語法與 makefile 一樣。文件scripts/Kbuild.include 為 kbuild
系統提供了一些常規的定義。因為我們包含了這個 kbuild
文件,我們可以看到和不同工具關聯的這些變數的定義,這些工具會在內核和模塊編譯過程中被使用(比如鏈接器、編譯器、來自 binutils 的二進位工具包 ,等等):
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
...
...
...
在這些定義好的變數後面,我們又定義了兩個變數:USERINCLUDE
和 LINUXINCLUDE
。他們包含了頭文件的路徑(第一個是給用戶用的,第二個是給內核用的):
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
LINUXINCLUDE :=
-I$(srctree)/arch/$(hdr-arch)/include
...
以及給 C 編譯器的標準標誌:
KBUILD_CFLAGS := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs
-fno-strict-aliasing -fno-common
-Werror-implicit-function-declaration
-Wno-format-security
-std=gnu89
這並不是最終確定的編譯器標誌,它們還可以在其他 makefile 裡面更新(比如 arch/
裡面的 kbuild)。變數定義完之後,全部會被導出供其他 makefile 使用。
下面的兩個變數 RCS_FIND_IGNORE
和 RCS_TAR_IGNORE
包含了被版本控制系統忽略的文件:
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
這就是全部了,我們已經完成了所有的準備工作,下一個點就是如果構建vmlinux
。
直面內核構建
現在我們已經完成了所有的準備工作,根 makefile(註:內核根目錄下的 makefile)的下一步工作就是和編譯內核相關的了。在這之前,我們不會在終端看到 make
命令輸出的任何東西。但是現在編譯的第一步開始了,這裡我們需要從內核根 makefile 的 598 行開始,這裡可以看到目標vmlinux
:
all: vmlinux
include arch/$(SRCARCH)/Makefile
不要操心我們略過的從 export RCS_FIND_IGNORE.....
到 all: vmlinux.....
這一部分 makefile 代碼,他們只是負責根據各種配置文件(make *.config
)生成不同目標內核的,因為之前我就說了這一部分我們只討論構建內核的通用途徑。
目標 all:
是在命令行如果不指定具體目標時默認使用的目標。你可以看到這裡包含了架構相關的 makefile(在這裡就指的是 arch/x86/Makefile)。從這一時刻起,我們會從這個 makefile 繼續進行下去。如我們所見,目標 all
依賴於根 makefile 後面聲明的 vmlinux
:
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
vmlinux
是 linux 內核的靜態鏈接可執行文件格式。腳本 scripts/link-vmlinux.sh 把不同的編譯好的子模塊鏈接到一起形成了 vmlinux。
第二個目標是 vmlinux-deps
,它的定義如下:
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
下一個可以被執行的目標如下:
$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
就像我們看到的,vmlinux-dir
依賴於兩部分:prepare
和 scripts
。第一個 prepare
定義在內核的根 makefile
中,準備工作分成三個階段:
prepare: prepare0
prepare0: archprepare FORCE
$(Q)$(MAKE) $(build)=.
archprepare: archheaders archscripts prepare1 scripts_basic
prepare1: prepare2 $(version_h) include/generated/utsrelease.h
include/config/auto.conf
$(cmd_crmodverdir)
prepare2: prepare3 outputmakefile asm-generic
第一個 prepare0
展開到 archprepare
,後者又展開到 archheader
和 archscripts
,這兩個變數定義在 x86_64
相關的 Makefile。讓我們看看這個文件。x86_64
特定的 makefile 從變數定義開始,這些變數都是和特定架構的配置文件 (defconfig,等等)有關聯。在定義了編譯 16-bit 代碼的編譯選項之後,根據變數 BITS
的值,如果是 32
, 彙編代碼、鏈接器、以及其它很多東西(全部的定義都可以在arch/x86/Makefile找到)對應的參數就是 i386
,而 64
就對應的是 x86_84
。
第一個目標是 makefile 生成的系統調用列表(syscall table)中的 archheaders
:
archheaders:
$(Q)$(MAKE) $(build)=arch/x86/entry/syscalls all
第二個目標是 makefile 里的 archscripts
:
archscripts: scripts_basic
$(Q)$(MAKE) $(build)=arch/x86/tools relocs
我們可以看到 archscripts
是依賴於根 Makefile里的scripts_basic
。首先我們可以看出 scripts_basic
是按照 scripts/basic 的 makefile 執行 make 的:
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
scripts/basic/Makefile
包含了編譯兩個主機程序 fixdep
和 bin2
的目標:
hostprogs-y := fixdep
hostprogs-$(CONFIG_BUILD_BIN2C) += bin2c
always := $(hostprogs-y)
$(addprefix $(obj)/,$(filter-out fixdep,$(always))): $(obj)/fixdep
第一個工具是 fixdep
:用來優化 gcc 生成的依賴列表,然後在重新編譯源文件的時候告訴make。第二個工具是 bin2c
,它依賴於內核配置選項 CONFIG_BUILD_BIN2C
,並且它是一個用來將標準輸入介面(LCTT 譯註:即 stdin)收到的二進位流通過標準輸出介面(即:stdout)轉換成 C 頭文件的非常小的 C 程序。你可能注意到這裡有些奇怪的標誌,如 hostprogs-y
等。這個標誌用於所有的 kbuild
文件,更多的信息你可以從documentation 獲得。在我們這裡, hostprogs-y
告訴 kbuild
這裡有個名為 fixed
的程序,這個程序會通過和 Makefile
相同目錄的 fixdep.c
編譯而來。
執行 make 之後,終端的第一個輸出就是 kbuild
的結果:
$ make
HOSTCC scripts/basic/fixdep
當目標 script_basic
被執行,目標 archscripts
就會 make arch/x86/tools 下的 makefile 和目標 relocs
:
$(Q)$(MAKE) $(build)=arch/x86/tools relocs
包含了重定位 的信息的代碼 relocs_32.c
和 relocs_64.c
將會被編譯,這可以在make
的輸出中看到:
HOSTCC arch/x86/tools/relocs_32.o
HOSTCC arch/x86/tools/relocs_64.o
HOSTCC arch/x86/tools/relocs_common.o
HOSTLD arch/x86/tools/relocs
在編譯完 relocs.c
之後會檢查 version.h
:
$(version_h): $(srctree)/Makefile FORCE
$(call filechk,version.h)
$(Q)rm -f $(old_version_h)
我們可以在輸出看到它:
CHK include/config/kernel.release
以及在內核的根 Makefiel 使用 arch/x86/include/generated/asm
的目標 asm-generic
來構建 generic
彙編頭文件。在目標 asm-generic
之後,archprepare
就完成了,所以目標 prepare0
會接著被執行,如我上面所寫:
prepare0: archprepare FORCE
$(Q)$(MAKE) $(build)=.
注意 build
,它是定義在文件 scripts/Kbuild.include,內容是這樣的:
build := -f $(srctree)/scripts/Makefile.build obj
或者在我們的例子中,它就是當前源碼目錄路徑:.
:
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.build obj=.
腳本 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
等等。之後,scripts/host-programs
就可以開始編譯我們的目標 vmlinux-dirs
了。
首先,我們先來理解一下 vmlinux-dirs
都包含了那些東西。在我們的例子中它包含了下列內核目錄的路徑:
init usr arch/x86 kernel mm fs ipc security crypto block
drivers sound firmware arch/x86/pci arch/x86/power
arch/x86/video net lib arch/x86/lib
我們可以在內核的根 Makefile 里找到 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)))
init-y := init/
drivers-y := drivers/ sound/ firmware/
net-y := net/
libs-y := lib/
...
...
...
這裡我們藉助函數 patsubst
和 filter
去掉了每個目錄路徑里的符號 /
,並且把結果放到 vmlinux-dirs
里。所以我們就有了 vmlinux-dirs
里的目錄列表,以及下面的代碼:
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
符號 $@
在這裡代表了 vmlinux-dirs
,這就表明程序會遞歸遍歷從 vmlinux-dirs
以及它內部的全部目錄(依賴於配置),並且在對應的目錄下執行 make
命令。我們可以在輸出看到結果:
CC init/main.o
CHK include/generated/compile.h
CC init/version.o
CC init/do_mounts.o
...
CC arch/x86/crypto/glue_helper.o
AS arch/x86/crypto/aes-x86_64-asm_64.o
CC arch/x86/crypto/aes_glue.o
...
AS arch/x86/entry/entry_64.o
AS arch/x86/entry/thunk_64.o
CC arch/x86/entry/syscall_64.o
每個目錄下的源代碼將會被編譯並且鏈接到 built-io.o
里:
$ find . -name built-in.o
./arch/x86/crypto/built-in.o
./arch/x86/crypto/sha-mb/built-in.o
./arch/x86/net/built-in.o
./init/built-in.o
./usr/built-in.o
...
...
好了,所有的 built-in.o
都構建完了,現在我們回到目標 vmlinux
上。你應該還記得,目標 vmlinux
是在內核的根makefile 里。在鏈接 vmlinux
之前,系統會構建 samples, Documentation 等等,但是如上文所述,我不會在本文描述這些。
vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
...
...
+$(call if_changed,link-vmlinux)
你可以看到,調用腳本 scripts/link-vmlinux.sh 的主要目的是把所有的 built-in.o
鏈接成一個靜態可執行文件,和生成 System.map。 最後我們來看看下面的輸出:
LINK vmlinux
LD vmlinux.o
MODPOST vmlinux.o
GEN .version
CHK include/generated/compile.h
UPD include/generated/compile.h
CC init/version.o
LD init/built-in.o
KSYM .tmp_kallsyms1.o
KSYM .tmp_kallsyms2.o
LD vmlinux
SORTEX vmlinux
SYSMAP System.map
vmlinux
和System.map
生成在內核源碼樹根目錄下。
$ ls vmlinux System.map
System.map vmlinux
這就是全部了,vmlinux
構建好了,下一步就是創建 bzImage.
製作bzImage
bzImage
就是壓縮了的 linux 內核鏡像。我們可以在構建了 vmlinux
之後通過執行 make bzImage
獲得bzImage
。同時我們可以僅僅執行 make
而不帶任何參數也可以生成 bzImage
,因為它是在 arch/x86/kernel/Makefile 里預定義的、默認生成的鏡像:
all: bzImage
讓我們看看這個目標,它能幫助我們理解這個鏡像是怎麼構建的。我已經說過了 bzImage
是被定義在 arch/x86/kernel/Makefile,定義如下:
bzImage: vmlinux
$(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 目錄執行 make
,在我們的例子里是這樣的:
boot := arch/x86/boot
現在的主要目標是編譯目錄 arch/x86/boot
和 arch/x86/boot/compressed
的代碼,構建 setup.bin
和 vmlinux.bin
,最後用這兩個文件生成 bzImage
。第一個目標是定義在 arch/x86/boot/Makefile 的 $(obj)/setup.elf
:
$(obj)/setup.elf: $(src)/setup.ld $(SETUP_OBJS) FORCE
$(call if_changed,ld)
我們已經在目錄 arch/x86/boot
有了鏈接腳本 setup.ld
,和擴展到 boot
目錄下全部源代碼的變數 SETUP_OBJS
。我們可以看看第一個輸出:
AS arch/x86/boot/bioscall.o
CC arch/x86/boot/cmdline.o
AS arch/x86/boot/copy.o
HOSTCC arch/x86/boot/mkcpustr
CPUSTR arch/x86/boot/cpustr.h
CC arch/x86/boot/cpu.o
CC arch/x86/boot/cpuflags.o
CC arch/x86/boot/cpucheck.o
CC arch/x86/boot/early_serial_console.o
CC arch/x86/boot/edd.o
下一個源碼文件是 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)
目標 $(obj)/compressed/vmlinux
依賴於 vmlinux-objs-y
—— 說明需要編譯目錄 arch/x86/boot/compressed 下的源代碼,然後生成 vmlinux.bin
、vmlinux.bin.bz2
,和編譯工具 mkpiggy
。我們可以在下面的輸出看出來:
LDS arch/x86/boot/compressed/vmlinux.lds
AS arch/x86/boot/compressed/head_64.o
CC arch/x86/boot/compressed/misc.o
CC arch/x86/boot/compressed/string.o
CC arch/x86/boot/compressed/cmdline.o
OBJCOPY arch/x86/boot/compressed/vmlinux.bin
BZIP2 arch/x86/boot/compressed/vmlinux.bin.bz2
HOSTCC arch/x86/boot/compressed/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 里的源文件可以繼續編譯:
AS arch/x86/boot/header.o
CC arch/x86/boot/main.o
CC arch/x86/boot/mca.o
CC arch/x86/boot/memory.o
CC arch/x86/boot/pm.o
AS arch/x86/boot/pmjump.o
CC arch/x86/boot/printf.o
CC arch/x86/boot/regs.o
CC arch/x86/boot/string.o
CC arch/x86/boot/tty.o
CC arch/x86/boot/video.o
CC arch/x86/boot/video-mode.o
CC arch/x86/boot/video-vga.o
CC arch/x86/boot/video-vesa.o
CC arch/x86/boot/video-bios.o
所有的源代碼會被編譯,他們最終會被鏈接到 setup.elf
:
LD 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
連接到一起。最終我們會看到輸出結果,就和那些用源碼編譯過內核的同行的結果一樣:
Setup is 16268 bytes (padded to 16384 bytes).
System is 4704 kB
CRC 94a88f9a
Kernel: arch/x86/boot/bzImage is ready (#5)
全部結束。
結論
這就是本文的結尾部分。本文我們了解了編譯內核的全部步驟:從執行 make
命令開始,到最後生成 bzImage
。我知道,linux 內核的 makefile 和構建 linux 的過程第一眼看起來可能比較迷惑,但是這並不是很難。希望本文可以幫助你理解構建 linux 內核的整個流程。
鏈接
- GNU make util
- Linux kernel top Makefile
- cross-compilation
- Ctags
- sparse
- bzImage
- uname
- shell
- Kbuild
- binutils
- gcc
- Documentation
- System.map
- Relocation
via: https://github.com/0xAX/linux-insides/blob/master/Misc/how_kernel_compiled.md
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive