arm-linux核心編譯過程小結
記在前面的雜七雜八
- 核心的生成,實際上最終的目的是生成一個binary檔案zImage,大小2-5MB的數量級。
- 使用者可以從kernel.org得到的tar.gz格式的核心原始碼,此程式碼解壓後,就會生成初始狀態的核心原始碼樹,這種狀態稱為核心的初始狀態。
- 通過make mrproper/make distclean等指令,可以使核心恢復到剛解壓的狀態。其中make mrproper只清除包括.config檔案在內的,為核心編譯及連線而生成的諸多配置檔案。distclean物件執行mrproper命令,清除核心編譯後生成的所有物件檔案,備份檔案等。
- 如果將初始化狀態的核心直接編譯,雖然能生成vmlinux,但大多數情況下會引起核心嚴重錯誤(kernel panic)。構建核心前,需要執行的最重要,最需謹慎處理的部分是核心配置(kernel configureation)過程。核心配置過程也是適當選擇與自身相吻合的各種核心要素的過程。
- 核心的配置可以用xconfig,menuconfig,gconfig等,最終都是會執行一個二進位制檔案,如menuconfig最終執行的是mconf,這個程式在./script/kconfig/目錄下。
- 在構建核心時,各個*.o的目錄下都有一個.*.cmd,這個檔案是記錄這個.o最終執行的編譯命令的,如vmlinux.cmd和.vmlinux.o.cmd。
- 一句make一般來說,預設的目標有兩個,一個是vmlinux,一個是zImage
通過emulator啟動goldfish的時候,實際上啟動的是zImage,這貨才2.5MB左右,啟動命令如下:
- 1
- 1
在圖形化介面下,核心的配置也會有很多很多問題,一般每個系統均提供自定義配置檔案,這些配置檔案都是與具體晶片相關的(Soc, System on Chip),如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
核心的構建
- 如想使用goldfish的配置,可以:
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
.config檔案是構建核心所需的核心配置目錄,它是在CONFIG_XXX變數中用y,n,m三個狀態進行配置的目錄,這種形態的核心配置系統叫做kconfig。根據kconfig提供的三個狀態(y,n,m)決定是否構建核心相應的模組(Kconfig系統中y,n,m只是bool型別的配置選項,實際上.config中可能有hex/int/bool/tristate/string這多種型別的選項)。
- 狀態為y時:相應的二進位制檔案,與vmlinux連結。
- 狀態為m時:不會和vmlinux連結,但作為模組執行編譯。
- 狀態為n時:不編譯。
mconf通過.config配置檔案,生成autoconf.h標頭檔案
- 1
- 2
- 3
- 1
- 2
- 3
這兩個檔案是一樣的,也不知道誰複製的誰,大體看一下檔案內容:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
可以發現,在.config中定義的的巨集,在預處理階段被處理成了各種#define語句。
利用kconfig完成核心配置,準備好.config檔案後,即可構建核心。構建核心是指,編譯核心,連結各個二進位制檔案,最終生成一個二進位制檔案zImage的一系列過程。
從linux2.6開始,核心採用kbuild系統來進行編譯了,kbuild是指上是一堆指令碼的組合。
linux核心Makefile分類:
* Kernel Makefile: 位於核心原始碼的頂層目錄,也叫Top Makefile。主要用於指定編譯核心的目標檔案(vmlinux)和模組。在編譯核心或模組時,這個檔案會被首先讀取,並根據內容設定環境變數。
* kbuild Makefile: kbuild系統使用kbuild Makefile來編譯核心或模組,Kbuild Makefile指定哪些編譯近核心,哪些編譯為模組。
* arch Makefile: 位於./arch/$(ARCH)/Makefile,是系統對應平臺的Makefile。top makefile會包含這個檔案來指定平臺相關資訊,只有平臺開發人員需要關心這個檔案。
vmlinux的生成
編譯後,vmlinux是在核心目錄樹的根目錄下生成的一個ELF檔案,這裡以goldfish下執行make為例,檢視vmlinux的生成。 當執行make命令的時候,會先掃描核心的根目錄的Makefile:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
後續會分析其中的各個目標
目標檔案 vmlinux.o
vmlinux.o是沒有去除符號表的可執行檔案,最終生成的vmlinux為真正的核心映象
- 1
- 2
- 1
- 2
目標檔案 vmlinux.lds
vmlinux.lds是一個連結指令碼,是給ld連結器使用的。一般來說,普通程式是不需要指定linker script的,也不需要關心各個section的具體位置。當程式執行時,kernel中的ELF Loader會根據ELF檔案頭解析可執行檔案的各個section,並把他們對映到虛擬地址空間。然而,核心啟動時,必須首先確定各個section的具體位置,這就是vmlinux.lds的作用。這個檔案必然是體系結構相關的,在arm中有兩個連線指令碼分別位於:
./arch/arm/kernel/vmlinux.lds(這個是給vmlinux編譯用的連線指令碼,就是這裡面的vmlinux.lds)
./arch/arm/boot/compressed/vmlinux.lds(這個是給zImage編譯時候用的連線指令碼)
目標檔案 kallsyms.o
在2.6核心中,為了更好的除錯核心,引入了kallsyms機制。kallsyms把核心中用到的所有函式地址和名稱連結到核心檔案,當核心啟動後,同時載入到記憶體中。當發生oops時候,核心就會
解析eip位於哪個函式中,然後打印出backtrace資訊。核心編譯的最後,make會執行:nm -n vmlinux|scripts/kallsyms,其中:
1. nm -n vmlinux負責生成所有的核心符號並按地址排序
2. scripts/kallsyms負責處理這個列表,並生成需要的連結檔案tmp_kallsyms%.s
也就是說kallsyms實際上是核心編譯完了之後,vmlinux中通過nm命令生成的,所以所有符號地址都包括了,實際上是和System.map是一樣的。
而且kallsyms中所有函式的地址,是放在一個全域性陣列kallsyms_addresses[]中的,如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
kallsyms_addresses中的每一個表項,都在重定位表中有記錄,如果核心發生了重定位,那麼kallsyms中的內容也會跟著修改,所以cat /proc/kallsyms的時候總是看到的是真正的函式地址。
kallsyms的整個符號表,最終都會放在kallsyms.o檔案中。
- 1
- 2
- 1
- 2
目標 vmlinux-init
- 1
- 2
- 1
- 2
- head-y
- 1
- 2
- 3
- 1
- 2
- 3
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
- init-y
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
所以最終vmlinux-init 實際上是:
1. arch/arm/kernel/head.o(這是Image/vmlinux的入口程式碼)。
2. arch/arm/kernel/init_task.o
3. init/built-in.o
三者連結而來的。
目標 vmlinux-main
- 1
- 1
- core-y
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7