一 说明
可以在busybox目录下,执行“make help”获取帮助信息;可以通过README获取更多信息。下面是Makefile的主要步骤:
1、读取工作目录下的默认makefile文件(makefile,Makefile) (开始读我们的写的makefile主文件了)
2、依次读取工作目录makefile文件中使用指示符"include"包含的文件 (makefile主文件中包含的其他文件也读进来了)
3、查找重建所有已读取的makefile文件的规则(如果存在一个目标是当前读取的某一个makefile文件,则执行此规则重建此makefile文件,完成以后从第一步开始重新执行) (makefile主文件极其包含的makefile文件有需要动态修改的,先修改在重新读进来)
4、初始化变量值并展开那些需要立即展开的变量和函数并根据预设条件确定执行分支 (相当于预处理过程吧?)
5、根据"终极目标"以及其他目标的依赖关系建立依赖关系链表 (开始整理我们的写的规则准备执行了,相当于编译链接过程)
6、执行除"终极目标"以外的所有的目标的规则(规则中如果所依赖的文件中一个时间戳比目标文件新,则根据规则所定义的命令重新创建目标) (最后两步就是执行了)
7、执行"终极目标"所在的规则
二 文件内容理解
VERSION = 1
PATCHLEVEL = 20
SUBLEVEL = 2
EXTRAVERSION =
NAME = Unnamed
上面关于版本信息,很容易理解,就不再重复。
不要打印"Entering directory ...",要不然进入每个目录都会打印该显示信息。
MAKEFLAGS += --no-print-directory
操作符“+=”的作用是给变量(“+=”前面的MAKEFLAGS)追加值。在执行make时的命令行选项参数被通过变量 “MAKEFLAGS”传递给子目录下的make程序。对于这个变量除非使用指示符“unexport”对它们进行声明,否则,它们在整个 make的执行过程中始终被自动的传递给所有的子make。还有个特殊变量SHELL与MAKEFLAGS一样,默认情况(没有用“unexport”声明)下在整个make的执行过程中被自动的传递给所有的子make。
我们使用递归构建方法,所以我们需要想一下怎样得到正确的顺序。最重要的是:子目录中的Makefile文件只能在它们本身的目录下进行修改。如果在某一个目录下,与另一个目录下的文件有依赖关系(这并不经常发生,但是在将built-in.o 目标文件链接成busybox的时候,这是不可避免的), 我们将会在别的目录中,调用make命令,这样我们就能保证那个目录下的文件都是最新的。
为了重点关注警告信息,默认输出是精简的。命令'make V=1' 可以看见所有的命令ifdef V
ifeq ("$(origin V)", "command line")
KBUILD_VERBOSE = $(V)
endif
endif
ifndef KBUILD_VERBOSE
KBUILD_VERBOSE = 0
endif
函数origin并不操作变量的值,只是告诉你,这个变量是哪里来的。语法是: $(origin ;)# origin函数的返回值有:"undefined" 从来没有定义过、“default”是一个默认的定义、"environment"是一个环境变量、 "file"这个变量被定义在Makefile中、"command line"这个变量是被命令行定义的、 "override"是被override指示符重新定义的、"automatic"是一个命令运行中的自动化变量。
ifdef C
ifeq ("$(origin C)", "command line")
KBUILD_CHECKSRC = $(C)
endif
endif
ifndef KBUILD_CHECKSRC
KBUILD_CHECKSRC = 0
endif
对于c文件的编译,调用sparse内核代码静态分析工具, 使用命令 'make C=1' 使能sparse检查。
用 M= 目录 来指定要构建的外部模块;旧语法 make ... SUBDIRS=$PWD 仍然被支持;设置环境变量KBUILD_EXTMOD,这种方法优先。
ifdef SUBDIRS
KBUILD_EXTMOD ?= $(SUBDIRS)
endif
ifdef M
ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif
endif
kbuild 支持保存输出文件到另外一个单独的目录下,它支持两种语法,两种情况下,工作目录必须是源代码的根目录:(1) O= ,使用 "make O=dir/to/store/output/files/"指定输出目录;(2) 设置环境变量 KBUILD_OUTPUT, 使用export KBUILD_OUTPUT=dir/to/store/output/files/。 “O= ”这种方法优先级高于环境变量 KBUILD_OUTPUT设置这种方法。
在OBJ目录下调用make时,设置KBUILD_SRC;(现在)KBUILD_SRC没有打算给普通用户使用。
好了,在源代码所在的目录下调用Make,下面就是其处理流程。首先,我们是否要将输出文件定位到独立的目录下?
ifeq ($(KBUILD_SRC),)
ifdef O
ifeq ("$(origin O)", "command line")
KBUILD_OUTPUT := $(O)
endif
endif
#如果命令行中没有给出任何编译选项,那么下面的_all就是我们的默认编译目标。
PHONY := _all
_all:
#KBUILD_OUTPUT定义了输出目录,记录要保存的目录,判断这个目录是否存在,不存在就提示错误输出。
ifneq ($(KBUILD_OUTPUT),)
saved-output := $(KBUILD_OUTPUT)
KBUILD_OUTPUT := $(shell cd $(KBUILD_OUTPUT) && /bin/pwd)
$(if $(KBUILD_OUTPUT),, \
$(error output directory "$(saved-output)" does not exist))
# MAKECMDGOALS指定你所需要的最终目标文件的列表,如果你没有指定,那么该值为空值
PHONY += $(MAKECMDGOALS)
$(filter-out _all,$(MAKECMDGOALS)) _all:
$(if $(KBUILD_VERBOSE:1=),@)$(MAKE) -C $(KBUILD_OUTPUT) \
KBUILD_SRC=$(CURDIR) \
KBUILD_EXTMOD="$(KBUILD_EXTMOD)" -f $(CURDIR)/Makefile $@
skip-makefile := 1
endif # ifneq ($(KBUILD_OUTPUT),)
endif # ifeq ($(KBUILD_SRC),)
$(filter-out , )函数的调用,过滤掉$(MAKECMDGOALS)指定的文件列表中存在的_all文件。$(KBUILD_VERBOSE:1=),如果KBUILD_VERBOSE为1,则替换为空。如果KBUILD_VERBOSE为空或者KBUILD_VERBOSE = 1,则回显;否则不回显提示信息(@,为不回显的命令)。在输出目录中,再次调用make,传递相关的变量。然后离开上层目录下make的调用处理过程。
如果这是最终的make调用,我们开始处理Makefile中剩余部分。
ifeq ($(skip-makefile),) #busybox源代码中的endif # ifeq($(skip-makefile),)在1316行
如果是编译一个外部模块,我们不关心all:规则;但是相反,_all依赖于modules;
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif
判断源代码目录是否存在,如果不存在,就设置为当前目录下。
srctree := $(if $(KBUILD_SRC),$(KBUILD_SRC),$(CURDIR))
TOPDIR := $(srctree) # 顶层目录,顶层目录变量TOPDIR已经被废弃,现在使用srctree/objtree
objtree := $(CURDIR)
src := $(srctree)
obj := $(objtree)
设置依赖文件的搜索路径,
VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))
使上面的变量生效,
export srctree objtree VPATH TOPDIR
指定交叉编译和选择不同的gcc/bin-utils工具。当我们为别的架构执行交叉编译的时候,ARCH应该被设置为目标架构,例如arm。(详细可以查看arch/目录下,能够支持的目标架构)。调用make时,可以设置ARCH变量,像“make ARCH=arm”;另一种方法是在环境变量中设置ARCH。默认ARCH是执行make的主机。
CROSS_COMPILE指定编译时,所使用所有的执行文件的前缀,像arm-fsl-linux-gnueabi-。CROSS_COMPILE可以在命令行中设置,“make CROSS_COMPILE=arm-fsl-linux-gnueabi-”;另外,也可以通过环境变量进行设置。
注意:有些架构的CROSS_COMPILE指派,在arch/目录下该架构对应的Makefile中指定。
CROSS_COMPILE ?=
busybox:可以使用.config文件中CONFIG_CROSS_COMPILER_PREFIX指定交叉编译的工具的前缀。
ifeq ($(CROSS_COMPILE),)
CROSS_COMPILE := $(shell grep ^CONFIG_CROSS_COMPILER_PREFIX .config 2>/dev/null)
CROSS_COMPILE := $(subst CONFIG_CROSS_COMPILER_PREFIX=,,$(CROSS_COMPILE))
CROSS_COMPILE := $(subst ",,$(CROSS_COMPILE))
#")
endif
SUBARCH表明用户模式(usermode)构建时,使用的架构类型。当在命令行里键入“ARCH=um”时,就选择使用了用户模式,那么SUBARCH就会覆盖了下面的ARCH设置。如果是本机构建,ARCH被指定,就会获得正常值,SUBARCH就会被忽略。说明:cut的用法,-d, –delimiter=DELIM 指定分隔符来代替默认的TAB分隔符;-f, –fields=LIST 依据 -d 指定的分隔符将一段内容分割成为数段,用 -f 取出第几段的意思。
ifneq ($(CROSS_COMPILE),)
SUBARCH := $(shell echo $(CROSS_COMPILE) | cut -d- -f1)
else
SUBARCH := $(shell uname -m)
endif
SUBARCH := $(shell echo $(SUBARCH) | 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/ )
ARCH ?= $(SUBARCH)
架构被保存在 compile.h文件中,
UTS_MACHINE := $(ARCH)
选择kbuild使用的shell工具。-x,表明是否具有执行权限。我的是/bin/bash。
CONFIG_SHELL := $(shell if [ -x "$$BASH"]; then echo $$BASH; \
else if [ -x /bin/bash ]; then echo /bin/bash; \
else echo sh; fi ; fi)
决定构建方式是内嵌,模块,或两者都是。正常情况下,只需选择内嵌方式。
KBUILD_MODULES :=
KBUILD_BUILTIN := 1
如果我们只“make modules”,则,不编译内嵌目标。
# 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 uptodate before we record them.
ifeq ($(MAKECMDGOALS),modules)
KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1)
endif
如果我们使用命令“make ifneq ($(filter all _all modules,$(MAKECMDGOALS)),)
KBUILD_MODULES := 1
endif
ifeq ($(MAKECMDGOALS),)
KBUILD_MODULES := 1
endif
使变量生效,
export KBUILD_MODULES KBUILD_BUILTIN
export KBUILD_CHECKSRC KBUILD_SRC KBUILD_EXTMOD
设置漂亮输出显示。
正常情况下,我们在执行命令前会打印整个命令。通过使用echo $($(quiet)$(cmd)),我们就有可能通过设置$(quiet)来选择更多的输出格式。例如
# quiet_cmd_cc_o_c = Compiling $(RELDIR)/$@
# cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $<
如果$(quiet)为空,整个命令都会被打印;如果$(quiet)=quiet_,只会打印一小部分;如果$(quiet)=silent_,什么也不打印,因为$(silent_cmd_cc_o_c)这个变量不存在。一个简单的变种方法就是,使用前缀命令$(Q),这个命令也是使输出变得简洁的有效方法。如果KBUILD_VERBOSE=0,上面的命令将会被隐藏;如果KBUILD_VERBOSE=1,上面的命令将会被显示。
# $(Q)ln $@ :<
ifeq ($(KBUILD_VERBOSE),1)
quiet =
Q =
else
quiet=quiet_
Q = @
endif
如果用户正在运行make -s(静默模式),也会抑制命令的回显。
ifneq ($(findstring s,$(MAKEFLAGS)),)
quiet=silent_
endif
export quiet Q KBUILD_VERBOSE #生效
为make命令寻找源代码根目录下的头文件目录,
MAKEFLAGS += --include-dir=$(srctree)
指定主机编译工具,
HOSTCC = gcc
HOSTCXX = g++
HOSTCFLAGS :=
HOSTCXXFLAGS :=
我们需要指定一些通用的定义,像通用函数和宏定义,
include $(srctree)/scripts/Kbuild.include
HOSTCFLAGS += $(call hostcc-option,-Wall -Wstrict-prototypes -O2 -fomit-frame-pointer,)# 指定-Wall -Wstrict-prototypes -O2 -fomit-frame-pointer为主机编译选项
HOSTCXXFLAGS += -O2
为了最大的性能,(可能会被随机打断,所以,不要注释掉下面的语句。)
MAKEFLAGS += -rR
指定Make命令的相关变量(像CC等等)。
AS = $(CROSS_COMPILE)as
CC = $(CROSS_COMPILE)gcc
LD = $(CC) -nostdlib
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm
STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump
AWK = awk
GENKSYMS = scripts/genksyms/genksyms
DEPMOD = /sbin/depmod
KALLSYMS = scripts/kallsyms
PERL = perl
CHECK = sparse
CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ -Wbitwise $(CF)
MODFLAGS = -DMODULE
CFLAGS_MODULE = $(MODFLAGS)
AFLAGS_MODULE = $(MODFLAGS)
LDFLAGS_MODULE = -r
CFLAGS_KERNEL =
AFLAGS_KERNEL =
# Use LINUXINCLUDE when you must reference the include/ directory.
# Needed to be compatible with the O= option
CFLAGS := $(CFLAGS)
只有在busybox的二进制文件编译的最后链接阶段被添加。
CFLAGS_busybox := $(CFLAGS_busybox)
CPPFLAGS := $(CPPFLAGS)
AFLAGS := $(AFLAGS)
LDFLAGS := $(LDFLAGS)
LDLIBS :=
# Read KERNELRELEASE from .kernelrelease (if it exists)
KERNELRELEASE = $(shell cat .kernelrelease 2> /dev/null)
KERNELVERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
使这些变量生效,
export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE KERNELVERSION \
ARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC \
CPP AR NM STRIP OBJCOPY OBJDUMP MAKE AWK GENKSYMS PERL UTS_MACHINE \
HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS
export CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS
export CFLAGS CFLAGS_KERNEL CFLAGS_MODULE
export AFLAGS AFLAGS_KERNEL AFLAGS_MODULE
export FLTFLAGS
当编译独立的模块时(out-of-tree),把MODVERDIR变量放到模块文件目录树中,而不是内核源代码树中。内核源代码甚至是只读的。
export MODVERDIR := $(if $(KBUILD_EXTMOD),$(firstword $(KBUILD_EXTMOD))/).tmp_versions
# Files to ignore in find ... statements
RCS_FIND_IGNORE := \( -name SCCS -o -name BitKeeper -o -name .svn -o -name CVS -o -name .pc -o -name .hg -o -name .git \) -prune -o
export RCS_TAR_IGNORE := --exclude SCCS --exclude BitKeeper --exclude .svn --exclude CVS --exclude .pc --exclude .hg --exclude .git
配置目标和编译目标之间共享的规则设定。首先,就是把位于../scripts/basic文件中定义的基本操作包含进来。
PHONY += scripts_basic
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
为了避免隐含规则生效,定义一个空的命令
scripts/basic/%: scripts_basic ;
这个目标从所有*.c文件中,产生Kbuild和Config.in的构建文件。
PHONY += gen_build_files
gen_build_files: $(wildcard $(srctree)/*/*.c) $(wildcard $(srctree)/*/*/*.c)
$(Q)$(srctree)/scripts/gen_build_files.sh $(srctree) $(objtree)
# bbox: we have helpers in applets/
# we depend on scripts_basic, since scripts/basic/fixdep
# must be built before any other host prog
PHONY += applets_dir
applets_dir: scripts_basic gen_build_files
$(Q)$(MAKE) $(build)=applets
applets/%: applets_dir ;
# 编译目标outputmakefile在输出目录中产生一个Makefile文件,前提是使用独立的目录来存储编译后的文件。
# 这个设置允许在输出目录中方便的使用make命令。
PHONY += outputmakefile
outputmakefile:
ifneq ($(KBUILD_SRC),)
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
$(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif
为了确保我们找到的*config目标不包含.config目标,然后再把它们移交给scripts/kconfig/Makefile。当调用make时,它允许指定更多的目标,包括混合*config目标和构建目标。
例如,使用“make oldconfig all”。
no-dot-config-targets := clean mrproper distclean \
cscope TAGS tags help %docs
#bbox# check% is removed from above
config-targets := 0
mixed-targets := 0
dot-config := 1
# 如果指定了.config目标,又指定了混合目标(mixing *config),则指定的.config无效。
ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),)
ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),)
dot-config := 0
endif
endif
#如果没有定为编译为外部模块,那么如果在命令行里设置了相关的配置config目标,则config-targets=1;如果还定义了混合config(除了config和%config,还有其他目标时),则mixed-targets=1.
ifeq ($(KBUILD_EXTMOD),)
ifneq ($(filter config %config,$(MAKECMDGOALS)),)
config-targets := 1
ifneq ($(filter-out config %config,$(MAKECMDGOALS)),)
mixed-targets := 1
endif
endif
endif
# 如果使用混合目标(*config和build targets),则一个一个处理。
# %表示所有目标都使用该规则;如果make命令是“make defconfig all”,那么执行语句展开就是,
# make -C $(srctree) KBUILD_SRC=defconfig;
# make -C $(srctree) KBUILD_SRC=all;
# -C 大写,切换到指定目录再执行 make 过程,makefile 在这个指定目录里面;
ifeq ($(mixed-targets),1)
%:: FORCE
$(Q)$(MAKE) -C $(srctree) KBUILD_SRC= $@
else
# 只使用指定的*config配置文件-确定前提条件都是最新的,且使用降序使用scripts/kconfig中文件,make *config 目标。
ifeq($(config-targets),1)
# 'make defconfig',生成最小配置,
# 用“-include”来代替“include”,忽略由于包含文件不存在或者无法创建时的错误,“-”的意思是告诉make,忽略此操作的错误,make 继续执行,
# 读arch目录下对应架构的默认Makefile文件,KBUILD_DEFCONFIG指向可选择的默认配置,
-include $(srctree)/arch/$(ARCH)/Makefile
export KBUILD_DEFCONFIG
config: scripts_basic outputmakefile gen_build_files FORCE
$(Q)mkdir -p include # 如果需要创建相应的父目录
$(Q)$(MAKE) $(build)=scripts/kconfig $@
$(Q)$(MAKE) -C $(srctree) KBUILD_SRC= .kernelrelease
%config: scripts_basic outputmakefile gen_build_files FORCE
$(Q)mkdir -p include
$(Q)$(MAKE) $(build)=scripts/kconfig $@
$(Q)$(MAKE) -C $(srctree) KBUILD_SRC= .kernelrelease
else # 构建目标时使用,包括busybox,arch specific targets,clean目标和其它的目标。通常情况下,除了*config配置目标之外的所有目标
ifeq ($(KBUILD_EXTMOD),)
# Additional helpers built in scripts/
# Carefully list dependencies so we do not try to build scripts twice
# in parallel
PHONY += scripts
scripts: gen_build_files scripts_basic include/config/MARKER
$(Q)$(MAKE) $(build)=$(@)
scripts_basic: include/autoconf.h
# Objects we will link into busybox / subdirs we need to visit
core-y := \
applets/ \
libs-y := \
archival/ \
archival/libarchive/ \
console-tools/ \
coreutils/ \
coreutils/libcoreutils/ \
debianutils/ \
e2fsprogs/ \
editors/ \
findutils/ \
init/ \
libbb/ \
libpwdgrp/ \
loginutils/ \
mailutils/ \
miscutils/ \
modutils/ \
networking/ \
networking/libiproute/ \
networking/udhcp/ \
printutils/ \
procps/ \
runit/ \
selinux/ \
shell/ \
sysklogd/ \
util-linux/ \
util-linux/volume_id/ \
endif # KBUILD_EXTMOD
# 下面就是定义了.config配置的情况,在这一段代码中,我们需要.config文件,所以需要包含进来;读取所有符合依赖关系的Kconfig*文件;
# 确保如果发生变化,能够运行oldconfig配置;
# 如果.config需要更新,那么根据.config的依赖关系自动完成,依赖描述位于autoconf文件中;
ifeq ($(dot-config),1)
-include .kconfig.d
-include .config
# 为了避免隐含规则发生作用,定义一个空命令;
.config .kconfig.d: ;
# 现在,根据.config中设定的规则定义CFLAGS等标志;
include $(srctree)/Makefile.flags
# 如果.config比include/auroconf.h要新,有些人就会修补它,且忘记运行“make oldconfig”;
# 如果我们在一个干净的源代码目录下(就是没有编译后的文件),kconfig文件遗失,我们需要执行config步骤,确保获取更新后的Kconfig文件;
include/autoconf.h: .kconfig.d .config $(wildcard $(srctree)/*/*.c) $(wildcard $(srctree)/*/*/*.c) | gen_build_files
$(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig
include/usage.h: gen_build_files
else
# 空目标,因为被用作前提条件;
include/autoconf.h
endif # ifeq ($(dot-config),1)