目录
kbuild 的机制和流程还是比较复杂的。
从某个点切入,是个比较好的办法。
uboot 和 Linux kernel 的主 Makefile 里面,都有下面这段话:
PHONY += scripts_basic
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
$(Q)rm -f .tmp_quiet_recordmcount
重点分析 $(Q)$(MAKE) $(build)=scripts/basic
,忽略 $(Q)
,展开得:
make -f ./scripts/Makefile.build obj=scripts/basic
关于这句话,有很多的故事要讲。
$(build)的展开
主 Makefile 里面,有一个非常重要的 include:
include scripts/Kbuild.include
这是一个头文件,相当于把 Kbuild.include
的内容直接写到主 Makefile 里。
Kbuild.include
代码片段:
###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj
这句话的实质,是变量的字符串展开;就是说 build 被当做了 Kbuild 的一个保留的字符串变量。
所以 $(Q)$(MAKE) $(build)=scripts/basic
的展开,就是把$(build)
用 := 后的字符串替换:
make -f ./scripts/Makefile.build obj=scripts/basic
-f
是 Makefile 的语法:
The arguments ‘-f name’ or ‘–file=name’ tell make to read the file name as the makefile.
Kbuild 之灵魂 Makefile.build(一)
Kbuild 中,几乎或者全部的 .o 都是由 Makefile.build 来完成编译的。
Makefile.build 会为不同类型的编译对象,指定不同的规则。类似于大家喜闻乐见的:
gcc -c -o hello.o hello.c
但是 Kbuild 追求的是批量化编译,所以指定规则的过程很复杂,需要认真分析下。
这篇文章,只分析 hostprogs 相关的规则生成和编译。
hostprogs 是在编译过程中主机所用到的一些工具;hostprogs 编译的输出件是运行在主机上的,最后不会被链接到目标文件中。
make -f ./scripts/Makefile.build obj=scripts/basic
过滤掉 Makefile.build 中和 hostprogs 无关的代码,得:
src := $(obj)
# $(obj) 是传入的参数,即 scripts/basic
PHONY := __build
__build:
...
include scripts/Kbuild.include
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)
include scripts/Makefile.lib
hostprogs := $(sort $(hostprogs))
ifneq ($(hostprogs),)
include scripts/Makefile.host
endif
...
__build: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \
$(if $(KBUILD_MODULES), $(targets-for-modules)) \
$(subdir-ym) $(always-y)
@:
PHONY += FORCE
FORCE:
...
.PHONY: $(PHONY)
找到子目录中的 Makefile
关于 $(kbuild-file)
的分析,暂略。
这些语句,相当于 include 了scripts/basic/Makefile
。
这个文件就一句话:
# scripts/basic/Makefile
hostprogs-always-y += fixdep
使用 Makefile.lib 对编译目标进行分类和整理
scripts/Makefile.lib
会对各种编译目标进行转换、抽象和提取。
编译目标的种类有很多,这里只追踪 hostprogs 相关的。
# scripts/Makefile.lib
hostprogs += $(hostprogs-always-y) $(hostprogs-always-m)
always-y += $(hostprogs-always-y) $(hostprogs-always-m)
always-y := $(addprefix $(obj)/,$(always-y))
这样,可以获得
hostprogs = fixdep
always-y = scripts/basic/fixdep
是的,虽然 Makefile.build
这个文件很长,但是真正和 scripts/basic
相关的,就上面 3 行代码。
Makefile.host 所做的工作
因为 hostprogs
不为空,所以引入了另一个包含:
# scripts/Makefile.build
ifneq ($(hostprogs),)
include scripts/Makefile.host
endif
scripts/Makefile.host
这个文件会将 $(hostprogs)
转换为 $(host-csingle)
、$(host-cmulti)
等目标。
这里需要介绍下 host-csingle
、host-cmulti
、host-cobjs
、host-cxxmulti
和 host-cxxobjs
这几个变量。
这是 Makefile.host
所能处理的几类编译目标。
Makefile.host
会为这几类目标生成编译规则。
说到底,Kbuild 的这一套机制,目的就是为不同的编译目标生成编译规则。
host-cobjs 实例
这里只是举例,在编译 scripts/basic
时是不会用到 host-cobjs
类型的目标的。
介绍 host-cobjs
,只是为后面分析 host-csingle
做铺垫。
以 qconf 为例,scripts/kconfig/Makefile
中的代码段:
# scripts/kconfig/Makefile
# object files used by all kconfig flavours
common-objs := confdata.o expr.o lexer.lex.o parser.tab.o preprocess.o \
symbol.o util.o
hostprogs += qconf
qconf-cxxobjs := qconf.o qconf-moc.o
qconf-objs := images.o $(common-objs)
然后可得:
hostprogs = qconf,也许还有其他的,此处忽略
qconf-objs = images.o confdata.o expr.o lexer.lex.o parser.tab.o preprocess.o symbol.o util.o
Makefile.host 对 host-cobjs 的处理
# scripts/Makefile.host
# Object (.o) files compiled from .c files
host-cobjs := $(sort $(foreach m,$(hostprogs),$($(m)-objs))) # 语句1
host-cobjs := $(addprefix $(obj)/,$(host-cobjs)) # 语句2
foreach
的作用,是将 $(hostprogs)
中的变量逐个取出,然后带入到 $($(m)-objs))
中
即得到$(qconf-objs)
注意,$(qconf-objs)
还会被展开一次,即得到
images.o confdata.o expr.o lexer.lex.o parser.tab.o preprocess.o symbol.o util.o
所以,语句1 的结果是:
host-cobjs := images.o confdata.o expr.o lexer.lex.o parser.tab.o preprocess.o symbol.o util.o
语句2 是为上面的这些 .o 添加目录前缀。
所以 $(host-cobjs)
实际上是若干 .c 对应的 .o 的集合。
当为 $(host-cobjs)
制定好规则以后,上面的 images.o confdata.o ...
所对应的 images.c confdata.c ...
均会批量化的按照制定的规则进行编译。
# 这里用到了“静态模式”的概念,暂不展开
$(host-cobjs): $(obj)/%.o: $(src)/%.c FORCE
$(call if_changed_dep,host-cobjs)
if_changed_dep,
的这个逗号后面,一定不要加空格什么的
或者说 call 的用法就这样,逗号后面不要加不相关的字符
另外,对应的 cmd_cpp_lds 等命令,一定用延时赋值“=”,而不是直接赋值“:=”,因为:
如果命令中用到 $@
或者 $<
时,延时赋值可以展开,直接赋值不会展开。
一定要注意!!!!!
host-csingle 实例
Makefile.host 对 host-csingle 的处理
# scripts/Makefile.host
# C code
# Executables compiled from a single .c file
host-csingle := $(foreach m,$(hostprogs), \
$(if $($(m)-objs)$($(m)-cxxobjs),,$(m))) # 语句1
host-csingle := $(addprefix $(obj)/,$(host-csingle)) # 语句2
$(if ...)
函数的用法,暂略。
此处 hostprogs = fixdep
,第一次带入得 $(fixdep-objs)
和 $(fixdep-cxxobjs)
,因为它们两个展开后,均为空,所以语句1 的返回结果就是 $(host-csingle) = fixdep
语句2 为 fixdep
添加了前缀,$(host-csingle) = scripts/basic/fixdep
所以,$(host-csingle)
指的是那些由单个 .c 编译而成的可执行文件,比如说 fixdep
Makefile.host 为 host-csingle 制定了编译规则。
# Create executable from a single .c file
# host-csingle -> Executable
quiet_cmd_host-csingle = HOSTCC $@
cmd_host-csingle = $(HOSTCC) $(hostc_flags) $(KBUILD_HOSTLDFLAGS) -o $@ $< \
$(KBUILD_HOSTLDLIBS) $(HOSTLDLIBS_$(target-stem))
$(host-csingle): $(obj)/%: $(src)/%.c FORCE
$(call if_changed_dep,host-csingle)
这个编译规则,暂不展开解析了。
再回到 scripts/Makefile.build
Makefile.host 为目标制定了规则。但是,制定规则不代表调用规则。
那 scripts_basic 的规则是怎么被调用的呢?
Makefile.build 的默认编译目标是 __build:
# scripts/Makefile.build
PHONY := __build
__build:
...
__build: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \
$(if $(KBUILD_MODULES), $(targets-for-modules)) \
$(subdir-ym) $(always-y)
@:
其实,这里有个难以理解和追踪的地方了:
__build
中的有效依赖是 $(always-y)
,但是搜索编整个 Makefile 工程都找不到对 $(always-y)
目标的编译——没有为 $(always-y)
设计编译规则。
那么,目标是怎么被编译的呢?
前面 scripts/Makefile.host
中得到了 $(host-csingle)
,虽然 __build
中并没有包含 $(host-csingle)
,但是 $(always-y)
和 $(host-csingle)
包含了相同的目标 scripts/basic/fixdep
。
所以 __build 对 $(always-y)
的依赖就是对 scripts/basic/fixdep
的依赖。
而 scripts/basic/fixdep
的编译规则已经被 $(host-csingle)
制定了。