通過前面的分析,我們已經(jīng)知道,在 Linux 中,區(qū)分有兩種模塊:內(nèi)部模塊和外部模塊。我們這里說的對目標(biāo) modules 的處理指的就是要編譯出那些內(nèi)部模塊,對外部模塊的處理我們將在后面敘述。我們還知道,不管是內(nèi)部模塊,還是外部模塊,其編譯都要分兩個(gè)階段進(jìn)行。階段一 生成組成模塊的對應(yīng) .o 文件和 .mod 文件,階段二要用 scripts/mod/modpost 來生成 .mod.c 文件,并將其編譯成 .mod.o 對象文件,最后將 .mod.o 連同前面的 .o 一起鏈接成 .ko 模塊文件。另外我們還知道,在生成vmlinux的過程中,會(huì)在內(nèi)核頂層目錄中生成一個(gè) Modules.symvers,里面存放基本內(nèi)核導(dǎo)出的、供模塊使用的符號(hào)以及CRC校驗(yàn)和。通過前面的討論所得到的這些知識(shí),或許對你來說還不十分清 楚,沒關(guān)系,我們再行深入繼續(xù)對內(nèi)部模塊目標(biāo) modules 的討論,它將讓你有個(gè)較為清楚的認(rèn)識(shí)。
好,先在頂層 Makefile 中(框架中的E1部分)找到處理 modules 目標(biāo)的規(guī)則:
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 += modules
modules: $(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 目標(biāo)依賴于 $(vmlinux-dirs)。這種依賴就意味著內(nèi)部模塊處理的第一階段就已經(jīng)在處理 vmlinux-dirs 的過程中完成了。前面對 vmlinux-dirs 的討論過程也說了如何編譯出構(gòu)成模塊的那些 .o 對象文件,以及如何生成 .mod 文件。
很顯然,既然內(nèi)部模塊的第一階段已經(jīng)完成,那處理 modules 目標(biāo)規(guī)則的命令部分就是來完成內(nèi)部模塊的第二階段了。
命令部分中的第一行用一個(gè)awk調(diào)用來將各子目錄中 modules.order 文件內(nèi)容歸集到頂層目錄的 modules.order 文件中。該文件列出了構(gòu)建系統(tǒng)構(gòu)建內(nèi)部模塊的次序。如果你深入學(xué)習(xí),你會(huì)知道package module-init-tools 中包含有一個(gè)工具:depmod。該工具解析出各個(gè)內(nèi)核模塊的依賴關(guān)系,它會(huì)將依賴關(guān)系保存在文件 modules.dep 中。所謂模塊之間的依賴,舉個(gè)例子比方說模塊A的代碼中用到了模塊B所導(dǎo)出來的函數(shù),那么我們就說模塊A是依賴于模塊B的。很顯然,既然模塊之間有依賴, 那模塊之間的加載次序就得規(guī)定好。如我們的例子中,必須先加載模塊B,后加載模塊A,否則就會(huì)出錯(cuò)。老版本的 depmod 只是單純的依賴內(nèi)部模塊之間的依賴來決定內(nèi)部模塊的加載順序。但是先版本的 depmod 還會(huì)考慮內(nèi)核構(gòu)建系統(tǒng)構(gòu)建各內(nèi)核模塊的順序。關(guān)于更多 modules.orders 的使用信息,你可以參考內(nèi)核開發(fā)郵件列表中的內(nèi)容,在這里可以看到:http:///lkml/2007/12/7/96。另外你也可以 查看 module-init-tools 包git的修訂記錄:
上面命令部分中最關(guān)鍵的就是接下來那一行:$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost。由于該命令沒有指定make的目標(biāo),所以它會(huì)構(gòu)建 Makefile.modpost 中的缺省目標(biāo) _modpost。而在同一個(gè)文件中查看一下 _modpost 的相關(guān)規(guī)則:
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。 同時(shí),如果有定義過KBUILD_MODPOST_NOFINAL,那么它還依賴于那些和模塊名稱對應(yīng)的 .o 文件。打個(gè)比方,如果有兩個(gè)對象文件 part1.o 和 part2.o組成一個(gè)模塊 MyModule.ko,那么它就依賴于 MyModule.o 對象文件。另外如果沒有定義過,那它還依賴于所有的內(nèi)部模塊。所以變量 KBUILD_MODPOST_NOFINAL 的定義就意味著我們只是生成 MyModule.o,而不要再繼續(xù)從 MyModule.o 出發(fā)生成 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)))
這個(gè)定義用 grep 搜索目錄 $(MODVERDIR)/ 中的所有 *.mod 文件,找出其中包含模塊文件名稱后綴 .ko 的那些行。效果上也就是等價(jià)于找出所有的內(nèi)部模塊名稱,組成列表賦給 modules。還記得么?前面提到過,目錄$(MODVERDIR)就是 .../.tmp_version/,其中存有模塊處理第一階段中生成的所有 .mod 文件。
我們回來看一下 __modpost 目標(biāo)的處理,找出代碼如下:
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
仔細(xì)看該規(guī)則的命令部分,它調(diào)用了 cmd_modpost,我們來看看它的定義:
# Step 2), invoke modpost
# Includes step 3,4
modpost = 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
似曾相識(shí)對吧?沒錯(cuò),我們在前面討論 vmlinux.o 的處理的時(shí)候,就已經(jīng)碰到工具程序 .../scripts/mod/modpost 的使用了。只不過,那時(shí)候使用它的是變量 cmd_kernel-mod,而非cmd_modpost。當(dāng)時(shí),構(gòu)建系統(tǒng)用來完成兩個(gè)動(dòng)作:mis-match section的檢查和生成基本內(nèi)核導(dǎo)出符號(hào)文件 Module.symvers,其中包含基本內(nèi)核所導(dǎo)出的所有符號(hào)及CRC校驗(yàn)。那此處調(diào)用 .../scripts/mod/modpost 來做何用途呢?我們且先來看 modpost 工具程序的調(diào)用方式。
由于我們的配置(s3c2410_defcofig)中,并沒有設(shè)置 CONFIG_MODVERSIONS,CONFIG_MODULE_SRCVERSION_ALL,CONFIG_DEBUG_SECTION_MISMATCH 以及 CONFIG_MARKERS。同時(shí)我們也沒設(shè)置KBUILD_EXTRA_SYMBOLS,而當(dāng)前我們是在處理內(nèi)部模塊的第二階段,所以上面處理 __modpost 規(guī)則中的命令實(shí)際上就是:
scripts/mod/modpost -o /home/yihect/linux-2.6.31/Module.symvers -S -c -s vmlinux MyModule.o YouModule.o HisModule.o ....
其中,命令后半部分包括省略號(hào)所表示的,是與各內(nèi)部模塊名稱對應(yīng)的 .o 文件。這個(gè)命令在這里主要也是要完成兩項(xiàng)工作:
a) 解 析出 vmlinux以及各對應(yīng)的 .o 文件內(nèi)的符號(hào),并重新將它們連同各自的CRC校驗(yàn)寫入到頂層目錄中的文件 Modules.symvers 內(nèi)。所以最后該文件內(nèi)不僅包含基本內(nèi)核的符號(hào)及CRC校驗(yàn),還包括各內(nèi)部模塊所導(dǎo)出的符號(hào)及CRC校驗(yàn),在結(jié)果上是前面處理 vmlinux.o 時(shí)所生成的 Modules.symvers 的超集;
b) 針對各個(gè)內(nèi)部模塊,生成對應(yīng)的 *.mod.c 文件。生成 *.mod.c 文件的代碼在 modpost.c 文件的main函數(shù)中:
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_* 函數(shù)當(dāng)中,由于超出本問主題范圍,我們在這里不對它們詳加闡述。你可自行查看代碼,并參與我們在 mail list 中的討論。我們這里看下它生成的 *.mod.c 文件的內(nèi)容,我們以目錄 .../net/wireless/ 下的模塊 cfg80211.ko 為例(由于 s3c2410_defconfig 的默認(rèn)配置將變量 CONFIG_CFG80211 設(shè)置為M,所以根據(jù)該目錄下 Makefile 的內(nèi)容,構(gòu)建系統(tǒng)會(huì)生成模塊 cfg80211.ko)。為了完整的說明 *.mod.c 文件的內(nèi)容,我們特意修改了 .../.config 配置文件,將 CONFIG_MODVERSIONS 及 CONFIG_MODULE_SRCVERSION_ALL 兩變量設(shè)置為y。也就是打開了內(nèi)核的 Module versioning 功能。我們列出文件 cfg80211.mod.c 的內(nèi)容(有刪減):
該文件大部分的代碼是定義一些變量,并將其放在三個(gè)不同的 elf section 內(nèi)(后面構(gòu)建系統(tǒng)會(huì)編譯這個(gè) .mod.c 形成對象文件,鏈接進(jìn) .ko):
a) 定 義struct module結(jié)構(gòu)變量 __this_module,并將其放在 .gnu.linkonce.this_module section 中。在將模塊加載進(jìn)運(yùn)行著的內(nèi)核時(shí),內(nèi)核負(fù)責(zé)將這個(gè)對象加到內(nèi)部的modules list中。modules list 是內(nèi)核維護(hù)所有已加載模塊的一個(gè)雙向鏈表(更多請看:http:///lkml/2002/12/27/16)。
b) 定 義 struct modversion_info 結(jié)構(gòu)數(shù)組 ____versions,并將其放到 __versions sectiong 中。該數(shù)組中存放的都是該模塊中使用到,但沒被定義的符號(hào),也就是所謂的 unresolved symbol,它們或在基本內(nèi)核中定義,或在其他模塊中定義,內(nèi)核使用它們來做 Module versioning。注意其中的 module_layout 符號(hào),這是一個(gè) dummy symbol。內(nèi)核使用它來跟蹤不同內(nèi)核版本關(guān)于模塊處理的相關(guān)數(shù)據(jù)結(jié)構(gòu)的變化。當(dāng)一個(gè)模塊在A版本的內(nèi)核中編譯后,又在另外一個(gè)B版本的內(nèi)核中加載,如 果兩個(gè)內(nèi)核中處理modules的那些數(shù)據(jù)結(jié)構(gòu)體定義發(fā)生變化了,那內(nèi)核就拒絕繼續(xù)做其他 Module versioning 工作,也就是拒絕加載模塊。
符號(hào) module_layout 在文件 .../kernel/module.c 中被定義成一個(gè)函數(shù):
#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
該函數(shù)函數(shù)體為空,但卻有很多的參數(shù)類型。為什么?就是因?yàn)閮?nèi)核要用它來跟蹤 module/modversion_info/kernel_param/kernel_symbol/marker/tracepoint 等結(jié)構(gòu)體定義變化。那如何跟蹤這種變化呢?內(nèi)核會(huì)和處理其他的符號(hào)一樣,用這個(gè)函數(shù)原型做一次CRC校驗(yàn), 產(chǎn)生校驗(yàn)和。將其放如 *.mod.c 的__versions section中,待在模塊加載時(shí),拿其與保存在正運(yùn)行的內(nèi)核中的CRC進(jìn)行比較,如果不同,就拒絕進(jìn)一步加載模塊。加載模塊時(shí)內(nèi)核對此項(xiàng)的檢查代碼在 .../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);
}
函數(shù) check_version 就做真正的檢查工作,檢查不通過,內(nèi)核會(huì)報(bào)出著名的錯(cuò)誤信息:disagrees about version of symbol module_layout。
b) 最后,.mod.c 中會(huì)將很多信息塞進(jìn) .modinfo section 中,包括:vermagic字符串,模塊依賴信息,srcversion信息等等(還有其他很多信息)。我們以vermagic來舉例分析。
宏 MODULE_INFO 定義在 .../include/linux/module.h 中:
/* Generic info of form tag = "info" */
#define MODULE_INFO(tag, info) __MODULE_INFO(tag, tag, info)
而 __MODULE_INFO 則定義在 .../include/linux/moduleparam.h 中:
#define ___module_cat(a,b) __mod_ ## a ## b
#define __module_cat(a,b) ___module_cat(a,b)
#define __MODULE_INFO(tag, name, info) \
static const char __module_cat(name,__LINE__)[] \
__used \
__attribute__((section(".modinfo"),unused)) = __stringify(tag) "=" info
其中 __stringify 又是定義在 .../include/linux/stringify.h 文件中的宏:
#define __stringify_1(x...) #x
#define __stringify(x...) __stringify_1(x)
所以,綜上所述,當(dāng)你在一個(gè)c程序文件的第21行寫下這樣一條語句后:MODULE_INFO(pppp, "qqq"); 那么經(jīng)過C預(yù)處理,就會(huì)展開成:
static const char __mod_pppp21[] __used __attribute__((section(".modinfo"),unused)) = "pppp" "=" "qqq";
實(shí)際上,就是定義了一個(gè)名為 __mod_pppp21 的字符數(shù)組,將其初始化成字符串 "pppp=qqq" 形式后放入 .modinfo section 中。再來看宏 VERMAGIC_STRING 的定義:它定義在文件.../include/linux/vermagic.h 中:
/* Simply sanity version stamp for modules. */
#ifdef CONFIG_SMP
#define MODULE_VERMAGIC_SMP "SMP "
#else
#define MODULE_VERMAGIC_SMP ""
#endif
#ifdef CONFIG_PREEMPT
#define MODULE_VERMAGIC_PREEMPT "preempt "
#else
#define MODULE_VERMAGIC_PREEMPT ""
#endif
#ifdef CONFIG_MODULE_UNLOAD
#define MODULE_VERMAGIC_MODULE_UNLOAD "mod_unload "
#else
#define MODULE_VERMAGIC_MODULE_UNLOAD ""
#endif
#ifdef CONFIG_MODVERSIONS
#define MODULE_VERMAGIC_MODVERSIONS "modversions "
#else
#define MODULE_VERMAGIC_MODVERSIONS ""
#endif
#ifndef MODULE_ARCH_VERMAGIC
#define MODULE_ARCH_VERMAGIC ""
#endif
#define VERMAGIC_STRING \
UTS_RELEASE " " \
MODULE_VERMAGIC_SMP MODULE_VERMAGIC_PREEMPT \
MODULE_VERMAGIC_MODULE_UNLOAD MODULE_VERMAGIC_MODVERSIONS \
MODULE_ARCH_VERMAGIC
可見,機(jī)器是否為SMP,內(nèi)核是否配置為搶占式,是否不允許模塊卸載以及 Module versioning功能是否開啟等等,都影響著 VERMAGIC_STRING 的取值。當(dāng)加載模塊到內(nèi)核中時(shí),內(nèi)核也會(huì)檢查 vermagic 是否有變化。如果不一樣,內(nèi)核照樣不允許該模塊的加載。檢查代碼在 .../kernel/module.c 中:
modmagic = get_modinfo(sechdrs, infoindex, "vermagic");
/* This is allowed: modprobe --force will invalidate it. */
if (!modmagic) {
err = try_to_force_load(mod, "bad vermagic");
if (err)
goto free_hdr;
} else if (!same_magic(modmagic, vermagic, versindex)) {
printk(KERN_ERR "%s: version magic '%s' should be '%s'\n",
mod->name, modmagic, vermagic);
err = -ENOEXEC;
goto free_hdr;
}
內(nèi)核先取得存儲(chǔ)在模塊中的 magic 字符串(由 .mod.c 編譯連接到模塊中)放在 modmagic 中,再用 same_magic 函數(shù)去和內(nèi)核中保存好的 magic 字符串比較。比較不一致時(shí),內(nèi)核就會(huì)報(bào)錯(cuò),從而拒絕該模塊的加載。
*.mod.c 文件生成之后,如果 KBUILD_MODPOST_NOFINAL 定義過,那對 _modpost 的處理就算結(jié)束了,否則因?yàn)?_modpost 要依賴于 $(modules),構(gòu)建系統(tǒng)還要負(fù)責(zé)構(gòu)建出各個(gè)內(nèi)部模塊(.ko)。從 .../scripts/Makefile.modpost 中找到處理 $(modules) 的相關(guān)規(guī)則:
$(modules): %.ko :%.o %.mod.o FORCE
$(call if_changed,ld_ko_o)
這是一條靜態(tài)匹配規(guī)則,可以看出內(nèi)部模塊文件 *.ko 要依賴于同名的 *.o 和 同名的 *.mod.o 。同名*.o已經(jīng)在第一階段處理 vmlinux-dirs 時(shí)準(zhǔn)備妥當(dāng),但是同名*.mod.o還未生成,所以構(gòu)建系統(tǒng)必須用下面的規(guī)則來生成它:
由處理 $(modules) 的規(guī)則看出,生成 *.mod.o 后,構(gòu)建系統(tǒng)使用變量 cmd_ld_ko_o 定義的命令來將同名*.o和同名*.mod.o鏈接成*.ko:
# Step 6), final link of the modules
quiet_cmd_ld_ko_o = LD [M] $@
cmd_ld_ko_o = $(LD) -r $(LDFLAGS) $(LDFLAGS_MODULE) -o $@ \
$(filter-out FORCE,$^)
好,至此,所有內(nèi)部模塊均已構(gòu)建完畢。是時(shí)候討論 zImage 的處理了。關(guān)于另外一種模塊,即外部模塊,我們在討論完 zImage 的處理后再來討論。