一、linux 内核makefile 组成
名称 | 描述 |
顶层 Makefile | 它是所有Makefile文件的核心,从总体上控制着内核的编译、连接 |
arch/$(ARCH)/Makefile | 对应体系结构的Makefile,它用来决定哪些体系结构相关的文件参与内核的生成,并提供一些规则来生成特定格式的内核映像 |
scripts/Makefile.* | Makefile公用的通用规则、脚本等 |
子目录kbuild Makefiles | 各级子目录的Makefile相对简单,被上一层Makefile.build调用来编译当前目录的文件。 |
顶层.config | 配置文件,配置内核时生成。所有的Makefile文件(包括顶层目录和各级子目录)都是根据.config来决定使用哪些文件的
|
总结:
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等文件。
二、分析顶层makefile 以周立功的imx287移植好的 linux-2.65 为例
1. 内核版本号
打开顶层 Makefile,开头的几行记录了内核源码的版本号,通常如下所示:
VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 35
EXTRAVERSION =3
说明代码版本为 2.6.35.3,编译得到的内核在目标板运行后,输入 uname -a 命令可以得
到印证:
# uname -a
Linux boy 2.6.35.3-571-gcca29a0-gd431b3d-dirty #22 PREEMPT Tue Oct 27 20:12:33 CST 2015 armv5tejl
GNU/Linux
2. 编译控制
(1)体系结构
Linux 是一个支持众多体系结构的操作系统,在编译过程中需指定体系结构,以与实际
平台对应。在顶层 Makefile 中,通过变量 ARCH 来指定:
ARCH ?= $(SUBARCH)
如果没有在编译命令行中指定 ARCH 参数,系统将会进行本地编译,通过获取本机信
息来自动指定:
SUBARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \
-e s/arm.*/arm/ -e s/sa110/arm/ \
-e s/s390x/s390/ -e s/parisc64/parisc/ \
-e s/ppc.*/powerpc/ -e s/mips.*/mips/ \
-e s/sh[234].*/sh/ )
如果进行 ARM 嵌入式 Linux 开发,则必须指定 ARCH 为 arm(注意大小写,须与 arch/
目录下的 arm 一致),如:
$make ARCH=arm
当然,也可以修改 Makefile,将修改为 ARCH ?= $(SUBARCH)修改为 ARCH = arm,在
命令行直接 make 即可。
(2)编译器
如果不是进行本地编译,则须指定交叉编译器,通过 CROSS_COMPILE 来指定。Makefile
中与交叉编译器的指定如下:
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
……
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) –E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
CONFIG_CROSS_COMPILE 是一个配置选项,可在内核配置时候指定。如果在配置内
核时候没有指定 CONFIG_CROSS_COMPILE,也没有在编译参数指定 CROSS_COMPILE,
则会采用本地编译器进行编译。
进行 ARM 嵌入式 Linux 开发,必须指定交叉编译器,可以在内核配置通过 CONFIG
_CROSS_COMPILE 指定交叉编译器,也可以通过 CROSS_COMPILE 指定。假定使用的交
叉编译器是 arm-linux-gnueabihf-gcc,则指定 CROSS_COMPILE 为 arm-linux-gnueabihf-:
$ make ARCH=arm CROSS_COMPILE= arm-linux-gnueabihf-
或者在 Makefile 中,直接指定 CROSS_COMPILE 的值:
CROSS_COMPILE = arm-linux-gnueabihf-
注意: CROSS_COMPILE 指定的交叉编译器必须事先安装并正确设置系统环境变量; 如果
没有设置环境变量, 则需使用绝对地址, 例如:
CROSS_COMPILE =/home/ctools/linux-devkit/bin/arm-linux-gnueabihf-
如果同时指定了 ARCH 和 CROSS_COMPILE, 则在编译的时候, 只需简单的 make 就
可以了。
(3)编译总目标uImage
内核配置完成后,在顶层目录中执行“#make uImage”便开始编译内核。但是,uImage却不是在顶层Makefile中定义,而是在arch/$(ARCH)/Makefile中定义。
其中srctree为源码绝对路径;而SRCARCH := $(ARCH),即该变量等于架构名称,我们以arm为例进行说明
可见uImage依赖于vmlinux,要先生成vmlinux,然后执行下边这条指令完成编译。
然后执行
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
<1> Q的定义:选择静态编译与否(是否打印编译信息)
顶层Makefile Line 292: ifeq ($(KBUILD_VERBOSE),1) quiet = Q = else quiet=quiet_ Q = @ endif
<2> MAKE: 系统环境变量,值为make
<3> build: 值为“-f scripts/Makefile.build obj=”实际上就是调用子Makefile--scripts/Makefile.build,然后传递参数目标文件夹。
Kbuild.include Line 141: # Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj= # Usage: # $(Q)$(MAKE) $(build)=dir build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj
Kbuild.include被顶层Makefile所包含
顶层Makefile Line 312:
# We need some generic definitions (do not try to remake the file). $(srctree)/scripts/Kbuild.include: ; include $(srctree)/scripts/Kbuild.include
2、vmlinux的生成
顶层Makefile Line 845: # vmlinux image - including updated kernel symbols vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE ifdef CONFIG_HEADERS_CHECK $(Q)$(MAKE) -f $(srctree)/Makefile headers_check endif ifdef CONFIG_SAMPLES $(Q)$(MAKE) $(build)=samples endif ifdef CONFIG_BUILD_DOCSRC $(Q)$(MAKE) $(build)=Documentation endif $(call vmlinux-modpost) $(call if_changed_rule,vmlinux__) $(Q)rm -f .old_version
vmlinux的依赖是“$(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE”,要想生成vmlinux必须要先把这些原材料准备好。
<1> vmlinux-lds
在编译之前要把连接脚本先生成,最后的连接阶段会用的着。
顶层Makefile Line 699: vmlinux-lds := arch/$(SRCARCH)/kernel/vmlinux.lds
vmlinux.lds 文件是由arch/arm/vmlinux-armv.lds.in 生成
LDSCRIPT = arch/arm/vmlinux-armv.lds.in
arch/arm/vmlinux.lds: arch/arm/Makefile $(LDSCRIPT) \
2、scripts/Makefile.build的总目标
scripts/Makefile.build Line 7: PHONY := __build __build: scripts/Makefile.build Line 93: __build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \ $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target)) \ $(subdir-ym) $(always) @:
<1> KBUILD_BUILTIN
在终端以#Make uImage执行Make时,KBUILD_BUILTIN := 1。
顶层Makefile Line 241: KBUILD_BUILTIN := 1 # If we have only "make modules", don't compile built-in objects. # When we're building modules with modversions, we need to consider # the built-in objects during the descend as well, in order to # make sure the checksums are up to date before we record them. ifeq ($(MAKECMDGOALS),modules) KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1) endif
顶层Makefile Line 264: export KBUILD_MODULES KBUILD_BUILTIN
<2> builtin-target
scripts/Makefile.build Line 85:
ifneq ($(strip $(obj-y) $(obj-m) $(obj-n) $(obj-) $(lib-target)),) builtin-target := $(obj)/built-in.o endif
builtin-target值为drivers/built-in.o
<3> lib-target
scripts/Makefile.build Line 81:
ifneq ($(strip $(lib-y) $(lib-m) $(lib-n) $(lib-)),) lib-target := $(obj)/lib.a endif
lib-target的值为drivers/lib.a
<4> $(if $(KBUILD_MODULES),$(obj-m) $(modorder-target))
初步认识时定义编译的模块
<5> subdir-ym
subdir-ym的定义
scripts/Makefile.lib Line 38: __subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y))) subdir-y += $(__subdir-y) __subdir-m := $(patsubst %/,%,$(filter %/, $(obj-m))) subdir-m += $(__subdir-m)
scripts/Makefile.lib Line 47: subdir-ym := $(sort $(subdir-y) $(subdir-m))
scripts/Makefile.lib Line 89: subdir-ym := $(addprefix $(obj)/,$(subdir-ym))
__subdir-y和__subdir-m首先将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、drivers/built-in.o的生成
scripts/Makefile.build的总目标__build中的一个目标是 builtin-target,而它的值为drivers/built-in.o,现在就来看看它是怎样生成的。
scripts/Makefile.build Line 288: $(builtin-target): $(obj-y) FORCE $(call if_changed,link_o_target)
<1> drivers/built-in.o的依赖
drivers/built-in.o依赖于obj-y(子目标),然后通过调用一个if_changed函数,将这些子目标连接起来,生成drivers/built-in.o。通过命令打印查看连接命令如下:
arm-linux-ld -EL -r -o drivers/built-in.o drivers/gpio/built-in.o drivers/video/built-in.o drivers/char/built-in.o drivers/gpu/built-in.o drivers/serial/built-in.o drivers/base/built-in.o drivers/block/built-in.o drivers/misc/built-in.o drivers/mfd/built-in.o drivers/macintosh/built-in.o drivers/scsi/built-in.o drivers/net/built-in.o drivers/ieee1394/built-in.o drivers/cdrom/built-in.o drivers/auxdisplay/built-in.o drivers/mtd/built-in.o drivers/usb/built-in.o drivers/usb/gadget/built-in.o drivers/input/built-in.o drivers/rtc/built-in.o drivers/i2c/built-in.o drivers/media/built-in.o drivers/watchdog/built-in.o drivers/lguest/built-in.o drivers/idle/built-in.o drivers/mmc/built-in.o drivers/firmware/built-in.o drivers/crypto/built-in.o drivers/hid/built-in.o drivers/platform/built-in.o
看下边的代码,原来drivers/Makefile中的obj-y是一群目录,通过第一行代码后就给这些目录后边加上了built-in.o,再经过第二行代码,给它们冠以前缀。我们以obj-y=net/为例说明,经过第一行代码obj-y=net/built-in.o,经过第二行代码obj-y=drivers/net/built-in.o
scripts/Makefile.lib Line 42: obj-y := $(patsubst %/, %/built-in.o, $(obj-y)) scripts/Makefile.lib Line 78: obj-y := $(addprefix $(obj)/,$(obj-y))
<2> if_changed函数
通过if_changed函数,完成了把这些子目录中的目标连接生成dirvers/built-in.o。
Kbuild.include Line 192: if_changed = $(if $(strip $(any-prereq) $(arg-check)), \ @set -e; \ $(echo-cmd) $(cmd_$(1)); \ echo 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd)
既然是函数,就会传递参数。相对于C语言来说形参就是(1),而传递实参的方法就是
(call if_changed,link_o_target),其中call是调用函数命令,if_changed是调用函数名,后边的link_o_target就是实参。
①$(if $(strip $(any-prereq) $(arg-check)) any-prereq检查是否有依赖比目标新,或者依赖还没有创建;arg-check检查编译目标的命令相对上次是否发生变化。如果两者中只要有一个发生改变,if_changed的值就等于
@set -e; \ $(echo-cmd) $(cmd_$(1)); \ echo 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd
否则,if_changed的值为空,也就是说$(call if_changed,link_o_target)将什么也不执行,因为没必要执行。
②set -e 是说下边的命令如果出错了就直接返回,不再继续执行
③echo-cmd
Kbuild.include Line 156: echo-cmd = $(if $($(quiet)cmd_$(1)),\ echo ' $(call escsq,$($(quiet)cmd_$(1)))$(echo-why)';)
echo-cmd就是打印出调用的命令。if (
(quiet)cmd_(1))是判断命令(quiet)cmd_(1)或者cmd_
(1)是否定义,也就是quiet_cmd_link_o_target或者cmd_link_o_target是否定义。如果有定义,echo-cmd就会将这个命令打印出来,也就是打印。
④$(cmd_$(1))
对于 $(call if_changed,link_o_target),该命令就是cmd_link_o_target。
scripst/Makefile.build Line 283: cmd_link_o_target = $(if $(strip $(obj-y)),\ $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) \ $(cmd_secanalysis),\ rm -f $@; $(AR) rcs $@)
$(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^) ,这个就是连接命令。 总结: if_changed函数的核心功能就是判断是否需要更新目标,如果需要就执行表达式$(cmd_$(1))展开后的值来完成重建目标。
4、drivers/net/built-in.o
drivers/built-in.o的依赖如何生成呢,比如说drivers/net/built-in.o,还记得subdir-ym吗?subdir-ym记录了当前目录里边要参与编译连接的子文件夹。在 “2 scripts/Makefile.build阶段 2、scripts/Makefile.build的总目标 <5> $(subdir-ym)”中,我们假定subdir-ym=drivers/net。
Makefile.build Line 356: PHONY += $(subdir-ym) $(subdir-ym): $(Q)$(MAKE) $(build)=$@
$(Q)$(MAKE) $(build)=$@就是再一次调用scripts/Makefile.build,并传递参数drivers/net,这和生成drivers/built-in.o没什么差别。
3 scripts/Makefile.build的再一次调用阶段
再次转战到scripts/Makefile.build,obj=drivers/net被带到该文件。
1、Makefile.build的包含文件
其包含文件与第一次调用阶段没什么大的区别,最为重要的区别就是包含子目录Makefile的改变,这次包含的文件是/workspace/linux-2.6.30.4/drivers/net/Makefile。
2、scripts/Makefile.build的总目标
总目标中的builtin-target值为drivers/net/built-in.o
3、drivers/net/built-in.o的生成
scripts/Makefile.build Line 288: $(builtin-target): $(obj-y) FORCE $(call if_changed,link_o_target)
由于子目录的Makefile变成了/workspace/linux-2.6.30.4/drivers/net/Makefile,所以obj-y对应的发生了变化。通过执行编译查看这个连接过程。
arm-linux-ld -EL -r -o drivers/net/built-in.o drivers/net/mii.o drivers/net/Space.o drivers/net/loopback.o drivers/net/dm9000.o drivers/net/arm/built-in.o drivers/net/e1000/built-in.o
<1> drivers/net/built-in.o的依赖
从drivers/net/Makefile中摘出两个典型的obj-y组成部分如下所示:
drivers/net/Makefile Line 5: obj-$(CONFIG_E1000) += e1000/ drivers/net/Makefile Line 230: obj-$(CONFIG_DM9000) += dm9000.o
drivers/net/built-in.o的依赖包含了文件夹,并且包含了直接由C文件生成的目标文件.o。
scripts/Makefile.lib Line 42: obj-y := $(patsubst %/, %/built-in.o, $(obj-y)) scripts/Makefile.lib Line 78: obj-y := $(addprefix $(obj)/,$(obj-y))
第一行代码让obj-y如果是文件夹,就添加built-in.o,如果是普通的目标文件*.o,什么也不操作;第二行代码冠以目录obj。所以obj-y最终等于drivers/net/e1000/built-in.o、drivers/net/dm9000.o。
我们越来越接近最终的目标了--分析到最底层的C文件生成目标文件。
<2> if_changed函数
drivers/net/built-in.o也是一个由众多的文件组成的库文件,所以也是通过调用if_changed函数完成连接。
4、drivers/net/dm9000.o
scripts/Makefile.build Line 226: # Built-in and composite module parts $(obj)/%.o: $(src)/%.c FORCE $(call cmd,force_checksrc) $(call if_changed_rule,cc_o_c) scripts/Makefile.build Line 263: $(obj)/%.o: $(src)/%.S FORCE $(call if_changed_dep,as_o_S)
$(obj)/%.o: $(src)/%.c FORCE是编译C程序,$(obj)/%.o: $(src)/%.S FORCE是编译汇编程序
<1> drivers/net/dm9000.o的依赖
drivers/net/dm9000.o的依赖是对应的$(src)/%.c,也就是drivers/net/dm9000.c。
<2>cmd函数
scripts/Kbuild Line 160: cmd = @$(echo-cmd) $(cmd_$(1))
此函数还未做深入分析。
<2>if_changed_rule函数
scripts/Kbuild Line 205:
# Usage: $(call if_changed_rule,foo) # Will check if $(cmd_foo) or any of the prerequisites changed, # and if so will execute $(rule_foo). if_changed_rule = $(if $(strip $(any-prereq) $(arg-check) ), \ @set -e; \ $(rule_$(1)))
$(call if_changed_rule,cc_o_c),从而会调用函数if_changed_rule,接着会执行命令$(rule_$(1))),也就是rule_cc_o_c。
scripts/Makefile.build Line 215: define rule_cc_o_c $(call echo-cmd,checksrc) $(cmd_checksrc) \ $(call echo-cmd,cc_o_c) $(cmd_cc_o_c); \ $(cmd_modversions) \ $(cmd_record_mcount) \ scripts/basic/fixdep $(depfile) $@ '$(call make-cmd,cc_o_c)' > \ $(dot-target).tmp; \ rm -f $(depfile); \ mv -f $(dot-target).tmp $(dot-target).cmd endef
其中$(cmd_cc_o_c)命令的定义是
scripts/Makefile.build Line 179: cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<
5、drivers/net/e1000/built-in.o的生成
subdir-ym肯定包含有文件夹drivers/net/e1000,于是继续再一次调用scripts/Makefile.build来完成drivers/net/e1000/built-in.o的生成。
Makefile.build Line 356: PHONY += $(subdir-ym) $(subdir-ym): $(Q)$(MAKE) $(build)=$@