2014年11月25日 星期二

[轉] kernel build system探索

vmlinux是如何煉成的-- kernel makefile

引子
kernelmakefile包含的內容還真是多,我就是想看看要是我自己添加一個目錄編譯到內核裡,要怎麼做
就是這麼個不起眼的實驗,引發了一堆的故事

最簡單的例子
添加 一個目錄,叫test, 添加了test.c 和 Makefile
文件內容很簡單,如下。
cat Makefile
#
# 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/
最後 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
恩,不錯,可以了。

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)。 恩,好複雜。
那來看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): prepare scripts
    $(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()
{
    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
}
好吧,原來是調用了這個函數。。。 打出來看看吧。
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

神秘的built-in.o
在最後的鏈接過程中,我們可以看到,幾乎所有的依賴條件中,都會生成built-in.o的文件。 那這個文件,是怎麼生成的呢?
$(vmlinux-dirs): prepare scripts
    $(Q)$(MAKE) $(build)=$@
從這個規則中可以看到vmlinux-dir目標是通過下面make來生成的。展開一下看看。 這buildscripts/Kbuild.include
make -f scripts/Makefile.build obj=$@
對應到test目錄 那就是
make -f scripts/Makefile.build obj=test
這麼看來,就要進scripts/Makefile.build文件了
PHONY := __build
__build:
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \
     $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \
     $(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
恩 原來這個就是這麼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)
恩,基本明白了,就是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"
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啊。
反向依賴 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
看上去是這麼回事兒。

革命尚未成功,同志仍需努力
好了,我要記錄的東西就到這裡了。突然這麼嘎然而止,估計大家一定意猶未盡
但是事實就是這樣,基本kconfig語法大家可以在 Document/kbuild/kconfig-language.txt中找到
就不用我在這裡搬出來照抄了。

kernel模塊之間的關聯又怎能是我這樣的初學者所能理解,不能理解又豈能講得清楚
這裡只是給大家一個入口,讓大家能夠進一步kernel的海洋中探索。話說,授之以魚不如授之以漁嘛
要讓linux kernel更好的發揮作用,讓更多的人參與這個項目,幫助更多的人,還有很長的路要走
革命尚未成功,同志仍需努力。

沒有留言:

張貼留言