vmlinux是如何煉成的-- kernel makefile
引子
kernel的makefile包含的內容還真是多,我就是想看看要是我自己添加一個目錄編譯到內核裡,要怎麼做。
就是這麼個不起眼的實驗,引發了一堆的故事。
最簡單的例子
添加 一個目錄,叫test, 添加了test.c 和 Makefile。
文件內容很簡單,如下。
cat Makefile
#
# Makefile for the linux kernel makefile experiment.
#
# Makefile for the linux kernel makefile experiment.
#
obj-y := test.o
cat test.c
#include <linux/export.h>
int test_global = 0;
然後在主 Makefile中 添加
-core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
+core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/ test/
+core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/ test/
最後 make test
make[1]: Nothing to be done for `all'.
HOSTCC arch/x86/tools/relocs
CHK include/linux/version.h
CHK include/generated/utsrelease.h
CC kernel/bounds.s
GEN include/generated/bounds.h
CC arch/x86/kernel/asm-offsets.s
GEN include/generated/asm-offsets.h
CALL scripts/checksyscalls.sh
CC test/test.o
LD test/built-in.o
HOSTCC arch
CHK incl
CHK incl
CC kernel/bounds.s
GEN incl
CC arch
GEN incl
CALL scri
CC test/test.o
LD test/built-in.o
恩,不錯,可以了。
vmlinux是如何煉成的-- kernel makefile
人總是不知足的,我又開始好奇,這個 build的過程究竟是怎麼個回事。
好吧,我們知道 make後,最終的結果叫 vmlinux,那我們就找找這個神奇的東西是怎麼
產生的吧。
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
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
+$(call if_changed,link-vmlinux)
vmlinx 基於上面三個目標, 而vmlinux-deps又基於 $(vmlinux-dirs)。 恩,好複雜。
$(Q)$(MAKE) -f $(srctree)/Makefile headers_check
endif
ifdef CONFIG_SAMPLES
$(Q)$(MAKE) $(build)=samples
endif
ifdef CONFIG_BUILD_DOCSRC
$(Q)$(MAKE) $(bu
endif
+$(call if_c
vmlinx 基於上面三個目標, 而vmlinux-deps又基於 $(vmlinux-dirs)。 恩,好複雜。
那來看看 vmlinux-dirs都包含什麼吧。
在主Makefile中看到下面的內容。
core-y += kernel/ mm/ fs/ ipc/ security/ crypto/ block/ test/
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-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
$(core-y) $(core-m) $(drivers-y) $(drivers-m) \
$(net-y) $(net-m) $(libs-y) $(libs-m)))
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
$(Q)$(MAKE) $(build)=$@
恩, 我們把 vmlinux-dirs的東東打印出來看看。
vmlinux-dris: init usr arch/x86 kernel mm fs ipc security crypto block test drivers sound firmware arch/x86/pci arch/x86/power arch/x86/video arch/x86/oprofile net lib arch/x86/lib
這樣,你是不是明白點了呢。 這些都是 kernel源代碼中子目錄。也就是 kernel將要挨個的
進入每個子目錄,編譯~。
那最後這個 vmlinux是怎麼生成的呢? 怎麼樣將每個目錄下生成的模塊鏈接成一個 vmlinux的文件的呢?
看到上面 vmlinux目標中,最後一個命令:
+$(call if_changed,link-vmlinux)
哦,原來是調用了 cmd_link-vmlinux。這個命令就定義在主 Makefile中。
cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)
打出來看看,長這樣。
/bin/bash $< ld -m elf_i386 --emit-relocs --build-id
$< 表示第一個以來目標,那麼在 vmlinux目標中,第一個目標是 scripts/link-vmlinux.sh, 展開後就成為。
/bin/bash scripts/link-vmlinux.sh ld -m elf_i386 --emit-relocs --build-id
額,原來是又調用了一個腳本。。。 好吧, 再進去看看。 發現這麼個東東
info LD vmlinux
vmlinux_link "${kallsymso}" vmlinux
vmlinux_link "${kallsymso}" vmlinux
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
}
local lds=
if [ "${SRCARCH}" != "um" ]; then
${LD} ${LDFLAGS} ${LDFLAGS_vmlinux} -o ${2}
-T ${lds} ${KB
--start-group ${KB
else
${CC} ${CFLAGS_vmlinux} -o ${2}
-Wl,-T,${lds} ${KB
-Wl
${KB
-Wl
-lutil ${1}
rm -f linux
fi
}
好吧,原來是調用了這個函數。。。 打出來看看吧。
ld -m elf_i386 --emit-relocs --build-id -o vmlinux -T arch/x86/kernel/head_32.o arch/x86/kernel/head32.o arch/x86/kernel/head.o init/built-in.o --start-group 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 test/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 --end-group .tmp_kallsyms2.o
恩,原來真相是這樣的。 最後把這麼多東西鏈接起來成為 vmlinux。 看到我們添加的 test目錄了麼,它也生成了一個 built-in.o,最後鏈接到了 vmlinux中。
$ nm vmlinux | grep test_global
c198d284 B test_global
啊哦,還真有這個symbol!
啊哦,還真有這個symbol!
神秘的built-in.o
在最後的鏈接過程中,我們可以看到,幾乎所有的依賴條件中,都會生成一個 built-in.o的文件。 那這個文件,是怎麼生成的呢?
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@
$(Q)$(MAKE) $(build)=$@
從這個規則中可以看到, vmlinux-dir目標是通過下面的 make來生成的。展開一下看看。 這個 build在scripts/Kbuild.include中。
make -f scripts/Makefile.build obj=$@
對應到test目錄 那就是
make -f scripts/Makefile.build obj=test
這麼看來,就要進到 scripts/Makefile.build文件了。
PHONY := __build
__build:
__build:
__build: $(if $(KBUILD_BUILTIN),$(builtin-target ) $(lib-target) $(extra-y)) \
$(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
$(subdir-ym) $(always)
@:
$(subdir-ym) $(always)
@:
__build是這個 makefile的第一個目標,也是默認的目標。 這裡面藏著一個 builtin-target,看著很像,再搜搜。
ifneq ($(strip $(obj-y) $(obj-m) $(obj-n) $(obj-) $(subdir-m) $(lib-target)),)
builtin-target := $(obj)/built-in.o
endif
endif
恩 原來這個就是這麼多叫 built-in.o的原因。但是要生成 buit-in.o,必須要以上的這些變量不能全部為空。
那再來看看編譯這個 built-in.o的規則是什麼
quiet_cmd_link_o_target = LD $@
# If the list of objects to link is empty, just create an empty built-in.o
cmd_link_o_target = $(if $(strip $(obj-y)),\
$(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \
$(cmd_secanalysis),\
rm -f $@; $(AR) rcs$(KBUILD_ARFLAGS) $@)
$(builtin-target): $(obj-y) FORCE
$(call if_changed,link_o_target)
targets += $(builtin-target)
# If the list of objects to link is empty, just create an empty built-in.o
cmd_link_o_target = $(if $(strip $(obj-y)),\
$(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \
$(cm
rm -f $@; $(AR) rcs$
$(builtin-target): $(obj-y) FORCE
$(call if_c
targets += $(builtin-target)
恩,基本明白了,就是當 obj-y的內容不為空,那麼就用 ld來鏈接成一個 built-in.o。
但是我試了一下,如果沒有 obj-y那麼,也會生成一個 built-in.o,但是用的是別的命令。
如在i386下,用的是
rm -f test/built-in.o; ar rcsD test/built-in.o
不知道這個是什麼高級玩意。
好了,到此為止,基本上一個最上層的框架有了一個概念。 那就先休息一下~
一切盡在掌握 -- kconfig 配置系統
kconfig是個強大的工具,如果說 makefile制定了完美的編譯依賴關系,那麼 kconfig制定了完美的模塊的依賴關係。
源頭
在根目錄下有個 Kconfig文件,這就是一切故事的起源。
整個文件就沒幾行,打出來看一眼。
#
# For a description of the syntax of this configuration file,
# see Documentation/kbuild/kconfig-language.txt.
#
mainmenu "Linux/$ARCH $KERNELVERSION Kernel Configuration"
config SRCARCH
string
option env="SRCARCH"
# see Docu
#
mainmenu "Linux/$ARCH $KERNELVERSION Kernel Configuration"
config SRCARCH
string
option env="SRCARCH"
source "arch/$SRCARCH/Kconfig"
第一行就是輸出個內核版本號。
第二行應該是配置一個環境變量? 不知道,以後再來看。
第三行很重要,這個是包含了一個 arch目錄下的 Kconfig文件。
當你打開
我們運行make menuconfig, 你可以看到,這個文件就是make menuconfig中顯示的東西。
一切都變得明朗起來,你是否有種太極生兩儀,兩儀生四象,四象生八卦的神奇感覺
剪不斷,理還亂
kernel中這麼多的模塊之間的依賴關係,簡直就是 剪不斷,理還亂。
幸好在 Kconfig文件中,我們可以找到一點蛛絲馬跡。
依賴 depends on
這個關鍵字表示了在某些配置選中後,本配置項才會顯示。
在 driver/pci/Kconfig文件中有,
config PCI_MSI
bool "Message Signaled Interrupts (MSI and MSI-X)"
depends on PCI
depends on ARCH_SUPPORTS_MSI
可以發現,要配置 MSI必須要先支持 PCI。 恩這個道理咱都懂, 連PCI都沒有,哪裡來的MSI啊。
bool "Message Signaled Interrupts (MSI and MSI-X)"
depends on PCI
depends on ARCH_SUPPORTS_MSI
可以發現
反向依賴 select
這個關鍵字表示了當本配置項選中後,其他的配置項也需要選中。
在 arch/x86/Kconfig文件中有:
config HIGHMEM64G
bool "64GB"
depends on !M386 && !M486
select X86_PAE
---help---
Select this if you have a 32-bit processor and more than 4
gigabytes of physical RAM.
其實這個我也不懂,看上去就是說要支持更多的物理內存,那麼在 x86的平台上,就要選中 X86_PAE。
bool "64GB"
depends on !M386 && !M486
select X86_PAE
---help---
Select this if you have a 32-bit processor and more than 4
gigabytes of physical RAM.
其實這個
看上去是這麼回事兒。
革命尚未成功,同志仍需努力
好了,我要記錄的東西就到這裡了。突然這麼嘎然而止,估計大家一定意猶未盡。
但是事實就是這樣,基本的 kconfig語法大家可以在 Document/kbuild/kconfig-language.txt中找到。
就不用我在這裡搬出來照抄了。
kernel模塊之間的關聯又怎能是我這樣的初學者所能理解,不能理解又豈能講得清楚。
這裡只是給大家一個入口,讓大家能夠進一步的在 kernel的海洋中探索。話說,授之以魚不如授之以漁嘛。
要讓linux kernel更好的發揮作用,讓更多的人參與這個項目,幫助更多的人,還有很長的路要走。
革命尚未成功,同志仍需努力。