linux内核配置系统分析 、 内核的 Kconfig & Makefile
linux内核配置系统(舊版)
隨著 Linux 作業系統的廣泛應用,特別是 Linux 在嵌入式領域的發展,越來越多的人開始投身到 Linux 內核級的開發中。面對日益龐大的 Linux 內核原始程式碼,開發者在完成自己的內核代碼後,都將面臨著同樣的問題,即如何將原始程式碼融入到 Linux 內核中,增加相應的 Linux 配置選項,並最終被編譯進 Linux 內核。這就需要瞭解 Linux 的內核配置系統。
眾所周知,Linux 內核是由分佈在全球的 Linux 愛好者共同開發的,Linux 內核每天都面臨著許多新的變化。但是,Linux 內核的組織並沒有出現混亂的現象,反而顯得非常的簡潔,而且具有很好的擴展性,開發人員可以很方便的向 Linux 內核中增加新的內容。原因之一就是 Linux 採用了模組化的內核配置系統,從而保證了內核的擴展性。
本文首先分析了 Linux 內核中的配置系統結構,然後,解釋了 Makefile 和設定檔的格式以及配置語句的含義,最後,通過一個簡單的例子--TEST Driver,具體說明如何將自行開發的代碼加入到 Linux 內核中。在下面的文章中,不可能解釋所有的功能和命令,只對那些常用的進行解釋,至於那些沒有討論到的,請讀者參考後面的參考文獻。
1. 配置系統的基本結構
Linux內核的配置系統由三個部分組成,分別是:
Makefile:分佈在 Linux 內核原始程式碼中的 Makefile,定義 Linux 內核的編譯規則;
設定檔(config.in):給使用者提供配置選擇的功能;
配置工具:包括配置命令直譯器(對配置腳本中使用的配置命令進行解釋)和配置使用者介面(提供基於字元介面、基於 Ncurses 圖形介面以及基於 Xwindows 圖形介面的使用者配置介面,各自對應於 Make config、Make menuconfig 和 make xconfig)。
這些配置工具都是使用指令碼語言,如 Tcl/TK、Perl 編寫的(也包含一些用 C 編寫的代碼)。本文並不是對配置系統本身進行分析,而是介紹如何使用配置系統。所以,除非是配置系統的維護者,一般的內核開發者無須瞭解它們的原理,只需要知道如何編寫 Makefile 和設定檔就可以。所以,在本文中,我們只對 Makefile 和設定檔進行討論。另外,凡是涉及到與具體 CPU 體系結構相關的內容,我們都以 ARM 為例,這樣不僅可以將討論的問題明確化,而且對內容本身不產生影響。
2. Makefile
2.1 Makefile 概述
Makefile 的作用是根據配置的情況,構造出需要編譯的原始檔案清單,然後分別編譯,並把目標代碼連結到一起,最終形成 Linux 內核二進位檔案。
由於 Linux 內核原始程式碼是按照樹形結構組織的,所以 Makefile 也被分佈在目錄樹中。Linux 內核中的 Makefile 以及與 Makefile 直接相關的檔有:
Makefile:頂層 Makefile,是整個內核配置、編譯的總體控制文件。
.config:內核設定檔,包含由使用者選擇的配置選項,用來存放內核配置後的結果(如 make config)。
arch/*/Makefile:位於各種 CPU 體系目錄下的 Makefile,如 arch/arm/Makefile,是針對特定平臺的 Makefile。
各個子目錄下的 Makefile:比如 drivers/Makefile,負責所在子目錄下原始程式碼的管理。
Rules.make:規則檔,被所有的 Makefile 使用。
使用者通過 make config 配置後,產生了 .config。頂層 Makefile 讀入 .config 中的配置選擇。頂層 Makefile 有兩個主要的任務:產生 vmlinux 檔和內核模組(module)。為了達到此目的,頂層 Makefile 遞迴的進入到內核的各個子目錄中,分別調用位於這些子目錄中的 Makefile。至於到底進入哪些子目錄,取決於內核的配置。在頂層 Makefile 中,有一句:include arch/$(ARCH)/Makefile,包含了特定 CPU 體系結構下的 Makefile,這個 Makefile 中包含了平臺相關的資訊。
位於各個子目錄下的 Makefile 同樣也根據 .config 給出的配置資訊,構造出當前配置下需要的原始檔案清單,並在檔的最後有 include $(TOPDIR)/Rules.make。
Rules.make 文件起著非常重要的作用,它定義了所有 Makefile 共用的編譯規則。比如,如果需要將本目錄下所有的 c 程式編譯成彙編代碼,需要在 Makefile 中有以下的編譯規則:
%.s: %.c
$(CC) $(CFLAGS) -S $< -o $@ 有很多子目錄下都有同樣的要求,就需要在各自的 Makefile 中包含此編譯規則,這會比較麻煩。而 Linux 內核中則把此類的編譯規則統一放置到 Rules.make 中,並在各自的 Makefile 中包含進了 Rules.make(include Rules.make),這樣就避免了在多個 Makefile 中重複同樣的規則。對於上面的例子,在 Rules.make 中對應的規則為: %.s: %.c $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$(*F)) $(CFLAGS_$@) -S $< -o $@
2.2 Makefile 中的變數
頂層 Makefile 定義並向環境中輸出了許多變數,為各個子目錄下的 Makefile 傳遞一些資訊。有些變數,比如 SUBDIRS,不僅在頂層 Makefile 中定義並且賦初值,而且在 arch/*/Makefile 還作了擴充。
常用的變數有以下幾類:
1) 版本資訊
版本資訊有:VERSION,PATCHLEVEL, SUBLEVEL, EXTRAVERSION,KERNELRELEASE。版本資訊定義了當前內核的版本,比如 VERSION=2,PATCHLEVEL=4,SUBLEVEL=18,EXATAVERSION=-rmk7,它們共同構成內核的發行版本本KERNELRELEASE:2.4.18-rmk7
2) CPU 體系結構:ARCH
在頂層 Makefile 的開頭,用 ARCH 定義目標 CPU 的體系結構,比如 ARCH:=arm 等。許多子目錄的 Makefile 中,要根據 ARCH 的定義選擇編譯原始檔案的列表。
3) 路徑資訊:TOPDIR, SUBDIRS
TOPDIR 定義了 Linux 內核原始程式碼所在的根目錄。例如,各個子目錄下的 Makefile 通過 $(TOPDIR)/Rules.make 就可以找到 Rules.make 的位置。
SUBDIRS 定義了一個目錄清單,在編譯內核或模組時,頂層 Makefile 就是根據 SUBDIRS 來決定進入哪些子目錄。SUBDIRS 的值取決於內核的配置,在頂層 Makefile 中 SUBDIRS 賦值為 kernel drivers mm fs net ipc lib;根據內核的配置情況,在 arch/*/Makefile 中擴充了 SUBDIRS 的值,參見4)中的例子。
4) 內核組成資訊:HEAD, CORE_FILES, NETWORKS, DRIVERS, LIBS
Linux 內核檔 vmlinux 是由以下規則產生的:
vmlinux: $(CONFIGURATION) init/main.o init/version.o linuxsubdirs
$(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o --start-group $(CORE_FILES) $(DRIVERS) $(NETWORKS) $(LIBS) --end-group -o vmlinux
可以看出,vmlinux 是由 HEAD、main.o、version.o、CORE_FILES、DRIVERS、NETWORKS 和 LIBS 組成的。這些變數(如 HEAD)都是用來定義連接生成 vmlinux 的目的檔案和庫檔列表。其中,HEAD在arch/*/Makefile 中定義,用來確定被最先連結進 vmlinux 的檔清單。比如,對於 ARM 系列的 CPU,HEAD 定義為:
HEAD := arch/arm/kernel/head-$(PROCESSOR).o arch/arm/kernel/init_task.o
表明 head-$(PROCESSOR).o 和 init_task.o 需要最先被連結到 vmlinux 中。PROCESSOR 為 armv 或 armo,取決於目標 CPU。 CORE_FILES,NETWORK,DRIVERS 和 LIBS 在頂層 Makefile 中定義,並且由 arch/*/Makefile 根據需要進行擴充。 CORE_FILES 對應著內核的核心檔,有 kernel/kernel.o,mm/mm.o,fs/fs.o,ipc/ipc.o,可以看出,這些是組成內核最為重要的文件。同時,arch/arm/Makefile 對 CORE_FILES 進行了擴充:
# arch/arm/Makefile
# If we have a machine-specific directory, then include it in the build.
MACHDIR := arch/arm/mach-$(MACHINE)
ifeq ($(MACHDIR),$(wildcard $(MACHDIR)))
SUBDIRS += $(MACHDIR)
CORE_FILES := $(MACHDIR)/$(MACHINE).o $(CORE_FILES)
endif
HEAD := arch/arm/kernel/head-$(PROCESSOR).o arch/arm/kernel/init_task.o
SUBDIRS += arch/arm/kernel arch/arm/mm arch/arm/lib arch/arm/nwfpe
CORE_FILES := arch/arm/kernel/kernel.o arch/arm/mm/mm.o $(CORE_FILES)
LIBS := arch/arm/lib/lib.a $(LIBS)
5) 編譯資訊:CPP, CC, AS, LD, AR,CFLAGS,LINKFLAGS
在 Rules.make 中定義的是編譯的通用規則,具體到特定的場合,需要明確給出編譯環境,編譯環境就是在以上的變數中定義的。針對交叉編譯的要求,定義了 CROSS_COMPILE。比如:
CROSS_COMPILE = arm-linux-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
......
CROSS_COMPILE 定義了交叉編譯器首碼 arm-linux-,表明所有的交叉編譯工具都是以 arm-linux- 開頭的,所以在各個交叉編譯器工具之前,都加入了 $(CROSS_COMPILE),以組成一個完整的交叉編譯工具檔案名,比如 arm-linux-gcc。
CFLAGS 定義了傳遞給 C 編譯器的參數。
LINKFLAGS 是連結生成 vmlinux 時,由連結器使用的參數。LINKFLAGS 在 arm/*/Makefile 中定義,比如:
# arch/arm/Makefile
LINKFLAGS :=-p -X -T arch/arm/vmlinux.lds
6) 配置變數CONFIG_*
.config 檔中有許多的配置變數等式,用來說明使用者配置
linux内核配置系统(新版) 使用內核的 Kconfig & Makefile
在內核編譯中如何將各個目錄樹中的檔組織起來編譯是一個很重要的問題,並且要根據使用者配置來編譯特有的內核。為瞭解決這個問題,內核使用兩種檔,Makefie和Kconfig。分佈到各目錄的Kconfig構成了一個分散式的內核配置資料庫,每個Kconfig分別描述了所屬目錄來原始檔案相關的內核配置功能表,就是我們使用命令 make menuconfig(或者xconfig)後產生的配置功能表,此功能表包含多層,每個層次都是由各個目錄中的Kconfig產生的。使用者根據需求來選擇如何編譯內核,然後將配置結果保存到.config中,然後執行Makefile時就會根據.config的結果來實現內核的編譯。
這個過程是由kbuild系統來完成的,Linux編譯系統會兩次掃描Linux的Makefile:首先編譯系統會讀取Linux內核頂層的Makefile,然後根據讀到的內容第二次讀取Kbuild的Makefile來編譯Linux內核。內核編譯系統或者說kbuild,是一種在編譯內核時,可以對內核配置選項進行選擇的機制。2.6內核樹中已經更新了這種機制,新版本的kbuild 不僅高速而且備有更完善的文檔。Kbuild機制完全依賴於原始程式碼的層次結構。
Makefile文件
面對樹狀結構的內核源碼目錄,內核編譯採用了各個子目錄擁有自己目錄相關的Makefile(被稱為sub-Makefile或kbuild Makefile),內核編譯依賴於各個子目錄下的子makefile(sub-Makefile)檔,這些sub-Makefile定義了根據該子目錄下的源碼檔構建目的檔案的規則,並且僅對該目錄下的檔作適當的修改。頂層Makefile採用遞迴的方式調用位元於init/, drivers/, sound/, net/, lib/ ,usr/等目錄下的各個子目錄中的 Makefile檔。在遞迴呼叫之前,kbuild首先要確定是否已經滿足一些必要的條件,包括在必要時更新include/Linux/version.h檔,並設置符號連結include/asm,使之指向與目標體系結構相關的檔。例如,如果為PPC編譯代碼,則include/asm指向include/asm-ppc。kbuild還要對文件include/Linux/autoconf.h和include/Linux/config進行編譯。之後,從根目錄開始進行遞迴。
各個子Makefile檔比較簡單,指出了該如何編譯目的檔案,例如/mm目錄下的Makefile片段:
16 obj-$(CONFIG_PROC_PAGE_MONITOR) += pagewalk.o
17 obj-$(CONFIG_BOUNCE) += bounce.o
18 obj-$(CONFIG_SWAP) += page_io.o swap_state.o swapfile.o thrash.o
19 obj-$(CONFIG_HAS_DMA) += dmapool.o
20 obj-$(CONFIG_HUGETLBFS) += hugetlb.o
Kconfig文件
Kconfig的作用就是為了讓使用者配置內核,在Kconfig中定義了一些變數,使用者通過設置變數的值來選擇如何個性化自己的系統內核。定義的變數將在
每個功能表都有一個關鍵字標識,最常見的就是config
語法:
config
symbol是個新的標記的功能表項目,options是在這個新的功能表項目下的屬性和選項
其中options部分有:
1、類型定義:
每個config功能表項目都要有類型定義,bool布林類型、 tristate三態:內建、模組、移除 string字串、 hex十六進位、 integer整型
例如config HELLO_MODULE
bool “hello test module”
bool 類型的只能選中或不選中,tristate類型的功能表項目多了編譯成內核模組的選項,假如選擇編譯成內核模組,則會在.config中生成一個 CONFIG_HELLO_MODULE=m的配置,假如選擇內建,就是直接編譯成內核影響,就會在.config中生成一個 CONFIG_HELLO_MODULE=y的配置.
2、依賴型定義depends on或requires
指此功能表的出現和否依賴於另一個定義
config HELLO_MODULE
bool “hello test module”
depends on ARCH_PXA
這個例子表明HELLO_MODULE這個功能表項目只對XScale處理器有效。
3、幫助性定義
只是增加幫助用關鍵字help或—help—
.config文件
上面提到了利用內核配置工具自動生成名為.config的內核設定檔,這是編譯內核的第一步。.config檔位於原始程式碼根目錄下,描述所有內核配置選項,可以借助內核配置工具來選擇這些選項。每個內核配置選項都有相關的名字和變數值。其名字形如CONFIG_
1 #
2 # Automatically generated make config: don’t edit
3 #
4 CONFIG_X86=y
5 CONFIG_MMU=y
6 CONFIG_UID16=y
7 CONFIG_GENERIC_ISA_DMA=y
8
9 #
10 # Code maturity level options
11 #
12 CONFIG_EXPERIMENTAL=y
13 CONFIG_CLEAN_COMPILE=
14 CONFIG_STANDALONE=y
15 CONFIG_BROKEN_ON_SMP=y
16
17 #
18 # General setup
19 #
20 CONFIG_SWAP=y
21 CONFIG_SYSVIPC=y
22 #CONFIG_POSIX_MQUEUE is not set
23 CONFIG_BSD_PROCESS_ACCT=y
上述.config檔指出第4到第7行的選項位於頂層功能表中,第12到第15行的選項位於代碼成熟度選項功能表中,第20行到第23行的選項位於通用設置選項功能表中。
所有配置工具都會產生上述功能表,並且已經看到前幾個選項、代碼成熟度選項、及通用設置選項都位於頂層。後面兩個選項被擴展為包含多個選項的子功能表。這些功能表都是在調用xconfig命令時,由qconf配置工具提供的。配置工具顯示的功能表都預設用於X86體系結構。
五、DIY:向內核添加自己的程式
A.在Linux內核中增加自己的程式步驟(注意這裡只是程式檔):
1.將編寫的原始程式碼複製到Linux內核原始程式碼的相應目錄中。
2.在目錄的Kconfig檔中增加新原始程式碼對應專案的編譯配置選項
3.在目錄的Makefile檔中增加對新原始程式碼的編譯條目。
B.在Linux內核drivers/目錄中增加目錄和子目錄步驟:
1.所加目錄為daiq,檔如下:
[daiq@localhost daiq]$ tree
.
-- Kconfig
-- Makefile
-- led
-- Kconfig
-- Makefile
`-- led.c
`-- test.c
#注意此時各個目錄中的Makefile和Kconfig檔是空的
2.在新增的相應目錄添加Kconfig和Makefile檔,上面的目錄中已經添加。
3.修改新增目錄的父目錄的Kconfig和Makefile檔,以便新增的Kconfig和
Makefile能被引用。向父目錄中的Makefile添加:
obj-y += daiq/
表示在編譯過程中包含子目錄daiq目錄。然後修改Kconfig檔,添加:
source “drivers/daiq/Kconfig”
表示在配置時引用子目錄daiq中的設定檔Kconfig。
4.實際上,要讓drivers/daiq/Kconfig有效,要在arch/arm/Kconfig文件中添加:
source “drivers/daiq/Kconfig”
父目錄drivers/Kconfig的修改可以不要。
5.經過上面一步,內核就可以找到所加的目錄daiq了,然後就是編輯各個目錄中的Makefile和Kconfig檔,在你添加的目錄daiq中的Makefile加入:
obj-$(CONFIG_TEST) += test.o #因為在daiq目錄中要編譯test.c文件
#所以會根據CONFIG_TEST來決定編譯選項
obj-y += led/#編譯daiq目錄中的子目錄led
然後Kconfig文件是:
menu "DaiQ device support" #在make menuconfig時要顯示的功能表入口
config DAIQ_TEST
bool "Test"
help
DaiQ device support
source "drivers/daiq/led/Kconfig"
endmenu
注意:menu和endmenu的前後要加回車,不然make menuconfig的時候會出錯。
再看led目錄下的Makefile和Kconfig:
Makefile為文件:
obj-$(CONFIG_LED)+=led.o
Kconfig文件:
config LED
tristate “led support”
5.現在可以make menuconfig來配置添加自己目錄daiq的驅動了!
沒有留言:
張貼留言