对技术执着
分类: LINUX
2015-03-14 17:24:59
原文地址:Linux内核构建系统之九 作者:kyok520
这个系列的上一片文章介绍了为讲述构建目标而举的第一个例子:"make ARCH=arm CROSS_COMPILE=arm-linux-"。接下来,让我们来看看另外一个例子,也就是编译外部模块的命令:"make ARCH=arm CROSS_COMPILE=arm-linux- -C KERNELDIR M=dir"。
关于包含在该命令中的两个选项 "-C" 和 "M",我们在前面已经有所介绍了。"-C"用来使make工具进入某个目录下面去make。这里是让make工具进入到内核源码所在目录KERNELDIR下面,去利用该目录下的 Makefile进行make处理。如果我们是为当前正在运行着的内核继续外部模块的编译,那我们需要设置 KERNELDIR 为:/lib/modules/`uname -r`/build。因为该/lib/modules/`uname -r`/build通常会在内核安装过程中软连接到内核代码所在目录。
选项M是用来指定外部模块代码所在目录的。所谓外部模块,就是指代码并非包含在内核源码树中的那些可加载模块。构建系统允许我们使用两种手段来指定外部模块代码所在目录。除了在命令行里使用M选项外,还可以在命令行中使用另外一个选项SUBDIR,但是这是过时的做法。如果你在命令行内既指定了M选项,又使用了SUBDIR选项,那内核构建系统会优先考虑使用M选项所指定的那个值。这可以从顶层Makefile中的下面代码中看出来:
# Use make M=dir to specify directory of external module to build # Old syntax make ... SUBDIRS=$PWD is still supported # Setting the environment variable KBUILD_EXTMOD take precedence ifdef SUBDIRS KBUILD_EXTMOD ?= $(SUBDIRS) endif ifeq ("$(origin M)", "command line") KBUILD_EXTMOD := $(M) endif
上面代码中,将外部模块所在目录赋值给make中的变量:KBUILD_EXTMOD。所以,如果我们愿意,我们也可以直接在 make 命令行中使用 KBUILD_EXTMOD 来设置。
需要注意的是,在进行外部模块的编译之前,内核源代码树中必须存在有 .../include/config/auto.conf 文件,因为我们在框架的G2部分中看到这样的代码:
# external modules needs include/linux/autoconf.h and include/config/auto.conf # but do not care if they are up-to-date. Use auto.conf to trigger the test PHONY += include/config/auto.conf include/config/auto.conf: $(Q)test -e include/linux/autoconf.h -a -e $@ || ( \ echo; \ echo " ERROR: Kernel configuration is invalid."; \ echo " include/linux/autoconf.h or $@ are missing."; \ echo " Run 'make oldconfig && make prepare' on kernel src to fix it."; \ echo; \ /bin/false)
我们可以在一个配置过的内核树中执行命令:"make ... prepare" 来生成 auto.conf 文件。另外编译外部模块也要求使用一些工具程序,譬如 .../scripts/mod 目录下面的 modpost 程序,你可以在一个配置过的内核树中执行命令:"make ... scripts" 来生成这些工具。一般情况下,如果你已经在你的内核树中编译过内核,那上面这两个条件都已经得到满足。因为编译内核通常需要比较长的时间,假如在编译外部模块之前等不及,或者老板在狠命的催你,那你就用这样的命令来准备一下你的内核树环境:"make ... modules_prepare"。你看makefile中代码的话,你会注意到这个命令先后make了prepare和scripts两个目标,所以能有一次执行上述两个命令的效果。当然,这个命令也有不好的地方,那就是它不会帮我们在顶层目录下生成 Modules.symvers 文件,即使设置了 CONFIG_MODVERSIONS 选项,也是如此。所以,你如果想起用 Module Versioning 功能的话,你最好还是花点时间先编译一下内核。
好,接下来注意看看我们例子二中的命令:"make ARCH=arm CROSS_COMPILE=arm-linux- -C KERNELDIR M=dir"。并没有指定所要 make 的目标是什么,但是构建系统会给它准备一个默认的目标 _all,这我们前面已经有所接触。但是由于设置了 KBUILD_EXTMOD,构建系统又会在下面的代码中让 _all 依赖于 modules。所以命令 "make ARCH=arm CROSS_COMPILE=arm-linux- -C KERNELDIR M=dir" 其实就相当于 "make ARCH=arm CROSS_COMPILE=arm-linux- -C KERNELDIR M=dir modules":
# If building an external module we do not care about the all: rule # but instead _all depend on modules PHONY += all ifeq ($(KBUILD_EXTMOD),) _all: all else _all: modules endif
这在前面已经看到过,modules 目标的处理也已经讨论过。但是注意,这里的这个 modules 并非之前的那个 modules。之前的那个 modules 是构建系统为了处理内部模块而准备的,它位于顶层 Makefile 的E1部分中。而此处的这个 modules 则是为了处理外部模块而准备的,它位于 E2部分中。前面已经说过 E1部分 和 E2部分 就是因为KBUILD_EXTMOD的取值而分开的。我们把 E2部分中的那个 modules 规则取出来:
module-dirs := $(addprefix _module_,$(KBUILD_EXTMOD)) PHONY += $(module-dirs) modules $(module-dirs): crmodverdir $(objtree)/Module.symvers echo 'KBUILD_MODULES := $(KBUILD_MODULES)' > modules_builtin.cmd echo 'KBUILD_BUILTIN := $(KBUILD_BUILTIN)' >> modules_builtin.cmd $(Q)$(MAKE) $(build)=$(patsubst _module_%,%,$@) modules: $(module-dirs) @$(kecho) ' Building modules, stage 2.'; $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost
比较这里处理外部模块的 modules 规则和前面处理内部模块的 modules规则。差别是显而易见的,但是也有共同点。这些异同主要体现在以下几个方面:
a) 从整体上可以看出,无论内部模块,还是外部模块的处理。都需要经过两个阶段的处理。
b) 内部模块依赖于 vmlinux-dirs 目标的处理,来完成第一阶段;而外部模块的处理则依赖于 modules-dirs 目标的处理。
c) 内部模块的 modules 目标处理所有内部模块;而外部模块的 modules 目标只处理 KBUILD_EXTMOD 目录中的外部模块。
d) 都会生成 .mod 文件,并将其放在 MODVERDIR 目录中,也就是 .tmp_version 目录;只是 .tmp_version 目录的位置不同。
e) 都会使用 .../scripts/mod/modpost 来生成 .mod.c 文件,以及处理 Module.symvers 文件。
还是让我们带着这些异同查看代码吧,也许,你会在看代码过程中概括出来更多的异同之处。modules 依赖于 $(modules-dirs),也就是依赖于 _module_dir(dir为外部模块代码所在的目录名称)。$(modules-dirs)目标的第一个依赖是 cromodverdir 目标,它的处理规则是:
PHONY += crmodverdir crmodverdir: $(cmd_crmodverdir)
而 cmd_crmodverdir 变量的定义为:
# Create temporary dir for module support files # clean it up only when building all modules cmd_crmodverdir = $(Q)mkdir -p $(MODVERDIR) \ $(if $(KBUILD_MODULES),; rm -f $(MODVERDIR)/*)
再追踪下去,变量 MODVERDIR 的定义我们前面已经见过。当定义有 KBUILD_EXTMOD 时,它的取值是 dir/.tmp_versions(其中dir为外部模块代码所在目录)。
$(modules-dirs)目标的第二个依赖是 $(objtree)/Module.symvers。因为变量 objtree 被定义成了 $(CURDIR),也就是 make 命令"-C"选项带进去的内核源代码树目录 KERNELDIR。所以我们说外部模块的处理,最好有一个东西作为保证,那就是在内核顶层目录下面存在文件 Modules.symvers。否则构建系统会按照下面规则而报出警告:
PHONY += $(objtree)/Module.symvers $(objtree)/Module.symvers: @test -e $(objtree)/Module.symvers || ( \ echo; \ echo " WARNING: Symbol version dump $(objtree)/Module.symvers"; \ echo " is missing; modules will have no dependencies and modversions."; \ echo )
回到处理 $(module-dirs) 的规则上面来。接下来,构建系统要处理命令:"$(Q)$(MAKE) $(build)=$(patsubst _module_%,%,$@)"。因为此时 $@ 为_module_dir,所以该命令可简化成:"make -f scripts/Makefile.build obj=dir"。这其实和我们前面处理 $(vmlinux-dirs) 目标的命令是等同的,其最终效果是要用下面这条规则去处理 .../scripts/Makefile.build 文件中的 __build 目标:
__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \ $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \ $(subdir-ym) $(always) echo 'KBUILD_MODULES := $(KBUILD_MODULES)' >> modules_builtin.cmd echo 'KBUILD_BUILTIN := $(KBUILD_BUILTIN)' >> modules_builtin.cmd @:
这条规则的细节,我这里就不再关注了。隐藏在其背后的本质是构建系统像对待内核代码树中的子目录那样去对待一个代码树之外的目录。这条规则执行的最后结果,是在外部模块代码所在的目录中构建出组成外部模块所需的 .o 文件以及 .mod 文件。注意 .mod 文件的存放位置,是 dir/.tmp_versions/,而非内核源码树顶层目录中的 .tmp_versions/。
回到处理外部模块 modules 的规则上面来,既然 modules 的依赖 $(module-dirs) 处理完毕,构建系统就会使用命令:$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost 来完成外部模块处理的第二阶段。其大体流程和内部模块的处理差不多,只是在调用 .../scripts/mod/modpost 的时候,所使用的参数有所不同,我们列出 .../scripts/Makefile.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 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
由于额外定义了 KBUILD_EXTMOD,所以构建系统处理 __modpost 的实际命令便是:
和之前构建内部模块时所用的命令(见下)相比,最重要的区别就在于其多了两个选项: -i 和 -I,并且选项 -o 的参数也不同了。
这是因为前面构建内部模块时,modpost是取出 vmlinux 及各内部模块中符号(包括CRC值),写入到顶层内核源码目录中的文件 Modules.symvers 中去的。而此时,构建系统构建的是外部模块,它却是要读入顶层内核源码目录中的 Module.symvers。这是因为构建系统在构建外部模块的时候,它会经常碰到一些在该外部模块中没有定义的符号,它要去检查这些符号是否为内核或某个内部模块所导出。如果是的话,那OK没问题,否则如果有的符号既没从基本内核中导出,又没从任一内部模块中导出,构建系统就会报错说 unresolve symbol。
modpost在读入内核顶层目录中的 Module.symvers 的同时,它也在外部模块所在目录中输出一个新的 Module.symvers,里面记载该外部模块自己本身所导出的符号(包括CRC值)。注意,在构建外部模块的时候,外部模块本身所在目录下就有一个 Module.symvers 的话,它也会读入。这是为了 module stacking 而准备的。因为一个外部模块A中,除了使用基本内核以及内部模块所导出来的符号外,还有可能使用另外的外部模块B所导出的符号。这样的话,我们在编译构建外部模块A的时候, 就可以把构建外部模块B所产生的那个 Module.symvers 文件放到模块A所在目录下面。
好,至此,我们的第二个例子,即构建系统如何处理外部模块已经讨论完毕。也就意味着框架中:"E部分-对vmlinux、modules等构建目标以及和.config无关目标的处理"讨论完毕。
接下来,我们还是来看框架中的G部分。在内核开发过程中,需要的经常不是整个内核的编译,而是经常需要重新编译生成某个单一的对象文件、某一个单一的目录、某一个单一的内核内部模块。这个时候,我们开发者就没有必要去动用 "make vmlinux" 之类的牛刀去重新编译整个内核,所以内核构建系统为支持此目的实现了对这些 Single target 的处理。作为内核构建系统的用户来讲,你只需要使用这些命令去构建:
当然,在做这些构建的时候,必须先完成内核的配置(kconfig),因为它们的执行至少要求有 .config 的存在。另外,构建系统甚至还允许编译外部模块中的某个对象文件,比方这样:
make ARCH=arm CROSS_COMPILE=arm-linux- -C ~/linux-2.6.31 M=`pwd` hello.o
G部分中和 Single target 相关的这些代码比较简单,这里就不再详细介绍,你自己就可以看懂。
到目前为止,内核构建系统的大部分重要的地方都已讨论完毕,惟独有一个很关键的方面还没讨论完全,那就是依赖关系的处理。我们在下面这个系列的最后一篇文章中讨论。