Arm Linux Kernel 構建 情景分析

構建一個內核,一般是先配置,後編譯。這裏以構建 Nexus5 內核爲例,代號爲 hammerhead。

配置

通常做法是以廠商預置的配置爲基礎,根據自己需要進行配置。命令:
make ARCH=arm hammerhead_defconfig

 

 
執行完畢後,"arch/arm/configs/hammerhead_defconfig" 文件會被複制到 ".config" ,作爲默認配置。
然後運行以下命令根據自己需要進行配置:
make ARCH=arm menuconfig

 

編譯

通常,需要生成 zImage 和 內核模塊。如果不指定目標,這兩個都會默認生成。命令:
  1. # CROSS_COMPILE 的值根據自己情況設定
    make ARCH=arm CROSS_COMPILE=arm-linux-androideabi-

     

這條命令做了什麼呢,把 make 輸出到控制檯的信息貼出來(省略中間相似的信息):
複製代碼
make ARCH=arm CROSS_COMPILE=arm-linux-androideabi- CONFIG_DEBUG_SECTION_MISMATCH=y
scripts/kconfig/conf --silentoldconfig Kconfig
WRAP arch/arm/include/generated/asm/auxvec.h
WRAP arch/arm/include/generated/asm/bitsperlong.h
WRAP arch/arm/include/generated/asm/cputime.h
...
WRAP arch/arm/include/generated/asm/siginfo.h
WRAP arch/arm/include/generated/asm/sizes.h
CHK include/linux/version.h
UPD include/linux/version.h
CHK include/generated/utsrelease.h
UPD include/generated/utsrelease.h
Generating include/generated/mach-types.h
CC kernel/bounds.s
GEN include/generated/bounds.h
CC arch/arm/kernel/asm-offsets.s
GEN include/generated/asm-offsets.h
CALL scripts/checksyscalls.sh
HOSTCC scripts/dtc/checks.o
HOSTCC scripts/dtc/data.o
...
HOSTCC scripts/conmakehash
HOSTCC scripts/recordmcount
CC init/main.o
CHK include/generated/compile.h
UPD include/generated/compile.h
CC init/version.o
CC init/do_mounts.o
CC init/do_mounts_rd.o
CC init/do_mounts_initrd.o
LD init/mounts.o
CC init/initramfs.o
CC init/calibrate.o
LD init/built-in.o
...
AR lib/lib.a
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
LD .tmp_vmlinux1
KSYM .tmp_kallsyms1.S
AS .tmp_kallsyms1.o
LD .tmp_vmlinux2
KSYM .tmp_kallsyms2.S
AS .tmp_kallsyms2.o
LD vmlinux
SYSMAP System.map
SYSMAP .tmp_System.map
OBJCOPY arch/arm/boot/Image
Kernel: arch/arm/boot/Image is ready
AS arch/arm/boot/compressed/head.o
GZIP arch/arm/boot/compressed/piggy.gzip
AS arch/arm/boot/compressed/piggy.gzip.o
CC arch/arm/boot/compressed/misc.o
CC arch/arm/boot/compressed/decompress.o
CC arch/arm/boot/compressed/string.o
AS arch/arm/boot/compressed/lib1funcs.o
AS arch/arm/boot/compressed/ashldi3.o
LD arch/arm/boot/compressed/vmlinux
OBJCOPY arch/arm/boot/zImage
Kernel: arch/arm/boot/zImage is ready
DTC arch/arm/boot/msm8974-hammerhead-rev-11.dtb
DTC arch/arm/boot/msm8974-hammerhead-rev-11j.dtb
DTC arch/arm/boot/msm8974-hammerhead-rev-10.dtb
DTC arch/arm/boot/msm8974-hammerhead-rev-c.dtb
DTC arch/arm/boot/msm8974-hammerhead-rev-b.dtb
DTC arch/arm/boot/msm8974-hammerhead-rev-bn.dtb
DTC arch/arm/boot/msm8974-hammerhead-rev-a.dtb
DTC arch/arm/boot/msm8974-hammerhead-rev-f.dtb
CAT arch/arm/boot/zImage-dtb
Kernel: arch/arm/boot/zImage-dtb is ready
make[1]:沒有什麼可以做的爲`arch/arm/boot/dtbs'。
複製代碼

 

 
簡單分析一下,大致做了這麼幾件事情:
  1. 根據配置信息,生成了一些頭文件
  2. 編譯了一些小工具
  3. 根據配置信息,有選擇性地編譯一些源碼,將輸出的 obj 鏈接成對應的 built-in.o
  4. 生成符號表文件
  5. 將所有的 built-in.o 和符號表鏈接成內核 vmlinux
  6. 使用 BOJCOPY 從 vmlinux 生成 Image
  7. 生成壓縮過的內核 arch/arm/boot/compressed/vmlinux
  8. 使用 OBJCOPY 從 壓縮過的內核 vmlinux 生成 zImage
  9. 生成 dtb(device tree blob)
  10.  將 zImage 和 dtb 連接成一個文件:zImage-dtb
而我們最終需要的文件就是 zImage-dtb(注意:這裏沒有生成內核模塊,因爲所有的內核功能都被配置爲 built-in ,編譯進 zImage-dtb 了)。

要點分析

       內核配置和編譯,依靠的是 make 和 kbuild 系統。無論是 make 還是 kbuild,都只是工具,我們並不一定要完全弄清其內部工作原理,只需要熟悉和工作相關的部分即可。
       這裏涉及到的有如下幾點:
  • vmlinux 的構建過程
  • arch/arm/boot/compressed/vmlinux 的構建過程
  • 源碼是如何選擇性地參與內核的構建的
       之所以要分析 vmlinux 和 arch/arm/boot/compressed/vmlinux ,是因爲這個兩個文件是最原始的兩個可執行文件:Image 由 vmlinux 生成;zImage 由 arch/arm/boot/compressed/vmlinux 生成。分析這連個文件的生成,還有助於分析 linux 內核的啓動過程。

基礎

       vmlinux 是 makefile 中的一個目標。makefile 中的規則定義了目標和源碼的關係,命令則定義瞭如何由源碼生成目標,變量起輔助作用。規則、命令和變量是 makefile 的三大要素。理清 makefile 規則中定義的依賴關係是分析構建過程的關鍵。涉及到的幾個重要文件:
Makefile
arch/arm/Makefile
arch/arm/boot/Makefile
arch/arm/mach-msm/Makefile.boot
arch/arm/compressed/Makefile


 

       vmlinux 是一個可執行程序,其鏈接過程必然涉及的鏈接腳本,鏈接腳本是做什麼的?看看 ld 手冊中的描述:
通過 lds 文件,我們至少可以知道一個可執行程序的入口在哪裏。
這裏又要涉及到幾個重要文件:
 
# 對應 vmlinux
arch/arm/kernel/vmlinux.lds


# 對應 /arch/arm/boot/compressed/vmlinux
arch/arm/boot/compressed/vmlinux.lds

 

       vmlinux 是一個可執行程序,由源碼編譯、鏈接而來。那麼是哪些源碼參與了構建過程,又是如何控制這些源碼參與的?後面會分析。
        爲了分析 makefile,這裏借用了 UML 的概念。
        用 包 表示 makefile 文件;用 類 表示 目標和文件;用類間依賴表示目標的依賴;用組合表示變量的定義。
       下面是一個總圖,表明了各個目標之間的依賴關係:
(紅色邊框是可執行程序,藍色邊框是對應的鏈接腳本)
vmlinux 的構建過程

和 lds 文件的關係

依賴鏈:
_all->all->vmlinux->$(vmlinux-lds)=arch/arm/kernel/vmlinux.lds

從 _all 到 all:

PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif

KBUILD_EXTMOD 只有在內核樹外編譯內核模塊的時候纔會定義 M 變量,從而給其賦值,否則爲空,這裏爲空。

從 all 到 vmlinux:
all: vmlinux

 

 
從 vmlinux 到 $(vmlinux-lds):
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE

 

 
$(vmlinux-lds) 定義:
 
vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds

 

和源碼的關係

​依賴鏈:
 
_all->all->vmlinux->$(vmlinux-init)+$(vmlinux-main)

 

看看這個:
 
複製代碼
# vmlinux
# ^
# |
# +-< $(vmlinux-init)
# | +--< init/version.o + more
# |
# +--< $(vmlinux-main)
# | +--< driver/built-in.o mm/built-in.o + more
# |
# +-< kallsyms.o (see description in CONFIG_KALLSYMS section)
複製代碼

 

關鍵部分上面已經列出,這裏再次列出來:
 
vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE

 

那麼 $(vmlinux-init) 連個變量是什麼呢?通過分析,第一次展開後爲:“$(head-y) $(init-y)”。沒有找到 $(head-y),而 $(init-y) 最終展開爲:init/built-in.o。
       到這裏有點眉目了(回頭看看 make 過程輸出的信息,裏面有大量的 built-in.o)。可以說是衆多的 built-in.o構成了vmlinux。所以 vmlinux 和源碼的關係轉變成了 built-in.o 和源碼的關係。
       還是看 make 的輸出信息:
 
複製代碼
CC init/version.o
CC init/do_mounts.o
CC init/do_mounts_rd.o
CC init/do_mounts_initrd.o
LD init/mounts.o
CC init/initramfs.o
CC init/calibrate.o
LD init/built-in.o
複製代碼

 

可以推測:init/built-in.o 是由 init 目錄下的 源碼編譯、鏈接而成。在 init 目錄下發現 Makefile:
 
複製代碼
obj-y := main.o version.o mounts.o
ifneq ($(CONFIG_BLK_DEV_INITRD),y)
obj-y += noinitramfs.o
else
obj-$(CONFIG_BLK_DEV_INITRD)+= initramfs.o
endif
obj-$(CONFIG_GENERIC_CALIBRATE_DELAY)+= calibrate.o

mounts-y := do_mounts.o
mounts-$(CONFIG_BLK_DEV_RAM)+= do_mounts_rd.o
mounts-$(CONFIG_BLK_DEV_INITRD)+= do_mounts_initrd.o
mounts-$(CONFIG_BLK_DEV_MD)+= do_mounts_md.o
複製代碼

 

有內核開發經驗的開發者應該知道,賦值到 obj-y 的目標會被編譯進 vmlinux,至於是如何控制的,推測 kbuild 系統是有參與的,這屬於 make 和 kubild 的內部原理,這裏不分析了,知道有這麼回事,會用就行了。$(CONFIG_BLK_DEV_INITRD) 等變量在 .config(沒錯,就是保存內核配置的文件) 文件中定義:
 
CONFIG_RELAY=y
CONFIG_BLK_DEV_INITRD=y
CONFIG_INITRAMFS_SOURCE=""

 

這裏 vmlinux 和源碼的關係就搞清了,是由 built-in.o 來當中間人的:
 
vmlinux<->built-in.o<->*.c

 

和符號表的關係

略。

arch/arm/boot/comressed/vmlinux 的構建過程

有了分析 vmlinux 的基礎,分析壓縮過的 vmlinux 就容易了。看 規則:
 
$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.$(suffix_y).o \
$(addprefix $(obj)/, $(OBJS)) $(lib1funcs) $(ashldi3) FORCE
@$(check_for_multiple_zreladdr)
$(call if_changed,ld)
@$(check_for_bad_syms)

 

參與壓縮過的 vmlinux 的構建過程的主要有三類文件:
  • 鏈接腳本:arch/arm/boot/compressed/vmlinux.lds
  • 解壓代碼:arch/arm/boot/compressed/ 下的源碼
  • 壓縮的數據:壓縮的 Image(由未經壓縮的 vmlinux 生成)
因爲解壓縮功能和內核開發關係不大,就不具體分析了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章