關注”技術簡說“,帶你由淺入深學習linux內核源碼。linux內核開發100講免費教程,每天晚上九點準時更新,敬請收看。進我主頁點”視頻“欄目即可觀看。
在之前的課程裡,當我們在編譯linux內核源碼的時候,不知道大家會不會有一些疑問:
1.linux內核源碼那麼多(大概800M),編譯的時候它(編譯系統)怎麼知道應該要編譯哪些文件呢?
2.怎樣保證源碼的編譯順序?比如,先編譯A模塊,再編譯B模塊?
3.怎麼樣把這些編譯出來的一個一個的目標文件,最終形成一個內核鏡像文件?
所有這些,都是通過Makefile來完成的。
本文所用內核源碼為 linux-4.9.229,ARCH=x86
linux內核源碼裡的Makefile分為三層:
- 頂層Makefile(源碼根目錄下)
- 體系相關的Makefile(arch/$(ARCH)/Makefile)
- 各級子目錄下面的Makefile。
那我們就先來看頂層Makefile,先摘幾段關鍵代碼:
<code> 262 SRCARCH :=$(ARCH)
... 546include
arch/$(SRCARCH)
/Makefile/<code>
這樣體系相關的Makefile就被include進來了。
而下面的代碼則間接的把各級子目錄下面的Makefile包含進來了。
<code>570
init-y := init/571
drivers-y := drivers/ sound/ firmware/572
net-y := net/573
libs-y := lib/574
core-y := usr/575
virt-y := virt//<code>
這樣頂層的Makefile就把體系相關的Makefile和各個子目錄的Makefile都包含進來了。
那接下來我們深入到某個具體的子目錄的Makefile,看它又是如何編譯這個子目錄下面的源代碼的呢?
具體到某個源碼目錄中,編譯系統是通過obj-y, obj-m,obj-n和lib-m來組織的。
- obj-y用來定義哪些源文件被編進內核。在當前目錄,這些被編譯到內核裡的.o文件,會被鏈接成一個built.o文件。大家可以看看,等內核編譯完成後,是不是每個子目錄下,都有一個built-in.o文件呢?
- obj-m用來定義哪些文件被編譯成可加載模塊,也就是驅動文件。
- obj-n表示當前源文件既不被編譯到內核,也不會編譯成驅動,也就是不做處理。
- lib-y用來定義哪些文件被編譯成庫文件。
- obj-y, obj-m,lib-y,lib-m還可以指定要包含的下一級目錄。這樣層層包含,保證能夠編譯到所有的內核源代碼。
我以drivers/tty子目錄為例,其Makefile的摘要如下:
<code>obj-$(CONFIG_TTY) += tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o \ tty_buffer.o tty_port.o tty_mutex.o tty_ldsem.o obj-$(CONFIG_HVC_DRIVER) += hvc//<code>
以上表示:
如果通過make menuconfig配置了CONFIG_TTY為y,那麼 tty_io.c n_tty.c tty_ioctl.c tty_ldisc.c
tty_buffer.c tty_port.c tty_mutex.c tty_ldsem.c 這些c文件就會被編譯到內核鏡像裡;
如果通過make menuconfig配置了CONFIG_HVC_DRIVER,那麼還會進入到hvc子目錄進一步執行它的Makefile。
所以,當某個子目錄下的makefile執行一遍以後,所有的需要編譯到內核裡的.o文件就都羅列在obj-y這個變量裡了,而所有需要編譯成驅動的.o文件就都羅列在obj-m裡了。
另外,某個子目錄下obj-y變量裡的所有 .o文件會被鏈接成built-in.o文件,並最終被鏈接到內核鏡像文件。
下面代碼是drivers/tty/built-in.o 生成的過程。
<code>ld -m elf_x86_64 -zmax
-page-size=0x200000
-r -o drivers/tty/built-in
.o drivers/tty/tty_io.o drivers/tty/n_tty.o drivers/tty/tty_ioctl.o drivers/tty/tty_ldisc.o drivers/tty/tty_buffer.o drivers/tty/tty_port.o drivers/tty/tty_mutex.o drivers/tty/tty_ldsem.o drivers/tty/pty.o drivers/tty/tty_audit.o drivers/tty/sysrq.o drivers/tty/vt/built-in
.o drivers/tty/serial/built-in
.o drivers/tty/ipwireless/built-in
.o /<code>
我們來看看內核鏡像是如何生成的。
還是看頂層的Makefile:
<code>core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ vmlinux-dirs:
= $(patsubst%/,%,$(filter %/
, $(init-y) $(init-m) \ $(core-y) $(core-m) $(drivers-y) $(drivers-m) \ $(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y))) vmlinux-alldirs:
= $(sort $(vmlinux-dirs) $(patsubst%/,%,$(filter %/
, \ $(init-) $(core-) $(drivers-) $(net-) $(libs-) $(virt-)))) 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) virt-y:
= $(patsubst%/, %/
built-in
.o, $(virt-y)) export KBUILD_ALLDIRS:
= $(sort $(filter-out arch/%,$(vmlinux-alldirs)) arch Documentationinclude
samples>
patsubst和filter是Makefile的內置函數,說明如下:
$(patsubst %/, %/built-in.o, $(init-y))
尋找$(init-y)中符合格式“%/”的字符串,用“%/built-in.o”替換它們。在之前我們有定義init-y的值為init/,所以,經過這麼轉換,init-y的值就變成了init/built-init.o了。其他同理。
而 $(filter pattern..., text)表示返回在“text”中由空格隔開且匹配格式“pattern...”的字符串,去掉不符合格式“pattern...”的字符串。例如:
<code>$(filter
%.c
%.s, foo.c
bar.c
jin.s xin.h) 結果為:foo.c
bar.c
jin.s/<code>
根據以上的分析,最終vmlinux-dirs的值為:
<code>init
usr arch/x86 kernel certs mm fs ipc security crypto block drivers sound firmware \ arch/x86/pci arch/x86/power arch/x86/video arch/x86/ras net lib arch/x86/lib virt/<code>
最終vmlinux-alldirs的值為:
<code>arch/x86/lib arch/x86/math-emu arch/x86/oprofile arch/x86/pci \ arch/x86/power arch/x86/ras arch/x86/video block certs crypto drivers firmware \ fs init ipc kernel lib mm net security sound usr virt
/<code>
linux內核代碼的鏈接腳本為:arch/$(SRCARCH)/kernel/vmlinux.lds。 對於x86,則位於
arch/x86/kernel/vmlinux.lds, 它是由
arch/x86/kernel/vmlinux.lds.S文件生成的。
在編譯內核的時候指定:make V=1,則可以打印出具體的編譯細節,最終編譯出linux內核鏡像的編譯過程為:
<code>ld
-m elf_x86_64 -z max-page-size=0x200000 --build-id -o vmlinux \ -T ./arch/x86/kernel/vmlinux.lds arch/x86/kernel/head_64.o \
arch/x86/kernel/ebda.o \ arch/x86/kernel/platform-quirks.o init/built-in.o \ --start-group usr/built-in.o arch/x86/built-in.o \ kernel/built-in.o certs/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 arch/x86/ras/built-in.o \ net/built-in.o virt/built-in.o --end-group .tmp_kallsyms2.o
/<code>
總結一下,linux內核源碼的編譯過程為:
- 頂層Makefile決定了linux內核源碼各個目錄和子目錄的編譯順序
- make menuconfig通過對內核配置,生成了一系列CONFIG_XXX的配置
- 各層級的Makefile結合這些CONFIG_XXX配置來決定哪些文件被編譯到內核、哪些文件被編譯成驅動
- 頂層Makefile按照鏈接腳本鏈接所有的.o文件並最終打包成一個完整的內核鏡像文件vmlinux。
關注”技術簡說“,帶你由淺入深學習linux內核源碼。linux內核開發100講免費教程,每天晚上九點準時更新,敬請收看。進我主頁點”視頻“欄目即可觀看。