轉自:http://www.juliantec.info/julblog/yihect/linux-kernel-build-system-7
通過前面的分析,我們已經知道,在 Linux 中,區分有兩種模組:內部模組和外部模組。我們這裡說的對目標 modules 的處理指的就是要編譯出那些內部模組,對外部模組的處理我們將在後面敘述。我們還知道,不管是內部模組,還是外部模組,其編譯都要分兩個階段進行。階段一產生組成模組的對應 .o 檔案和 .mod 檔案,階段二要用 scripts/mod/modpost 來產生 .mod.c 檔案,並將其編譯成 .mod.o 對象檔案,最後將 .mod.o 連同前面的 .o 一起連結成 .ko 模組檔案。另外我們還知道,在產生vmlinux的過程中,會在核心頂層目錄中產生一個 Modules.symvers,裡面存放基本核心匯出的、供模組使用的符號以及CRC校正和。通過前面的討論所得到的這些知識,或許對你來說還不十分清楚,沒關係,我們再行深入繼續對內部模組目標 modules 的討論,它將讓你有個較為清楚的認識。
好,先在頂層 Makefile 中(架構中的E1部分)找到處理 modules 目標的規則:
all: modules # Build modules## A module can be listed more than once in obj-m resulting in# duplicate lines in modules.order files. Those are removed# using awk while concatenating to the final file. PHONY += modulesmodules: $(vmlinux-dirs) $(if $(KBUILD_BUILTIN),vmlinux) $(Q)$(AWK) '!x[$$0]++' $(vmlinux-dirs:%=$(objtree)/%/modules.order) > $(objtree)/modules.order @$(kecho) ' Building modules, stage 2.'; $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.fwinst obj=firmware __fw_modbuild
上面顯示 modules 目標依賴於 $(vmlinux-dirs)。這種依賴就意味著內部模組處理的第一階段就已經在處理 vmlinux-dirs 的過程中完成了。前面對 vmlinux-dirs 的討論過程也說了如何編譯出構成模組的那些 .o 對象檔案,以及如何產生 .mod 檔案。
很顯然,既然內部模組的第一階段已經完成,那處理 modules 目標規則的命令部分就是來完成內部模組的第二階段了。
命令部分中的第一行用一個awk調用來將各子目錄中 modules.order 檔案內容歸集到頂層目錄的 modules.order 檔案中。該檔案列出了構建系統構建內部模組的次序。如果你深入學習,你會知道package module-init-tools 中包含有一個工具:depmod。該工具解析出各個核心模組的依賴關係,它會將依賴關係儲存在檔案 modules.dep 中。所謂模組之間的依賴,舉個例子比方說模組A的代碼中用到了模組B所匯出來的函數,那麼我們就說模組A是依賴於模組B的。很顯然,既然模組之間有依賴,那模組之間的載入次序就得規定好。如我們的例子中,必須先載入模組B,後載入模組A,否則就會出錯。老版本的 depmod 只是單純的依賴內部模組之間的依賴來決定內部模組的載入順序。但是先版本的 depmod 還會考慮核心構建系統構建各核心模組的順序。關於更多 modules.orders 的使用資訊,你可以參考核心開發郵件清單中的內容,在這裡可以看到:http://lkml.org/lkml/2007/12/7/96。另外你也可以查看 module-init-tools 包git的歷程記錄:
http://git.kernel.org/?p=utils/kernel/module-init-tools/module-init-tools.git&a=search&h=HEAD&st=commit&s=modules.order
上面命令部分中最關鍵的就是接下來那一行:$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost。由於該命令沒有指定make的目標,所以它會構建 Makefile.modpost 中的預設目標 _modpost。而在同一個檔案中查看一下 _modpost 的相關規則:
PHONY := _modpost_modpost: __modpost......# Stop after building .o files if NOFINAL is set. Makes compile tests quicker_modpost: $(if $(KBUILD_MODPOST_NOFINAL), $(modules:.ko:.o),$(modules))
上面代碼錶明,_modpost 依賴於 __modpost。 同時,如果有定義過KBUILD_MODPOST_NOFINAL,那麼它還依賴於那些和模組名稱對應的 .o 檔案。打個比方,如果有兩個對象檔案 part1.o 和 part2.o組成一個模組 MyModule.ko,那麼它就依賴於 MyModule.o 對象檔案。另外如果沒有定義過,那它還依賴於所有的內部模組。所以變數 KBUILD_MODPOST_NOFINAL 的定義就意味著我們只是產生 MyModule.o,而不要再繼續從 MyModule.o 出發產生 MyModule.ko 模組。變數 modules 被這樣定義:
# Step 1), find all modules listed in $(MODVERDIR)/__modules := $(sort $(shell grep -h '\.ko' /dev/null $(wildcard $(MODVERDIR)/*.mod)))modules := $(patsubst %.o,%.ko, $(wildcard $(__modules:.ko=.o)))
這個定義用 grep 搜尋目錄 $(MODVERDIR)/ 中的所有 *.mod 檔案,找出其中包含模組檔案名稱尾碼 .ko 的那些行。效果上也就是等價於找出所有的內部模組名稱,組成列表賦給 modules。還記得麼。前面提到過,目錄$(MODVERDIR)就是 .../.tmp_version/,其中存有模組處理第一階段中產生的所有 .mod 檔案。
我們回來看一下 __modpost 目標的處理,找出代碼如下:
PHONY += __modpost__modpost: $(modules:.ko=.o) FORCE $(call cmd,modpost) $(wildcard vmlinux) $(filter-out FORCE,$^) $(Q)echo 'cmd_$@ := (call cmd,modpost) $(wildcard vmlinux) $(filter-out FORCE,$^)' > $(@D)/.$(@F).cmd
仔細看該規則的命令部分,它調用了 cmd_modpost,我們來看看它的定義:
# Step 2), invoke modpost# Includes step 3,4modpost = scripts/mod/modpost \ $(if $(CONFIG_MODVERSIONS),-m) \ $(if $(CONFIG_MODULE_SRCVERSION_ALL),-a,) \ $(if $(KBUILD_EXTMOD),-i,-o) $(kernelsymfile) \ $(if $(KBUILD_EXTMOD),-I $(modulesymfile)) \ $(if $(KBUILD_EXTRA_SYMBOLS), $(patsubst %, -e %,$(KBUILD_EXTRA_SYMBOLS))) \ $(if $(KBUILD_EXTMOD),-o $(modulesymfile)) \ $(if $(CONFIG_DEBUG_SECTION_MISMATCH),,-S) \ $(if $(CONFIG_MARKERS),-K $(kernelmarkersfile)) \ $(if $(CONFIG_MARKERS),-M $(markersfile)) \ $(if $(KBUILD_EXTMOD)$(KBUILD_MODPOST_WARN),-w) \ $(if $(cross_build),-c) quiet_cmd_modpost = MODPOST $(words $(filter-out vmlinux FORCE, $^)) modules cmd_modpost = $(modpost) -s
似曾相識對吧。沒錯,我們在前面討論 vmlinux.o 的處理的時候,就已經碰到工具程式 .../scripts/mod/modpost 的使用了。只不過,那時候使用它的是變數 cmd_kernel-mod,而非cmd_modpost。當時,構建系統用來完成兩個動作:mis-match section的檢查和產生基本核心匯出符號檔案 Module.symvers,其中包含基本核心所匯出的所有符號及CRC校正。那此處調用 .../scripts/mod/modpost 來做何用途呢。我們且先來看 modpost 工具程式的調用方式。
由於我們的配置(s3c2410_defcofig)中,並沒有設定 CONFIG_MODVERSIONS,CONFIG_MODULE_SRCVERSION_ALL,CONFIG_DEBUG_SECTION_MISMATCH 以及 CONFIG_MARKERS。同時我們也沒設定KBUILD_EXTRA_SYMBOLS,而當前我們是在處理內部模組的第二階段,所以上面處理 __modpost 規則中的命令實際上就是:
scripts/mod/modpost -o /home/yihect/linux-2.6.31/Module.symvers -S -c -s vmlinux MyModule.o YouModule.o HisModule.o ....
其中,命令後半部分包括省略符號所表示的,是與各內部模組名稱對應的 .o 檔案。這個命令在這裡主要也是要完成兩項工作:
a) 解析出 vmlinux以及各對應的 .o 檔案內的符號,並重新將它們連同各自的CRC校正寫入到頂層目錄中的檔案 Modules.symvers 內。所以最後該檔案內不僅包含基本核心的符號及CRC校正,還包括各內部模組所匯出的符號及CRC校正,在結果上是前面處理 vmlinux.o 時所產生的 Modules.symvers 的超集;
b) 針對各個內部模組,產生對應的 *.mod.c 檔案。產生 *.mod.c 檔案的代碼在 modpost.c 檔案的main函數中:
int main(int argc, char **argv){ struct module *mod; struct buffer buf = { }; //...... for (mod = modules; mod; mod = mod->next) { char fname[strlen(mod->name) + 10]; if (mod->skip) continue; buf.pos = 0; add_header(&buf, mod); add_staging_flag(&buf, mod->name); err |= add_versions(&buf, mod); add_depends(&buf, mod, modules); add_moddevtable(&buf, mod); add_srcversion(&buf, mod); sprintf(fname, "%s.mod.c", mod->name); write_if_changed(&buf, fname); } //....... return err;}
具體的產生代碼就分布在不同的 add_* 函數當中,由於超出本問主題範圍,我們在這裡不對它們詳加闡述。你可自行查看代碼,並參與我們在 mail list 中的討論。我們這裡看下它產生的 *.mod.c 檔案的內容,我們以目錄 .../net/wireless/ 下的模組 cfg80211.ko 為例(由於 s3c2410_defconfig 的預設配置將變數 CONFIG_CFG80211 設定為M,所以根據該目錄下 Makefile 的內容,構建系統會產生模組 cfg80211.ko)。為了完整的說明 *.mod.c 檔案的內容,我們特意修改了 .../.config 設定檔,將 CONFIG_MODVERSIONS 及 CONFIG_MODULE_SRCVERSION_ALL 兩變數設定為y。也就是開啟了核心的 Module versioning 功能。我們列出檔案 cfg80211.mod.c 的內容(有刪減):
該檔案大部分的代碼是定義一些變數,並將其放在三個不同的 elf section 內(後面構建系統會編譯這個 .mod.c 形成對象檔案,連結進 .ko):
a) 定義struct module結構變數 __this_module,並將其放在 .gnu.linkonce.this_module section 中。在將模組載入進運行著的核心時,核心負責將這個對象加到內部的modules list中。modules list 是核心維護所有已載入模組的一個雙向鏈表(更多請看:http://lkml.org/lkml/2002/12/27/16)。
b) 定義 struct modversion_info 結構數組 ____versions,並將其放到 __versions sectiong 中。該數組中存放的都是該模組中使用到,但沒被定義的符號,也就是所謂的 unresolved symbol,它們或在基本核心中定義,或在其他模組中定義,核心使用它們來做 Module versioning。注意其中的 module_layout 符號,這是一個 dummy symbol。核心使用它來跟蹤不同核心版本關於模組處理的相關資料結構的變化。當一個模組在A版本的核心中編譯後,又在另外一個B版本的核心中載入,如果兩個核心中處理modules的那些資料結構體定義發生變化了,那核心就拒絕繼續做其他 Module versioning 工作,也就是拒絕載入模組。
符號 module_layout 在檔案 .../kernel/module.c 中被定義成一個函數:
#ifdef CONFIG_MODVERSIONS/* Generate the signature for all relevant module structures here. * If these change, we don't want to try to parse the module. */void module_layout(struct module *mod, struct modversion_info *ver, struct kernel_param *kp, struct kernel_symbol *ks, struct marker *marker, struct tracepoint *tp){}EXPORT_SYMBOL(module_layout);#endif
該函數函數體為空白,但卻有很多的參數類型。為什麼。就是因為核心要用它來跟蹤 module/modversion_info/kernel_param/kernel_symbol/marker/tracepoint 等結構體定義變化。那如何跟蹤這種變化呢。核心會和處理其他的符號一樣,用這個函數原型做一次CRC校正, 產生校正和。將其放如 *.mod.c 的__versions section中,待在模組載入時,拿其與儲存在正啟動並執行核心中的CRC進行比較,如果不同,就拒絕進一步載入模組。載入模組時核心對此項的檢查代碼在 .../kernel/module.c,如下:
static inline int check_modstruct_version(Elf_Shdr *sechdrs, unsigned int versindex, struct module *mod){ const unsigned long *crc; if (!find_symbol(MODULE_SYMBOL_PREFIX "module_layout", NULL, &crc, true, false)) BUG(); return check_version(sechdrs, versindex, "module_layout", mod, crc);}
函數 check_version 就做真正的檢查工作,檢查不通過,核心會報出著名的錯誤資訊:disagrees about version of symbol module_layout。
b) 最後,.mod.c 中會將很多資訊塞進 .modinfo section 中,包括:vermagic字串,模組依賴資訊,srcversion資訊等等(還有其他很多資訊)。我們以vermagic來舉例分析。
宏 MODULE_INFO 定義在 .../include/linux/module.h 中: