探索Linux內核:Kconfig / kbuild的祕密

探索Linux內核:Kconfig / kbuild的祕密

深入瞭解Linux配置/構建系統的工作原理

自從Linux內核代碼遷移到Git以來,Linux內核配置/構建系統(也稱爲Kconfig / kbuild)已存在很長時間了。然而,作爲支持基礎設施,它很少成爲人們關注的焦點; 甚至在日常工作中使用它的內核開發人員也從未真正考慮過它。

爲了探索如何編譯Linux內核,本文將深入介紹Kconfig / kbuild內部進程,解釋如何生成.config文件和vmlinux / bzImage文件,並介紹依賴性跟蹤的智能技巧。

Kconfig

構建內核的第一步始終是配置。Kconfig有助於使Linux內核高度模塊化和可定製。Kconfig爲用戶提供了許多配置目標:

配置 使用面向行的程序更新當前配置
nconfig 使用ncurses基於菜單的程序更新當前配置
menuconfig 使用基於菜單的程序更新當前配置
xconfig的 使用基於Qt的前端更新當前配置
gconfig 使用基於GTK +的前端更新當前配置
oldconfig 使用提供的.config作爲基礎更新當前配置
localmodconfig 更新當前配置禁用未加載的模塊
localyesconfig 更新將本地mod轉換爲核心的當前配置
defconfig 默認來自Arch提供的defconfig的新配置
savedefconfig 將當前配置保存爲./defconfig(最小配置)
allnoconfig 新配置,其中所有選項均以“否”回答
allyesconfig 使用“是”接受所有選項的新配置
allmodconfig 儘可能新的配置選擇模塊
alldefconfig 所有符號都設置爲默認值的新配置
randconfig 新配置,隨機回答所有選項
listnewconfig 列出新選項
olddefconfig 與oldconfig相同,但在沒有提示的情況下將新符號設置爲其默認值
kvmconfig 爲KVM來賓內核支持啓用其他選項
xenconfig 爲xen dom0和來賓內核支持啓用其他選項
tinyconfig 配置最小的內核

我認爲menuconfig是這些目標中最受歡迎的。目標由不同的主程序處理,這些程序由內核提供並在內核構建期間構建。一些目標有一個GUI(爲了方便用戶),而大多數沒有。與Kconfig相關的工具和源代碼主要位於內核源代碼中的scripts / kconfig /下。從scripts / kconfig / Makefile可以看出,有幾個主機程序,包括confmconfnconf。除了conf之外,它們中的每一個都負責基於GUI的配置目標之一,因此,conf處理其中的大多數。

從邏輯上講,Kconfig的基礎結構有兩部分:一部分實現一種新語言來定義配置項(參見內核源代碼下的Kconfig文件),另一部分解析Kconfig語言並處理配置操作。

大多數配置目標具有大致相同的內部過程(如下所示):
kconfig_process

請注意,所有配置項都具有默認值。

第一步讀取源根目錄下的Kconfig文件,構建初始配置數據庫; 然後它根據此優先級讀取現有配置文件來更新初始數據庫:

.config
/ lib / modules / $(shell,uname -r)/ .config /
etc / kernel-config
/ boot / config - $(shell,uname -r)
ARCH_DEFCONFIG
arch / $(ARCH)/ defconfig

如果您通過menuconfig進行基於GUI的配置或通過oldconfig進行基於命令行的配置,則會根據您的自定義更新數據庫。最後,配置數據庫被轉儲到.config文件中。

但.config文件不是內核構建的最終素材; 這就是syncconfig目標存在的原因。syncconfig曾經是一個名爲silentoldconfig的配置目標,但它不會執行舊名稱所說的內容,因此它已重命名。此外,因爲它是供內部使用(不適用於用戶),所以它已從列表中刪除。

以下是syncconfig的作用:
在這裏插入圖片描述

syncconfig將.config作爲輸入並輸出許多其他文件,這些文件分爲三類:

  • auto.conf和tristate.conf

    用於makefile文本處理。例如,您可以在組件的makefile中看到這樣的語句:

    obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o
    
  • autoconf.h用於C語言源文件。

  • **include / config /**下的空頭文件用於kbuild期間的配置依賴性跟蹤,如下所述。

配置完成後,我們將知道哪些文件和代碼片段未編譯。

kbuild的

組件式構建(稱爲遞歸make)是GNU make管理大型項目的常用方法。Kbuild是遞歸make的一個很好的例子。通過將源文件劃分爲不同的模塊/組件,每個組件都由其自己的makefile管理。當您開始構建時,頂級makefile以正確的順序調用每個組件的makefile,構建組件,並將它們收集到最終的執行程序中。

Kbuild指的是不同類型的makefile:

  • Makefile是源根目錄中的頂級makefile。
  • .config是內核配置文件。
  • arch / $(ARCH)/ Makefile是arch makefile,它是top makefile的補充。
  • **scripts / Makefile。***描述了所有kbuild makefile的通用規則。
  • 最後,大約有500個kbuild makefile

top makefile包含arch makefile,讀取.config文件,下載到子目錄,在scripts / Makefile。*中定義的例程的幫助下,在每個組件的makefile上調用make,構建每個中間對象,並將所有中間對象鏈接到vmlinux中。內核文檔Documentation / kbuild / makefiles.txt描述了這些makefile的所有方面。

作爲一個例子,讓我們看看如何在x86-64上生成vmlinux:
在這裏插入圖片描述

(該插圖基於Richard Y. Steven的博客。它已更新,並在作者允許的情況下使用。)

進入vmlinux的所有**.o文件首先進入他們自己的內置.a**,通過變量 KBUILD_VMLINUX_INITKBUILD_VMLINUX_MAINKBUILD_VMLINUX_LIBS指示,然後收集到vmlinux文件中。

在簡化的makefile代碼的幫助下,瞭解如何在Linux內核中實現遞歸make:

# In top Makefile vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) +$(call if_changed,link-vmlinux) # Variable assignments vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS) export KBUILD_VMLINUX_INIT := $(head-y) $(init-y) export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y) export KBUILD_VMLINUX_LIBS := $(libs-y1) export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds init-y := init/ drivers-y := drivers/ sound/ firmware/ net-y := net/ libs-y := lib/ core-y := usr/ virt-y := virt/ # Transform to corresponding built-in.a init-y := $(patsubst %/, %/built-in.a, $(init-y)) core-y := $(patsubst %/, %/built-in.a, $(core-y)) drivers-y := $(patsubst %/, %/built-in.a, $(drivers-y)) net-y := $(patsubst %/, %/built-in.a, $(net-y)) libs-y1 := $(patsubst %/, %/lib.a, $(libs-y)) libs-y2 := $(patsubst %/, %/built-in.a, $(filter-out %.a, $(libs-y))) virt-y := $(patsubst %/, %/built-in.a, $(virt-y)) # Setup the dependency. vmlinux-deps are all intermediate objects, vmlinux-dirs # are phony targets, so every time comes to this rule, the recipe of vmlinux-dirs # will be executed. Refer "4.6 Phony Targets" of `info make` $(sort $(vmlinux-deps)): $(vmlinux-dirs) ; # Variable vmlinux-dirs is the directory part of each built-in.a 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))) # The entry of recursive make $(vmlinux-dirs): $(Q)$(MAKE) $(build)=$@ need-builtin=1

遞歸製作配方已擴展,例如:

make -f scripts/Makefile.build obj=init need-builtin=1

這意味着make將進入scripts / Makefile.build繼續構建每個內置的工作.a。在scripts / link-vmlinux.sh的幫助下,vmlinux文件最終位於源根目錄下。

瞭解vmlinux與bzImage

許多Linux內核開發人員可能不清楚vmlinux和bzImage之間的關係。例如,這是他們在x86-64中的關係:
vmlinux-bzimage

源根vmlinux被剝離,壓縮,放入piggy.S,然後與其他對等對象鏈接到arch / x86 / boot / compressed / vmlinux。同時,在arch / x86 / boot下生成一個名爲setup.bin的文件。可能有一個可選的第三個文件具有重定位信息,具體取決於CONFIG_X86_NEED_RELOCS的配置 。

由內核提供的稱爲build的宿主程序將這兩個(或三個)部分構建到最終的bzImage文件中。

依賴性跟蹤

Kbuild跟蹤三種依賴關係:

  1. 所有必備文件(* .c和* .h
  2. 所有必備文件中使用的**CONFIG_**選項
  3. 用於編譯目標的命令行依賴項

第一個很容易理解,但第二個和第三個呢?內核開發人員經常會看到如下代碼:

#ifdef CONFIG_SMP __boot_cpu_id = cpu; #endif

CONFIG_SMP更改時,應重新編譯這段代碼。編譯源文件的命令行也很重要,因爲不同的命令行可能會導致不同的目標文件。

當**.c文件通過#include**指令使用頭文件時,您需要編寫如下規則:

main.o: defs.h recipe...

管理大型項目時,需要大量的這些規則; 把它們全部寫下來會很乏味和乏味。幸運的是,大多數現代C編譯器都可以通過查看源文件中的**#include行來爲您編寫這些規則。對於GNU編譯器集合(GCC),只需添加命令行參數:-MD depfile**

# In scripts/Makefile.lib c_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ -include $(srctree)/include/linux/compiler_types.h \ $(__c_flags) $(modkern_cflags) \ $(basename_flags) $(modname_flags)

這將生成一個**.d**文件,內容如下:

init_task.o: init/init_task.c include/linux/kconfig.h \ include/generated/autoconf.h include/linux/init_task.h \ include/linux/rcupdate.h include/linux/types.h \ ...

然後主機程序fixdep通過將depfile和命令行作爲輸入來處理其他兩個依賴項,然後以makefile語法輸出**。 .cmd**文件,該文件記錄命令行和所有先決條件(包括配置)爲目標。它看起來像這樣:

# The command line used to compile the target cmd_init/init_task.o := gcc -Wp,-MD,init/.init_task.o.d -nostdinc ... ... # The dependency files deps_init/init_task.o := \ $(wildcard include/config/posix/timers.h) \ $(wildcard include/config/arch/task/struct/on/stack.h) \ $(wildcard include/config/thread/info/in/task.h) \ ... include/uapi/linux/types.h \ arch/x86/include/uapi/asm/types.h \ include/uapi/asm-generic/types.h \ ...

在遞歸make期間將包含一個**。 .cmd**文件,提供所有依賴關係信息並幫助決定是否重建目標。

這背後的祕密是fixdep將解析depfile.d文件),然後解析內部的所有依賴文件,搜索所有CONFIG_字符串的文本,將它們轉換爲相應的空頭文件,並將它們添加到目標的先決條件。每次配置更改時,相應的空頭文件也將更新,因此kbuild可以檢測到該更改並重建依賴於它的目標。因爲還記錄了命令行,所以很容易比較最後和當前的編譯參數。

展望未來

Kconfig / kbuild在很長一段時間內保持不變,直到新的維護者Masahiro Yamada於2017年初加入,現在kbuild再次正在積極開發。如果您很快就會看到與本文中的內容不同的內容,請不要感到驚訝。

轉自:https://opensource.com/article/18/10/kbuild-and-kconfig

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章