周亦行
2014年11月
参考文档
② if_changed_rule/cc_o_c/any-prereq/arg-check
一、Makefile组成
(一)基本组成
- 顶层 Makefile
它是所有Makefile文件的核心,从总体上控制着内核的编译、连接
- arch/$(ARCH)/Makefile
对应体系结构的Makefile,它用来决定哪些体系结构相关的文件参与内核的生成,并提供一些规则来生成特定格式的内核映像
- scripts/Makefile.*
Makefile公用的通用规则、脚本等
- 子目录kbuild Makefiles
各级子目录的Makefile相对简单,被上一层Makefile.build调用来编译当前目录的文件。
- 顶层.config
配置文件,配置内核时生成。所有的Makefile文件(包括顶层目录和各级子目录)都是根据.config来决定使用哪些文件的
(二)通用规则
- Makefile.build
被顶层Makefile所调用,与各级子目录的Makefile合起来构成一个完整的Makefile文件,定义built-in.o、.lib以及目标文件.o的生成规则。这个Makefile文件生成了子目录的.lib、built-in.o以及目标文件.o
- Makefile.clean
被顶层Makefile所调用,用来删除目标文件等
- Makefile.lib
被Makefile.build所调用,主要是对一些变量的处理,比如说在obj-y前边加上obj目录
- Kbuild.include
被Makefile.build所调用,定义了一些函数,如if_changed、if_changed_rule、echo-cmd
(三)总结
- Linux内核Makefile体系核心的Makefile文件就两个:顶层Makefile、scripts/Makefile.build。
- 子目录中的Makefile、kbuild不是Makefile文件(完整的Makefile文件),只能算作是Makefile的包含文件。
- 顶层Makefile文件负责将各个目录生成的*.built-in.o、lib.a等文件连接到一起。而scripts/Makefile.build
包含子目录中的Makefile文件来生成这些.built-in.o、lib.a、.o等文件。
二、内核编译
以s3c6400编译uImage过程为例。一般来说内核的编译过程为
make ARCH=arm s3c6400_defconfig
make ARCH=arm menuconfig
make ARCH=arm -j2 CROSS_COMPILE=arm-linux-gnueabihf- uImage
三、目标文件
编译uImage,自然uImage就是目标文件。内核配置完成后,在顶层目录开始编译内核。但是,uImage却不是在顶层Makefile中定义,而是在arch/$(ARCH)/Makefile中定义。
顶层Makefile文件
534 include $(srctree)/arch/$(SRCARCH)/Makefile
SRCARCH := $(ARCH),即该变量等于架构名称,s3c6400为arm架构。
arch/arm/Makefile文件
300 BOOT_TARGETS = zImage Image xipImage bootpImage uImage
305 $(BOOT_TARGETS): vmlinux
306 $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/[email protected]
uImage依赖vmlinux。通过$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/[email protected]
指令编译生成。
- Q的定义:选择静态编译与否(是否打印编译信息)
顶层Makefile文件:
75 ifeq ($(KBUILD_VERBOSE),1)
76 quiet =
77 Q =
78 else
79 quiet=quiet_
80 Q = @
81 endif
- build:值为“-f scripts/Makefile.build
obj=”实际上就是调用子Makefile–scripts/Makefile.build,然后传递参数目标文件夹。
顶层Makefile文件
352 include $(srctree)/scripts/Kbuild.include
通过include的方式添加Kbuild.include文件。
Kbuild.include文件
170 ###
171 # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
172 # Usage:
173 # $(Q)$(MAKE) $(build)=dir
174 build := -f $(srctree)/scripts/Makefile.build obj
四、依赖推导
(一)依赖Vmlinux
顶层Makefile文件
897 # Externally visible symbols (used by link-vmlinux.sh)
898 export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
899 export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y)
900 export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
901 export LDFLAGS_vmlinux
902 # used by scripts/pacmage/Makefile
903 export KBUILD_ALLDIRS := $(sort $(filter-out arch/%,$(vmlinux-alldirs)) arch Documentation include samples scripts tools virt)
904
905 vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN)
906
907 # Final link of vmlinux
908 cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)
909 quiet_cmd_link-vmlinux = LINK [email protected]
910
911 # Include targets which we want to
912 # execute if the rest of the kernel build went well.
913 vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE
914 echo $(vmlinux-deps)
915 ifdef CONFIG_HEADERS_CHECK
916 $(Q)$(MAKE) -f $(srctree)/Makefile headers_check
917 endif
918 ifdef CONFIG_SAMPLES
919 $(Q)$(MAKE) $(build)=samples
920 endif
921 ifdef CONFIG_BUILD_DOCSRC
922 $(Q)$(MAKE) $(build)=Documentation
923 endif
924 +$(call if_changed,link-vmlinux)
跟参考博客略有出入,但内容是一致的。Vmlinux的依赖为vmlinux.lds,$(head-y),$(init-y),$(core-y),$(libs-y),$(drivers-y),$(net-y),FORCE
另外还有一个sh脚本link-vmlinux.sh
。代码echo $(vmlinux-deps)
是笔者插入的一行调试代码,用于分析依赖。
从vmlinux目标的生成规则来看,vmlinux是由最后一条命令完成的(因为其它命令的都是可选的)。
(二)Vmlinux依赖
1.vmlinux-lds
连接脚本。在编译之前先生成,最后的连接阶段会用的着。
2.head-y
arch/arm/Makefile文件
127 head-y := arch/arm/kernel/head$(MMUEXT).o
head-y最终等于arch/arm/kernel/head.o
3.init-y
顶层Makefile文件
558 init-y := init/
889 init-y := $(patsubst %/, %/built-in.o, $(init-y))
Patsubst函数将最后一个’/’后面的字符替换成built-in.o字符。init-y最终等于init/built-in.o。
4.drivers-y
分析过程基本与init-y的一致。drivers-y最终等于drivers/built-in.o、sound/built-in.o、firmware/built-in.o
5.net-y
分析过程基本与init-y的一致。net-y最终等于net/built-in.o
6.Libs-y
arch/arm/Makefile文件
276 libs-y := arch/arm/lib/ $(libs-y)
顶层Makefile文件
561 libs-y := lib/
893 libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
894 libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))
895 libs-y := $(libs-y1) $(libs-y2)
Libs-y最终等于arch/arm/lib/lib.a、lib/lib.a、arch/arm/lib/built-in.o、lib/built-in.o
7.core-y
分析过程基本与init-y的一致。Core-y最终等于usr/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/net/built-in.o、arch/arm/crypto/built-in.o、arch/arm/firmware/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。
8.FORCE
顶层Makefile文件
1592 PHONY += FORCE
1593 FORCE:
1594
1595 # Declare the contents of the .PHONY variable as phony. We keep that
1596 # information in a variable so we can use it in if_changed and friends.
1597 .PHONY: $(PHONY)
可见,FORCE是一个伪目标,其作用是保证伪目标的执行命令执行。而且,依赖伪目标的目标文件永远比伪目标文件旧。
9.link-vmlinux.sh
该脚本用于生成vmlinux。该文件为静态文件,不需要生成。具体生成过程后面分析。
(三)Vmlinux生成
前文提及,vmlinux的最终生成命令为“+$(call if_changed,link-vmlinux)”
。该命令的含义有:
- ‘+’,表示该命令结果不可忽略即引发编译终止,并且在make的“-n”、“-q”和“-q”等参数下执行。普及Makefile基础:‘-’代表可忽略即不会引发编译终止。
- Call函数,用于调用其他函数。这里其他函数是指if_changed,link-vmlinux为函数参数。
- $(call if_changed,link-vmlinux)的结果作为命令执行。
if_changed函数的原型如下。
Kbuild.include文件
209 ifneq ($(KBUILD_NOCMDDEP),1)
210 # Check if both arguments has same arguments. Result is empty string if equal.
211 # User may override this check using make KBUILD_NOCMDDEP=1
212 arg-check = $(strip $(filter-out $(cmd_$(1)), $(cmd_[email protected])) \
213 $(filter-out $(cmd_[email protected]), $(cmd_$(1))) )
214 else
215 arg-check = $(if $(strip $(cmd_[email protected])),,1)
216 endif
226 # Find any prerequisites that is newer than target or that does not exist.
227 # PHONY targets skipped in both cases.
228 any-prereq = $(filter-out $(PHONY),$?) $(filter-out $(PHONY) $(wildcard $^),$^)
232 if_changed = $(if $(strip $(any-prereq) $(arg-check)), 233 @set -e; 234 $(echo-cmd) $(cmd_$(1)); 235 printf ‘%s\n‘ ‘[email protected] := $(make-cmd)‘ > $(dot-target).cmd)
顶层Makefile文件
1577 targets := $(wildcard $(sort $(targets)))
1578 cmd_files := $(wildcard .*.cmd $(foreach f,$(targets),$(dir $(f)).$(notdir $(f)).cmd))
1579
1580 ifneq ($(cmd_files),)
1581 $(cmd_files): ; # Do not try to update included dependency files
1582 include $(cmd_files)
1583 endif
1.any-prereq
$?
表示所有比目标还要新的依赖文件。$^
表示所有的依赖文件。$(PHONY)
是自定变量,Makefile用来记录用到的伪目标。
Kbuild.include的228行中$(filter-out $(PHONY),$?)
先排除所有伪目标,然后得到文件更新列表;$(filter-out $(PHONY) $(wildcard $^),$^)
先得到文件缺失表,然后再排除所有伪目标。也就是说,变量any-prereq如果为空则依赖文件没有变动,如果非空则变动。
总结:变量any-prereq用来检查依赖文件是否变动。
2.arg-check
Kbuild.include的228行通过$(filter-out $(cmd_$(1)), $([email protected]))
排除上一个执行命令与当前执行命令的相同部分;$(filter-out $([email protected]), $(cmd_$(1)))
排除当前执行命令与上一个执行命令的相同部分。如果两次结果都为空,则执行命令没有变动。
命令执行时通过Kbuild.include的235行建立$(dot-target).cmd)
文件用于保存该命令,例如目标为vmlinux则建立.vmlinux.cmd文件。
编译时通过顶层Makefile的1582行将旧命令包含进来。例如.vmlinux.cmd的内容如下。
cmd_vmlinux := /bin/bash scripts/link-vmlinux.sh arm-linux-gnueabihf-ld -EL -p --no-undefined -X --build-id
正是生成目标vmlinux的命令。
总结:变量arg-check用来检查生成命令是否变动
3.生成命令
Kbuild.include的233行@set -e;
表示余下的命令如果出错了就直接返回,不再继续执行。234行$(echo-cmd)
用于显示执行的命令;$(cmd_$(1)
为执行命令,这里推导过程为$(cmd_$(1) => $(cmd_link-vmlinux) => /bin/bash scripts/link-vmlinux.sh arm-linux-gnueabihf-ld -EL -p --no-undefined -X --build-id
这里用到了前文提及到的脚本link-vmlinux.sh。这里其实笔者曾产生过一个小小的疑惑,link-vmlinux.sh并不知道根据那些依赖文件来生成vmlinux啊?其实在顶层Makefile文件中设置了如下环境变量。
顶层Makefile文件
897 # Externally visible symbols (used by link-vmlinux.sh)
898 export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
899 export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y) $(drivers-y) $(net-y)
900 export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
901 export LDFLAGS_vmlinux
另外,FORCE规则问题。笔者发现,执行make ARCH=arm -j2 CROSS_COMPILE=arm-linux-gnueabihf- zImage
时,vmlinux并没有重新生成。但是vmlinux的依赖包括FORCE啊?其实因为if_changed是一个if函数。当条件strip $(any-prereq) $(arg-check))
为FALSE的时候,函数返回空。也当然就没有重新生成。
(四)依赖vmlinux-deps
从前文可知,vmlinux需要生成的依赖为$(vmlinux-deps)
。
顶层Makefile文件
882 vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) 883 $(core-y) $(core-m) $(drivers-y) $(drivers-m) 884 $(net-y) $(net-m) $(libs-y) $(libs-m)))
927 $(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
929 # Handle descending into subdirectories listed in $(vmlinux-dirs)
930 # Preset locale variables to speed up the build process. Limit locale
931 # tweaks to this spot to avoid wrong language settings when running
932 # make menuconfig etc.
933 # Error messages still appears in the original language
934
935 PHONY += $(vmlinux-dirs)
936 $(vmlinux-dirs): prepare scripts
937 $(Q)$(MAKE) $(build)=[email protected]
上述代码中,927行表示$(vmlinux-deps)
所有的依赖为$(vmlinux-dirs)
。并且通过$(Q)$(MAKE) $(build)[email protected]
命令生成。而882行指明vmlinux-dir变量指代的目录。通过调试手段得到vmlinux-dirs具体的内容如下。
init、usr、arch/arm/vfp、arch/arm/kernel、arch/arm/mm、arch/arm/common、arch/arm/net、arch/arm/crypto、arch/arm/firmware、arch/arm/mach-s3c64xx、arch/arm/plat-samsung、kernel、mm、fs、ipc、security、crypto、block、drivers、sound、firmware、net、arch/arm/lib和lib
prepare scripts依赖不做分析详细分析(其实笔者也没看明白)。Prepare用于处理编译中间文件与源码分开存放,如果分开存放则建立相应的目录,并且创建生成include/config/kernel.release、include/linux/version.h include/linux/utsrelease.h 等文件。Scripts用于处理生成编译选项文件include/config/auto.conf。
(五)vmlinux-deps依赖
上文已经总结过vmlinux-deps依赖,不再分析。
(六)vmlinux-deps生成
前文已经分析得到build变量的内容为-f $(srctree)/scripts/Makefile.build obj
。可以得知顶层Makefile文件的936,937行可以推导成如下编译命令。
@make -f ./scripts/Makefile.build obj=init
@make -f ./scripts/Makefile.build obj=usr
@make -f ./scripts/Makefile.build obj=arch/arm/vfp
@make -f ./scripts/Makefile.build obj=arch/arm/kernel
@make -f ./scripts/Makefile.build obj=arch/arm/mm
@make -f ./scripts/Makefile.build obj=arch/arm/common
@make -f ./scripts/Makefile.build obj=arch/arm/net
@make -f ./scripts/Makefile.build obj=arch/arm/crypto
@make -f ./scripts/Makefile.build obj=arch/arm/firmware
@make -f ./scripts/Makefile.build obj=arch/arm/mach-s3c64xx
@make -f ./scripts/Makefile.build obj=arch/arm/plat-samsung
@make -f ./scripts/Makefile.build obj=kernel
@make -f ./scripts/Makefile.build obj=mm
@make -f ./scripts/Makefile.build obj=fs
@make -f ./scripts/Makefile.build obj=ipc
@make -f ./scripts/Makefile.build obj=security
@make -f ./scripts/Makefile.build obj=crypto
@make -f ./scripts/Makefile.build obj=block
@make -f ./scripts/Makefile.build obj=drivers
@make -f ./scripts/Makefile.build obj=sound
@make -f ./scripts/Makefile.build obj=firmware
@make -f ./scripts/Makefile.build obj=net
@make -f ./scripts/Makefile.build obj=arch/arm/lib
@make -f ./scripts/Makefile.build obj=lib
上述命令列表的各个命令过程都是类似的,一叶知秋。通过@make -f ./scripts/Makefile.build obj=drivers
的过程来讲述。
1.Makefile.build构成
scripts/Makefile.build文件
5 src := $(obj)
34 -include include/config/auto.conf
36 include scripts/Kbuild.include
41 # The filename Kbuild has precedence over Makefile
42 kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
43 kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
44 include $(kbuild-file)
53 include scripts/Makefile.lib
62 # Do not include host rules unless needed
63 ifneq ($(hostprogs-y)$(hostprogs-m),)
64 include scripts/Makefile.host
65 endif
这里的重点是理解Makefile.build是如何编译子目录?
首先关注第5和42行kbuild-dir的等式中有filter函数,它的目的是将不是以“/”开头的目录滤除,显然笔者的src=drivers,所以就被滤除掉,这个函数的值为空。也就是说$(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
的结果最终等于$(srctree)/$(src)
。也就是给src添加$(srctree)
路径。所以kbuild-dir值等于./drivers。
kbuild-file右边的等式,首先查看在kbuild-dir目录中是否有Kbuild存在,如果有就等于这个文件,否则使用这个目录中的Makefile文件。kbuild-file最终等于./drivers/Makefile。问题的答案就在这里。正是通过包含子目录的Makefile文件,根据其指定的需要编译的文件生成其目录下的built-in.o文件。
另外,include/config/auto.conf文件根据配置.config文件生成的,主要是去掉多余的注释。
2.Makefile.build总目标
前文提及需要通过@make -f ./scripts/Makefile.build obj=drivers
命令分析vmlinux-deps的生成。该make命令指定Makefile文件为./scripts/Makefile.build,需要传递参数obj=drivers。
scripts/Makefile.build文件
94 __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) 95 $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) 96 $(subdir-ym) $(always)
97 @:
Makefile.build总目标的依赖分为三个部分KBUILD_BUILTIN
部分,KBUILD_MODULES
部分和$(subdir-ym) $(always)
部分。通过调试手段可以得到如下结论。
KBUILD_BUILTIN=1
KBUILD_MODULES=
也就是说KBUILD_MODULES部分不进行编译。
- KBUILD_BUILTIN部分
scripts/Makefile.build文件
86 ifneq ($(strip $(obj-y) $(obj-m) $(obj-) $(subdir-m) $(lib-target)),)
87 builtin-target := $(obj)/built-in.o
88 endif
也就是说KBUILD_BUILTIN部分为./driver/built-in.o
- $(subdir-ym)部分
scripts/Makefile.lib文件
38 __subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
39 subdir-y += $(__subdir-y)
40 __subdir-m := $(patsubst %/,%,$(filter %/, $(obj-m)))
41 subdir-m += $(__subdir-m)
42 obj-y := $(patsubst %/, %/built-in.o, $(obj-y))
43 obj-m := $(filter-out %/, $(obj-m))
44
45 # Subdirectories we need to descend into
46
47 subdir-ym := $(sort $(subdir-y) $(subdir-m))
__subdir-y
和__subdir-m
首先将drivers/Makefile
指定obj-y、obj-m
中的非文件夹滤除,然后通过patsubst
函数将最后的“/”去除。注意到drivers/Makefile
中的obj-y、obj-m
通通都是文件夹。笔者以obj-y = net/
为例进行以下内容的说明,所以__subdir-y=net。subdir-ym
就是在__subdir-y
和__subdir-m
前边添加obj的前缀,所以最终等于drivers/net
。
3.Makefile.build生成
Makefile.build的总目标是一个伪目标__build。继续以@make -f ./scripts/Makefile.build obj=drivers
例子来看,Makefile.build生成两部分./driver/built-in.o和driver子目录(递归)。
- 子目录built-in.o生成
scripts/Makefile.build文件
400 PHONY += $(subdir-ym)
401 $(subdir-ym):
402 $(Q)$(MAKE) $(build)=[email protected]
递归调用$(Q)$(MAKE) $(build)[email protected]
。不再重复分析。
- ./driver/built-in.o生成
scripts/Makefile.build文件
324 #
325 # Rule to compile a set of .o files into one .o file
326 #
327 ifdef builtin-target
328 quiet_cmd_link_o_target = LD [email protected]
329 # If the list of objects to link is empty, just create an empty built-in.o
330 cmd_link_o_target = $(if $(strip $(obj-y)),331 $(LD) $(ld_flags) -r -o [email protected] $(filter $(obj-y), $^) 332 $(cmd_secanalysis),333 rm -f [email protected]; $(AR) rcs$(KBUILD_ARFLAGS) [email protected])
334
335 $(builtin-target): $(obj-y) FORCE
336 $(call if_changed,link_o_target)
337
338 targets += $(builtin-target)
339 endif # builtin-target
361 $(lib-target): $(lib-y) FORCE
362 $(call if_changed,link_l_target)
通过调试手段可以得到如下结论。
arm-linux-gnueabihf-ld -EL -r -o drivers/built-in.o drivers/irqchip/built-in.o drivers/bus/built-in.o drivers/pinctrl/built-in.o drivers/gpio/built-in.o drivers/pwm/built-in.o drivers/video/built-in.o drivers/idle/built-in.o drivers/amba/built-in.o drivers/dma/built-in.o drivers/soc/built-in.o drivers/tty/built-in.o drivers/char/built-in.o drivers/gpu/built-in.o drivers/base/built-in.o drivers/block/built-in.o drivers/misc/built-in.o drivers/mfd/built-in.o drivers/nfc/built-in.o drivers/macintosh/built-in.o drivers/mtd/built-in.o drivers/spi/built-in.o drivers/hsi/built-in.o drivers/net/built-in.o drivers/firewire/built-in.o drivers/cdrom/built-in.o drivers/auxdisplay/built-in.o drivers/input/serio/built-in.o drivers/input/built-in.o drivers/rtc/built-in.o drivers/i2c/built-in.o drivers/media/built-in.o drivers/hwmon/built-in.o drivers/lguest/built-in.o drivers/mmc/built-in.o drivers/leds/built-in.o drivers/firmware/built-in.o drivers/clocksource/built-in.o drivers/hid/built-in.o drivers/platform/built-in.o drivers/clk/built-in.o drivers/iommu/built-in.o
五、总结
CSDN排版非常麻烦。建议支持office导入。