内核构建系统之所以要在链接 vmlinux 之前,去链接出vmlinux.o。其原因并不是要将 vmlinux.o 链接进 vmlinux,而是要在链接 vmlinux.o 的过程中做完两个动作:
(1)其中第一个动作,由于明白它需要很多elf文件格式的知识,而本文重在解释内核构建系统的原理与实现,所以要在这里详细讲解就显得不妥,等以后有机会了再写文章来讨论。有兴趣的同学,可以先行查看 scripts/mod/modpost.c 中的代码。
(2)而第二个动作中提到的 Modules.symvers 文件,其中包含内核及内部模块所导出的各种符号及其相关CRC校验值。构建系统会在编译 vmlinux.o 的过程中将原始内核文件(vmlinux)所导出的符号记录在这个文件中,在后面讨论内部模块的第二步处理的时候,我们会发现它也会将各内部模块所导出的符号记录在这个文件中。
所谓符号,你可以先将其理解成是基本内核或者内部模块导出来的,供其他人(通常是驱动程序)使用的函数或者变量(不完全准确)。等你以后开发驱动程序进行调试的时候,你会发现有这么一个dump文件会有多么方便。构建系统在编译外部模块的时候也要使用这个文件。
为了不脱离本文主题,我在下面讨论vmlinu.o编译的过程中,特意将上面两个动作中和内核构建无关的部分略去,以后我们会专门写另外的文章来讨论。
vmlinux.o
# 顶层目录的Makefile中
modpost-init := $(filter-out init/built-in.o, $(vmlinux-init))
vmlinux.o: $(modpost-init) $(vmlinux-main) FORCE
$(call if_changed_rule,vmlinux-modpost)
filter-out是一个反过滤函数,主要是过滤掉$(vmlinux-init)中的init/built-in.o,此时:modpost-init :=arch/arm/kernel/head.o arch/arm/kernel/init_task.o init/built-in.o,至于为什么依赖要去掉这个文件,暂时还未找到原因,日后找到后再来批注。
vmlinux.o目标的的生成规则是调用 if_changed_rule函数,入参为vmlinux-modpost。(如果不懂if_changed_rule函数的使用,请移步到内核源码中单个.o文件的编译过程(六)中,里面有一小节有讲到这个函数)这里构建系统要调用 rule_vmlinux-modpost 变量所定义的命令,定义如下:
# 在scripts/Kbuild.include中
# printing commands
cmd = @$(echo-cmd) $(cmd_$(1))
...
# Name of target with a '.' as filename prefix. foo/bar.o => foo/.bar.o
dot-target = $(dir $@).$(notdir $@)
-------------------------------------------------------------------------------------------------------
# 顶层目录的Makefile中
# Do modpost on a prelinked vmlinux. The finally linked vmlinux has
# relevant sections renamed as per the linker script.
quiet_cmd_vmlinux-modpost = LD $@
cmd_vmlinux-modpost = $(LD) $(LDFLAGS) -r -o $@ \
$(vmlinux-init) --start-group $(vmlinux-main) --end-group \
$(filter-out $(vmlinux-init) $(vmlinux-main) FORCE ,$^)
define rule_vmlinux-modpost
:
+$(call cmd,vmlinux-modpost)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost $@
$(Q)echo 'cmd_$@ := $(cmd_vmlinux-modpost)' > $(dot-target).cmd
endef
变量 rule_vmlinux-modpost 中定义的命令中,具体流程如下:
(1)首先调用 cmd_vmlinux-modpost 变量所定义的命令来链接生成 vmlinux.o 目标。
(2)然后构建系统就用 scripts/Makefile.modpost来调用 make。注意看第二条make命令中的目标为 $@,也就是说要make vmlinux.o。
(3)最后,它将构建 vmlinux.o 目标的命令保存到 .vmlinux.o.cmd 文件中。
1. $(call cmd,vmlinux-modpost)
调用cmd_vmlinux-modpost。链接生成vmlinux.o。实际执行的命令如下:
arm-linux-ld -EL -r -o vmlinux.o arch/arm/kernel/head.o arch/arm/kernel/init_task.o init/built-in.o --start-group usr/built-in.o arch/arm/nwfpe/built-in.o
arch/arm/vfp/built-in.o arch/arm/kernel/built-in.o arch/arm/mm/built-in.o arch/arm/common/built-in.o arch/arm/mach-s3c64xx/built-in.o arch/arm/plat-samsung/built-in.o
kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o arch/arm/lib/lib.a lib/lib.a arch/arm/lib/built-in.o
lib/built-in.o drivers/built-in.o sound/built-in.o firmware/built-in.o net/built-in.o --end-group
2. $ (Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost $@
展开后为:
make -f scripts/Makefile.modpost vmlinux.o
vmlinux.o的构建规则定义如下
# scripts/Makefile.modpost
quiet_cmd_kernel-mod = MODPOST $@
cmd_kernel-mod = $(modpost) $@
vmlinux.o: FORCE
$(call cmd,kernel-mod)
调用cmd函数,入参为kernel-mod(cmd函数定义见上面的代码)。实际上执行的是@ $(echo-cmd) $(cmd_ $(1))这么一句命令。它的作用同样就是打印执行的命令,然后执行这个命令—cmd_kernel-mod。实际执行的是 $(modpost) $@;其中 $@ =vmlinux.o, $(modpost)定义如下:
# scripts/Makefile.modpost
kernelsymfile := $(objtree)/Module.symvers
...
ifneq ($(KBUILD_BUILDHOST),$(ARCH))
cross_build := 1
endif
...
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 $(KBUILD_EXTMOD)$(KBUILD_MODPOST_WARN),-w) \
$(if $(cross_build),-c)
其中 $ (objtree)为空。且可以看出,其中包含有很多条件的判断。由于编译 vmlinux.o 时, CONFIG_MODVERSIONS,CONFIG_MODULE_SRCVERSION_ALL,KBUILD_EXTMOD,KBUILD_EXTRA_SYMBOLS,CONFIG_MARKERS,CONFIG_DEBUG_SECTION_MISMATCH 等等都没定义,并且我们做的是交叉编译,即 cross_build = 1。所以在 scripts/Makefile.modpost 中,处理 vmlinux.o 的命令就剩下以下三行有用的部分:
modpost = scripts/mod/modpost \
$(if $(KBUILD_EXTMOD),-i,-o) $(kernelsymfile) \
$(if $(CONFIG_DEBUG_SECTION_MISMATCH),,-S) \
$(if $(cross_build),-c)
# 最终展开形式:
modpost = scripts/mod/modpost -o Module.symvers -S -c
因此$ (Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost $@实际执行的是以下命令
scripts/mod/modpost -o /home/hh/linux-2.6.38/Module.symvers -S -c vmlinux.o
即它最终会调用modpost工具(该工具是由scripts/mod/modpost.c编译出来的),作用是利用modpost工具 来解析 vmlinux.o 对象文件,并将基本内核导出的所有符号都记录到文件 Module.symvers 中去。当然,这个命令附带完成的,还有前面所说到的检查 sections 是否 mis match 的工作。你可以先浏览看看 scripts/mod/modpost.c 中的代码。打开文件 Module.symvers,你会发现针对每个内核导出的符号(symbol),都会有一行,其格式和举例如下:
0x00000000 usb_serial_generic_submit_read_urb vmlinux EXPORT_SYMBOL_GPL
0x00000000 unregister_vt_notifier vmlinux EXPORT_SYMBOL_GPL
0x00000000 generic_file_splice_write vmlinux EXPORT_SYMBOL
0x00000000 set_anon_super vmlinux EXPORT_SYMBOL
0x00000000 kmem_cache_alloc vmlinux EXPORT_SYMBOL
0x00000000 __cond_resched_softirq vmlinux EXPORT_SYMBOL
0x00000000 i2c_put_adapter vmlinux EXPORT_SYMBOL
0x00000000 rtc_class_open vmlinux EXPORT_SYMBOL_GPL
0x00000000 scsi_sense_key_string vmlinux EXPORT_SYMBOL
...
注意,此时打开该文件后看到的符号CRC值都是0x00000000,那是因为我在配置的时并没有设置 CONFIG_MODVERSIONS。一旦设置过这个配置选项,就意味着打开了内核的 Module versioning功能。Module versioning 功能应用在我们使用模块的场合。如果Module versioning功能被打开的话,它会以每个导出符号的C原型声明作为输入,计算出对应的CRC校验值,保存在文件 Module.symvers 中。如此一来,内核在后面要加载使用模块的时候,会两相比较模块中的CRC值和保存下来的CRC值,如果发现不相等,内核就拒绝加载这个模块。
3. $ (Q)echo ‘cmd_$@ := $(cmd_vmlinux-modpost)’ > $(dot-target).cmd
$(dot-target)定义在scripts/Kbuild.include中,主要是定义一个文件名,.前面是目标的目录,后面是目标去掉目录之后的名字。实际执行时的打印如下:
echo 'cmd_vmlinux.o := arm-linux-ld -EL -r -o vmlinux.o
arch/arm/kernel/head.o arch/arm/kernel/init_task.o init/built-in.o --start-group usr/built-in.o
arch/arm/nwfpe/built-in.o arch/arm/vfp/built-in.o arch/arm/kernel/built-in.o arch/arm/mm/built-in.o
arch/arm/common/built-in.o arch/arm/mach-s3c64xx/built-in.o arch/arm/plat-samsung/built-in.o kernel/built-in.o
mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o block/built-in.o arch/arm/lib/lib.a
lib/lib.a arch/arm/lib/built-in.o lib/built-in.o drivers/built-in.o sound/built-in.o firmware/built-in.o net/built-in.o
--end-group ' > ./.vmlinux.o.cmd
补充:
保存*.cmd 有什么用?
将编译 vmlinux.o 的命令写入到.vmlinux.o.cmd 文件中保存起来,以便下次再编译内核时可以进行新旧命令的比较。以便下次再编译内核时可以进行新旧命令的比较。