柴米油鹽計劃

Kbuild 編譯 Linux 內核系列(七)

Author: Wei Yang
作者公眾號:楊小偉的世界

有編譯過內核的話,一般都會看到在根目錄下有個文件 vmlinux ,這個就是通常所說的內核了。但是用了這麼久,倒是從來沒看過是怎麼編譯出來的。那今天我們就通過它來試試我們這知識到底有沒有掌握。

1 那些七大姑八大姨們

一切的一切都是 make 讀取 makefile 編譯鏈接的,就好像孫悟空逃不出如來佛祖的手掌, vmlinux 的出世也是在 makefile 的安排之下。那就現在看看 makefile 。

# SHELL used by kbuild
CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
      else if [ -x /bin/bash ]; then echo /bin/bash; \
      else echo sh; fi ; fi)

# Final link of vmlinux
      cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)
quiet_cmd_link-vmlinux = LINK    $@

vmlinux: scripts/link-vmlinux.sh vmlinux_prereq $(vmlinux-deps) FORCE
    +$(call if_changed,link-vmlinux)

第一次看完這一段差點一口老血吐出來,我真是沒有想到他們盡然可以這麼玩。。。還好我們已經經歷過了之前的九九八十一難,見到這些也還能算是氣定神閑了~
if_changed 也是在頭文件 scripts/Kbuild.include 中定義,詳細解釋可以看 if_changed 。這個的用法也是類似之前看到的 if_changed_rule 是一個回調函數,當條件滿足就運行 cmd_link-vmlinux 。 而這個 cmd_link-vmlinux 就是把第一個依賴作為腳本傳給了系統使用的 shell ,由系統 shell 執行。好吧,我也是醉了,不過道行也就這麼又深了一點點。你說是不是。

回到正題,這次我們先看看 vmlinux 相關依賴的都是誰。
vmlinux 一共依賴兩個 一個是 vmlinux_prereq ,另一個是 vmlinux-deps 。那分頭分析。

1.1 vmlinux_prereq

# Include targets which we want to execute sequentially if the rest of the
# kernel build went well. If CONFIG_TRIM_UNUSED_KSYMS is set, this might be
# evaluated more than once.
PHONY += vmlinux_prereq
vmlinux_prereq: $(vmlinux-deps) FORCE
ifdef CONFIG_HEADERS_CHECK
    $(Q)$(MAKE) -f $(srctree)/Makefile headers_check
endif
ifdef CONFIG_BUILD_DOCSRC
    $(Q)$(MAKE) $(build)=Documentation
endif
ifdef CONFIG_GDB_SCRIPTS
    $(Q)ln -fsn `cd $(srctree) &amp;&amp; /bin/pwd`/scripts/gdb/vmlinux-gdb.py
endif
ifdef CONFIG_TRIM_UNUSED_KSYMS
    $(Q)$(CONFIG_SHELL) $(srctree)/scripts/adjust_autoksyms.sh \
      "$(MAKE) KBUILD_MODULES=1 -f $(srctree)/Makefile vmlinux_prereq"
endif

這個看覺和最後的 vmlinux 關係不大, 咱暫時就不看了。

1.2 vmlinux-deps

vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT)   $(KBUILD_VMLINUX_MAIN)

vmlinux-deps 是這幾個變數的集合,這下再次展開,我們一個個來看~

1.2.1 KBUILD_LDS —— 鏈接文件
export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds

這個好說,就是鏈接文件~好多秘密都在這裡,暫且不表,之後再說。

1.2.2 KBUILD_VMLINUX_INIT
export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)

KBUILD_VMLINUX_INIT 又是兩個變數的集合, head-y 和 init-y 。

1.2.2.1 head-y

這個 head-y 定義根據架構不同而不同,比如在 x86 架構下,該定義為:

head-y := arch/x86/kernel/head_$(BITS).o
head-y += arch/x86/kernel/head$(BITS).o
head-y += arch/x86/kernel/head.o

恩,就一個架構,都還有三個文件,好多~忘了說了,這幾個定義在 arch/x86/Makefile 中。那是怎麼被根目錄的 Makefile 引用到的呢?你猜?
在搜索的時候又發現這麼一句在文檔中。

$(head-y) lists objects to be linked first in vmlinux.

這個提醒了我一下,在鏈接的時候,文件出現的順序是有影響的。

1.2.2.2 init-y
init-y      := init/

init-y      := $(patsubst %/, %/built-in.o, $(init-y))

init-y 有兩種情況。

  • 是 init/ 這個目錄
  • 是 init/built-in.o 這個文件。

在不同情況下展現出不同形式。而在 KBUILD_VMLINUX_INIT 這個變數中,保存的是 init/built-in.o 。

1.2.3 KBUILD_VMLINUX_MAIN
export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y) $(virt-y)

這個包含的內容比較多了啊。

#core-y
core-y      := usr/
core-y      += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/
core-y      := $(patsubst %/, %/built-in.o, $(core-y))

#libs-y
libs-y      := lib/
libs-y1     := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2     := $(patsubst %/, %/built-in.o, $(libs-y))
libs-y      := $(libs-y1) $(libs-y2)

#drivers-y
drivers-y   := drivers/ sound/ firmware/
drivers-y   := $(patsubst %/, %/built-in.o, $(drivers-y))

#net-y
net-y       := net/
net-y       := $(patsubst %/, %/built-in.o, $(net-y))

#virt-y
virt-y      := virt/
virt-y      := $(patsubst %/, %/built-in.o, $(virt-y))

這下,內核根目錄下所有的子目錄就都包含了~
除了lib目錄下有兩個文件, built-in.o 和 lib.a ,其餘下都只有一個文件 built-in.o 。到這裡你有沒有感覺眼前一亮?有沒有覺得這個 built-in.o 很眼熟?

1.2.3.1 VMLINUX_MAIN 中目標是如何的編譯

可以看到 VMLINUX_MAIN 中包含的都是內核子系統的根目標了。我們可以猜到最終根目錄下的 vmlinux 就是由這些根目標鏈接而成。那這些子系統的根目標是如何生成的呢?
過程有點繞,我們還是直接來看代碼。

"Makefile"

vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)

# The actual objects are generated when descending,
# make sure no implicit rule kicks in
$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;

凡是在 vmlinux-deps 中的變數,都依賴於 vmlinux-dirs 變數的值。那就去看看 vmlinux-dirs 中的目標是什麼規則咯。

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)))

# Handle descending into subdirectories listed in $(vmlinux-dirs)
# Preset locale variables to speed up the build process. Limit locale
# tweaks to this spot to avoid wrong language settings when running
# make menuconfig etc.
# Error messages still appears in the original language

PHONY += $(vmlinux-dirs)
$(vmlinux-dirs): prepare scripts
    $(Q)$(MAKE) $(build)=$@

vmlinux-dirs 定義為目錄。而他的規則是 $(Q)$(MAKE) $(build)=$@ 。按照在 scripts/Kbuild.include 中的定義,展開後就像下面這樣。

make -f scripts/Makefile.build obj=$@

他的意思就是,各回各家,各找各媽,各自去各自的目錄去執行編譯吧~

2 誰是你們的粘合劑

看到了這麼多內核子系統的根目標,現在該是時候把他們鏈接起來生成 vmlinux 了~
還記得 cmd_link-vmlinux 這個命令么?

cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS)

這是 make 中定義的自動變數,表示依賴關係中的第一個依賴。在 vmlinux 規則中,第一個依賴是 scripts/link-vmlinux.sh 。所以接下來的鏈接工作,就靠它了。

2.1 vmlinux_link

這個腳本中除了鏈接 vmlinux 之外,還做了些其他的事情。我們暫且不表。還是來專註看看 vmlinux 是怎麼鏈接的。

info LD vmlinux
vmlinux_link "${kallsymso}" vmlinux

然後又調用了 vmlinux_link 。

# Link of vmlinux
# ${1} - optional extra .o files
# ${2} - output file
vmlinux_link()
{
    local lds="${objtree}/${KBUILD_LDS}"
    local objects

    if [ "${SRCARCH}" != "um" ]; then
        if [ -n "${CONFIG_THIN_ARCHIVES}" ]; then
            objects="--whole-archive built-in.o ${1}"
        else
            objects="${KBUILD_VMLINUX_INIT}         \
                --start-group               \
                ${KBUILD_VMLINUX_MAIN}          \
                --end-group             \
                ${1}"
        fi

        ${LD} ${LDFLAGS} ${LDFLAGS_vmlinux} -o ${2}     \
            -T ${lds} ${objects}
    else
        if [ -n "${CONFIG_THIN_ARCHIVES}" ]; then
            objects="-Wl,--whole-archive built-in.o ${1}"
        else
            objects="${KBUILD_VMLINUX_INIT}         \
                -Wl,--start-group           \
                ${KBUILD_VMLINUX_MAIN}          \
                -Wl,--end-group             \
                ${1}"
        fi

        ${CC} ${CFLAGS_vmlinux} -o ${2}             \
            -Wl,-T,${lds}                   \
            ${objects}                  \
            -lutil -lrt -lpthread
        rm -f linux
    fi
}

截取其中關鍵的一點。

${LD} ${LDFLAGS} ${LDFLAGS_vmlinux} -o ${2}     \
    -T ${lds} ${objects}

這不就是一個普通的鏈接么,再看看鏈接了誰? objects 嘛。那 objects 都是誰呢?就是我們剛才分析的 KBUILD_VMLINUX_INIT 和 KBUILD_VMLINUX_MAIN 唄~

嗯,沒錯就是它了。

3 一張圖總結

最後畫一張圖吧。

                            kernel/built-in.o  certs/built-in.o
                            mm/built-in.o      fs/built-in.o
                            ipc/buit-in.o      security/built-in.o
                            crypto/built-in.o  block/built-in.o
                            lib/built-in.o     lib/lib.a
 head_64.o                  usr/built-in.o     drivers/built-in.o
 head64.o                   sound/built-in.o   firmware/built-in.o
 head.o    init/built-in.o  net/built-in.o     virt/built-in.o
    ||          ||             ||                ||

 $(head-y)    $(init-y)     $(core-y)  $(drivers-y)
                            $(net-y)   $(libs-y)  
                            $(virt-y)
     |            |             |          |
     |            |             |          |
      \          /               \        /
       \        /                 \      /

    KBUILD_VMLINUX_INIT         KBUILD_VMLINUX_MAIN
               \                   /
                 \              /
                       vmlinux

嗯,丑是丑了點,不過意思都在了。


本文是 LinuxStory 柴米油鹽計劃的投稿文章。
LinuxStory 整理髮布。
原作者署名為:Wei Yang 。
轉載請註明出處,否則必究相關責任。
本文鏈接:https://linuxstory.org/kbuild-linux-7/

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
這裡是柴米油鹽計劃投稿的發布賬號。

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    柴米油鹽計劃

    VIM 使用演示

    此視頻來自 LinuxStory 志願者的投稿,他堅信: 當一件事做到足夠多時,便會有質的改變。 你說呢?來看看他又學習了什麼新技能吧!
    柴米油鹽計劃

    C 語言總結

    本文來自 wybuhui 的投稿截圖,原稿是 PDF 格式,如果不想看圖片,可以到文末地址下載原文 PDF 文件。 下面讓我們一起欣賞這篇佳作吧。 原文鏈接:PDF 文件地址 本文鏈接:https:/ […]