Linux内核Makefile笔记

来源:互联网 发布:椭圆机品牌 知乎 编辑:程序博客网 时间:2024/04/30 01:40

Linux内核Makefile笔记
周亦行
2014年11月

参考文档
① linux源码Makefile的详细分析
② if_changed_rule/cc_o_c/any-prereq/arg-check
③ Makefile中的伪目标
④ Linux kernel 3.18.5
一、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_defconfigmake ARCH=arm menuconfigmake ARCH=arm -j2 CROSS_COMPILE=arm-linux-gnueabihf- uImage

三、目标文件
编译uImage,自然uImage就是目标文件。内核配置完成后,在顶层目录开始编译内核。但是,uImage却不是在顶层Makefile中定义,而是在arch/$(ARCH)/Makefile中定义。

顶层Makefile文件534 include $(srctree)/arch/$(SRCARCH)/MakefileSRCARCH := $(ARCH),即该变量等于架构名称,s3c6400为arm架构。 arch/arm/Makefile文件300 BOOT_TARGETS    = zImage Image xipImage bootpImage uImage305 $(BOOT_TARGETS): vmlinux306     $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@

uImage依赖vmlinux。通过$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@指令编译生成。

  • 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)=dir174 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.lds901 export LDFLAGS_vmlinux902 # used by scripts/pacmage/Makefile903 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 vmlinux908       cmd_link-vmlinux = $(CONFIG_SHELL) $< $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux)909 quiet_cmd_link-vmlinux = LINK    $@910 911 # Include targets which we want to912 # execute if the rest of the kernel build went well.913 vmlinux: scripts/link-vmlinux.sh $(vmlinux-deps) FORCE914     echo $(vmlinux-deps)915 ifdef CONFIG_HEADERS_CHECK916     $(Q)$(MAKE) -f $(srctree)/Makefile headers_check917 endif918 ifdef CONFIG_SAMPLES919     $(Q)$(MAKE) $(build)=samples920 endif921 ifdef CONFIG_BUILD_DOCSRC922     $(Q)$(MAKE) $(build)=Documentation923 endif924     +$(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 += FORCE1593 FORCE:1594 1595 # Declare the contents of the .PHONY variable as phony.  We keep that1596 # 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_$@)) \               213                     $(filter-out $(cmd_$@),   $(cmd_$(1))) )214 else215 arg-check = $(if $(strip $(cmd_$@)),,1)216 endif226 # 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' 'cmd_$@ := $(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 files1582   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)), $(cmd_$@)) 排除上一个执行命令与当前执行命令的相同部分;$(filter-out $(cmd_$@), $(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.lds901 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 locale931 # tweaks to this spot to avoid wrong language settings when running932 # make menuconfig etc.933 # Error messages still appears in the original language934 935 PHONY += $(vmlinux-dirs)936 $(vmlinux-dirs): prepare scripts937     $(Q)$(MAKE) $(build)=$@

上述代码中,927行表示$(vmlinux-deps)所有的依赖为$(vmlinux-dirs)。并且通过$(Q)$(MAKE) $(build)=$@命令生成。而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.conf36 include scripts/Kbuild.include41 # The filename Kbuild has precedence over Makefile42 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.lib62 # Do not include host rules unless needed63 ifneq ($(hostprogs-y)$(hostprogs-m),)64 include scripts/Makefile.host65 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=1KBUILD_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))4445 # Subdirectories we need to descend into4647 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)=$@

递归调用$(Q)$(MAKE) $(build)=$@。不再重复分析。

  • ./driver/built-in.o生成
scripts/Makefile.build文件324 #325 # Rule to compile a set of .o files into one .o file326 #327 ifdef builtin-target328 quiet_cmd_link_o_target = LD      $@329 # If the list of objects to link is empty, just create an empty built-in.o330 cmd_link_o_target = $(if $(strip $(obj-y)),\331               $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \332               $(cmd_secanalysis),\333               rm -f $@; $(AR) rcs$(KBUILD_ARFLAGS) $@)334 335 $(builtin-target): $(obj-y) FORCE336     $(call if_changed,link_o_target)337 338 targets += $(builtin-target)339 endif # builtin-target361 $(lib-target): $(lib-y) FORCE362     $(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导入。

0 0
原创粉丝点击