Vmlinux
Vmlinux的目标定义在顶层Makefile文件中
vmlinux: scripts/link-vmlinux.sh vmlinux_prereq $(vmlinux-deps) FORCE +$(call if_changed,link-vmlinux) |
If_changed是定义在scripts/Kbuild.include中的函数
# Execute command if command has changed or prerequisite(s) are updated. if_changed = $(if $(strip $(any-prereq) $(arg-check)), \ @set -e; \ $(echo-cmd) $(cmd_$(1)); \ printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:) |
核心的代码是 $(cmd_$(1)) , 展开之后$(cmd_link-vmlinux), 也就是调用cmd_link-vmlinux函数。cmd_link-vmlinux函数的定义在顶层Makefile
# Final link of vmlinux with optional arch pass after final link cmd_link-vmlinux = \ $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) ; \ $(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true) |
第一行代码中$<代表第一个依赖项, 这行代码展开之后是
sh scripts/link-vmlinux.sh $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) |
其实就是调用scripts/link-vmlinux.sh这个脚本来完成vmlinux的构建工作, 这个脚本的分析就不在这里展开。 vmlinux生成时需要链接内核各个目录下built-in.o文件,所以各个目录built-in.o文件需要提前编译出来,而这些.o文件是通过一个叫$(vmlinux-dirs)定义的伪目标来驱动产生的 。
$(vmlinux-dirs)
$(vmlinux-dirs)伪目标定义在顶层Makefile中
$(vmlinux-dirs): prepare scripts $(Q)$(MAKE) $(build)=$@ |
$(vmlinux-dirs)的值展开后就是内核源文件的各个子目录
例如
init usr arch/arm/vfp arch/arm/kvm arch/arm/vdso arch/arm/kernel arch/arm/mm arch/arm/common arch/arm/probes arch/arm/net arch/arm/crypto arch/arm/firmware arch/arm/mach-sunxi kernel certs mm fs ipc security crypto block drivers sound firmware net arch/arm/lib lib virt |
而构建命令$(Q)$(MAKE) $(build)=$@,其中的$(build)定义在scripts/Kbuild.include文件中。
### # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj= # Usage: # $(Q)$(MAKE) $(build)=dir build := -f $(srctree)/scripts/Makefile.build obj |
$@代表当前的目标项, 以init目录为例, 展开后就是
make -f ./scripts/Makefile.build obj=init |
所以这里通过使用./scripts/Makefile.build编译脚本, 传递需要编译的目录作为参数, 来分别构建需要构建的目录下的built-in.o
Makefile.build
这个脚本的默认目标是__build
# We keep a list of all modules in $(MODVERDIR) __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \ $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \ $(subdir-ym) $(always) @: |
其中依赖项$(builtin-target)的定义是
ifneq ($(strip $(obj-y) $(obj-m) $(obj-) $(subdir-m) $(lib-target)),) builtin-target := $(obj)/built-in.o endif |
所以只要这些$(obj-y) $(obj-m) $(obj-) $(subdir-m) $(lib-target))变量的值不为空的话,$(obj)/built-in.o就会成为一个需要构建的依赖项。
$(builtin-target): $(obj-y) FORCE $(call if_changed,link_o_target) |
$(call if_changed,link_o_target)展开之后就是调用命令
# If the list of objects to link is empty, just create an empty built-in.o cmd_link_o_target = $(if $(strip $(obj-y)),\ $(cmd_make_builtin) $@ $(filter $(obj-y), $^) \ $(cmd_secanalysis),\ $(cmd_make_empty_builtin) $@) |
核心代码是$(cmd_make_builtin) $@ $(filter $(obj-y), $^) ,以init目录的编译为例, 展开后是
rm -f init/built-in.o; arm-linux-ar rcSTPD init/built-in.o init/main.o init/version.o init/mounts.o init/initramfs.o init/calibrate.o init/init_task.o |
$@代表目标init/built-in.o, $(filter $(obj-y), $^)其实就是$(obj-y), Makefile.build会包含要编译目录下的Makefile文件
# The filename Kbuild has precedence over Makefile kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile) include $(kbuild-file) |
obj-y就是在init目录下的Makefile文件里面定义的
obj-y := main.o version.o mounts.o ifneq ($(CONFIG_BLK_DEV_INITRD),y) obj-y += noinitramfs.o else obj-$(CONFIG_BLK_DEV_INITRD) += initramfs.o endif obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o ifneq ($(CONFIG_ARCH_INIT_TASK),y) obj-y += init_task.o endif |
而各个.o文件的编译规则定义在
# Built-in and composite module parts $(obj)/%.o: $(src)/%.c $(recordmcount_source) $(objtool_dep) FORCE $(call cmd,force_checksrc) $(call if_changed_rule,cc_o_c) |
$(call if_changed_rule,cc_o_c)展开后调用rule_cc_o_c
define rule_cc_o_c $(call echo-cmd,checksrc) $(cmd_checksrc) \ $(call cmd_and_fixdep,cc_o_c) \ $(call echo-cmd,objtool) $(cmd_objtool) \ $(cmd_modversions_c) \ $(call echo-cmd,record_mcount) $(cmd_record_mcount) endef |
核心代码$(call cmd_and_fixdep,cc_o_c), 其中cmd_and_fixdep定义是
cmd_and_fixdep = \ $(echo-cmd) $(cmd_$(1)); \ scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(dot-target).tmp;\ rm -f $(depfile); \ mv -f $(dot-target).tmp $(dot-target).cmd; |
最后展开后还是调用 cmd_cc_o_c
cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< |
$(c_flags)的定义在scripts/Makefile.lib文件中
c_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ $(__c_flags) $(modkern_cflags) \ $(basename_flags) $(modname_flags) |
__c_flags的相关定义在
__c_flags = $(if $(obj),$(call addtree,-I$(src)) -I$(obj)) \ $(call flags,_c_flags) |
orig_c_flags = $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) $(KBUILD_SUBDIR_CCFLAGS) \ $(ccflags-y) $(CFLAGS_$(basetarget).o) _c_flags = $(filter-out $(CFLAGS_REMOVE_$(basetarget).o), $(orig_c_flags)) |
在编译的目录下面Makefile文件也可以修改$(ccflags-y) 调整编译参数
ccflags-y := -fno-function-sections -fno-data-sections |