分类: LINUX
2014-08-11 13:38:27
原文地址:Linux内核构建系统之七 作者:kyok520
通过前面的分析,我们已经知道,在 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 += 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 目标依赖于 $(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 的使用信息,你可以参考内核开发邮件列表中的内容,在这里可以看到:。另外你也可以查看 module-init-tools 包git的修订记录:
上面命令部分中最关键的就是接下来那一行:$(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,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
似曾相识对吧?没错,我们在前面讨论 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 是内核维护所有已加载模块的一个双向链表(更多请看:)。
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 中:
/* 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)
所以,综上所述,当你在一个c程序文件的第21行写下这样一条语句后:MODULE_INFO(pppp, "qqq"); 那么经过C预处理,就会展开成:
static const char __mod_pppp21[] __used __attribute__((section(".modinfo"),unused)) = "pppp" "=" "qqq";
实际上,就是定义了一个名为 __mod_pppp21 的字符数组,将其初始化成字符串 "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
可见,机器是否为SMP,内核是否配置为抢占式,是否不允许模块卸载以及Module versioning功能是否开启等等,都影响着 VERMAGIC_STRING 的取值。当加载模块到内核中时,内核也会检查 vermagic 是否有变化。如果不一样,内核照样不允许该模块的加载。检查代码在 .../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; }
内核先取得存储在模块中的 magic 字符串(由 .mod.c 编译连接到模块中)放在 modmagic 中,再用 same_magic 函数去和内核中保存好的 magic 字符串比较。比较不一致时,内核就会报错,从而拒绝该模块的加载。
*.mod.c 文件生成之后,如果 KBUILD_MODPOST_NOFINAL 定义过,那对 _modpost 的处理就算结束了,否则因为 _modpost 要依赖于 $(modules),构建系统还要负责构建出各个内部模块(.ko)。从 .../scripts/Makefile.modpost 中找到处理 $(modules) 的相关规则:
$(modules): %.ko :%.o %.mod.o FORCE $(call if_changed,ld_ko_o)
这是一条静态匹配规则,可以看出内部模块文件 *.ko 要依赖于同名的 *.o 和 同名的 *.mod.o 。同名*.o已经在第一阶段处理 vmlinux-dirs 时准备妥当,但是同名*.mod.o还未生成,所以构建系统必须用下面的规则来生成它:
由处理 $(modules) 的规则看出,生成 *.mod.o 后,构建系统使用变量 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,$^)
好,至此,所有内部模块均已构建完毕。是时候讨论 zImage 的处理了。关于另外一种模块,即外部模块,我们在讨论完 zImage 的处理后再来讨论。