0. kbuild的组成
0.1 构成文件
顶层Makefile
.config
arch/$(ARCH)/Makefile
各个目录下的Makefile
scripts/Makefile.*
0.2 预定义的目标和变量
obj-m
obj-y
xxx-objs
zImage
menuconfig
0.2 工作流程
include arch/$(ARCH)/Makefile
读取.config,读取用户的各种配置变量,
解析预定义目标,构建依赖关系
编译各个模块或组件
- 将每个目录下的源文件编译为对应的.o
- 将.o归档为built-in.a
将built-in.a链接为vmlinux
1. kbuild的使用
make ARCH=arm vexpress_defconfig
make menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- uImage LOADADDR=0x60003000
注意 LOADADDR 没有硬性要求,只要在 RAM 地址范围 几乎都可以,比如可以设置为 0x60000000
kbuild给开发者提供了如下 变量
obj-m : 内容为所有需要以模块形式生产的组件
obj-y : 内容为所有需要编译进内核的组件
xxx-objs : 代表一个组件,依赖的目标文件
开发者通常只需要通过 menuconfig 设置上诉变量,原理是
若有一个 hello 模块,则 Kconfig 文件有如下定义
config HELLO
tristate "hello"
default n
help
for test
开发者运行完 menuconfig 后,主目录会生成 .config,其中有
CONFIG_HELLO=m
或
CONFIG_HELLO=y
在进行编译时,hello模块目录下有Makefile文件,内容为
obj-$(CONFIG_HELLO) += hello.o
如果 CONFIG_HELLO=m,则hello.o 会生成 hello-objs 目标,hello-objs目标的默认依赖是 hello.o。
所以开发者可以容易的编译内核和模块。
2. .config
2.1 生成.config的原理
执行 xxx_defconfig,或 menuconfig 都是如下目标
%config: outputmakefile scripts_basic FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
MAKE 变量 和 build 变量
root@ubuntu:~/wlt/build/linux-5.16.2# grep "^build\s*[: ]=" -rn ./
$(srctree)/scripts/Makefile.build obj
root@ubuntu:~/wlt/build/linux-5.16.2# grep "^MAKE\s*[: ]=" -rn ./
./tools/bpf/Makefile:8:MAKE = make
MAKE : make,
build : -f $(srctree)/scripts/Makefile.build obj
所以上面的make的执行是
make -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig xxx_defconfig
make -f $(srctree)/scripts/Makefile.build obj=scripts/kconfig menuconfig
scripts/Makefile.build
src := $(obj)
# The filename Kbuild has precedence over Makefile
kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
include $(kbuild-file)
kbuild-dir, 如果 src是绝对路径则使用src,否则改为绝对路径.
kbuild-file,如果 $(kbuild-dir)/Kbuild 文件存在,则用$(kbuild-dir)/Kbuild,否则用$(kbuild-dir)/Makefile
这里,kbuild-file为scripts/kconfig/Makefile
scripts/kconfig/Makefile
menuconfig-prog := mconf
$(foreach c, config menuconfig nconfig gconfig xconfig, $(eval $(call config_rule,$(c))))
%_defconfig: $(obj)/conf
$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$@ $(Kconfig)
当使用 xxx_defconfig,执行
$(obj)/conf $(silent) --defconfig=arch/$(SRCARCH)/configs/xxx_defconfig $(Kconfig)
可见是 使用命令 conf ,并且参数为 arch/$(SRCARCH)/configs/xxx_defconfig
当使用 menuconfig,则调用函数 config_rule
39 define config_rule
40 PHONY += $(1)
41 $(1): $(obj)/$($(1)-prog)
42 $(Q)$$< $(silent) $(Kconfig)
也就是规则
41 meunconfig: $(obj)/$(menuconfig-prog)
42 $(Q)$$< $(silent) $(Kconfig)
可见是运行命令 mconf
所以 mconf 最终生成根目录的 .config
2.2 构建时.config如何指导构建
2.2.1 syncconfig
kbuild根据 .config 生成
include/config/auto.conf 用来配置Makefile
include/generated/autoconf.h 给C程序使用
include/config/*.h 本质是空文件,用于构建依赖关系
733 %/config/auto.conf %/config/auto.conf.cmd %/generated/autoconf.h: $(KCONFIG_CONFIG)
734 $(Q)$(kecho) " SYNC $@"
735 $(Q)$(MAKE) -f $(srctree)/Makefile syncconfig
和上面的分析一样,最终到
scripts/kconfig/Makefile
70 simple-targets := oldconfig allnoconfig allyesconfig allmodconfig \
71 alldefconfig randconfig listnewconfig olddefconfig syncconfig \
72 helpnewconfig yes2modconfig mod2yesconfig
76 $(simple-targets): $(obj)/conf
77 $(Q)$< $(silent) --$@ $(Kconfig)
所以执行的命令是
$(obj)/conf $(silent) --syncconfig $(Kconfig)
2.2 include/config/auto.conf
主Makefile,默认会加载 auto.conf 文件
284 need-config := 1
663 ifdef need-config
664 include include/config/auto.conf
665 endif
auto.conf内容是用户配置
9 CONFIG_KERNEL_GZIP=y
10 CONFIG_DEFAULT_INIT=""
在make构建时,会根据auto.conf的输入,设置变量 obj-y obj-m。
21 obj-$(CONFIG_PARISC) += parisc/
22 obj-$(CONFIG_RAPIDIO) += rapidio/
2.3 include/generated/autoconf.h
include/generated/autoconf.h的内容是宏定义
9 #define CONFIG_KERNEL_GZIP 1
10 #define CONFIG_DEFAULT_INIT ""
源文件可以使用这些宏,已实现功能配置
536 #ifdef CONFIG_USB_OHCI_BIG_ENDIAN_DESC
537 #ifdef CONFIG_USB_OHCI_LITTLE_ENDIAN
538 #define big_endian_desc(ohci) (ohci->flags & OHCI_QUIRK_BE_DESC)
539 #else
540 #define big_endian_desc(ohci) 1 /* only big endian */
541 #endif
542 #else
543 #define big_endian_desc(ohci) 0 /* only little endian */
544 #endif
2.3 include/config下的空文件
Kbuild需要追踪的
-
编译所需所有源文件和头文件
kbuild使用 gcc -MD a.c 获得 a.c 依赖的 *.h 文件,生成 a.d 文件,对 a.d 文件进行整理后生成文件 .a.c.cmd,内容为 a.c 的依赖关系供make使用。 -
编译配置选项
kbuild 将根据使用的配置变量(CONFIG_XXX),在include/config/下生成对应名称的.h空文件,并根据使用此配置变量的.c文件,为.c文件添加依赖关系,记录到 .xx.c.cmd 中
3. vmlinux
3.1 vmlinux的构建
core-y := init/ usr/ arch/$(SRCARCH)/
drivers-y := drivers/ sound/
drivers-$(CONFIG_SAMPLES) += samples/
drivers-$(CONFIG_NET) += net/
drivers-y += virt/
libs-y := lib/
KBUILD_VMLINUX_OBJS := $(head-y) $(patsubst %/,%/built-in.a, $(core-y))
KBUILD_VMLINUX_OBJS += $(addsuffix built-in.a, $(filter %/, $(libs-y)))
ifdef CONFIG_MODULES
KBUILD_VMLINUX_OBJS += $(patsubst %/, %/lib.a, $(filter %/, $(libs-y)))
KBUILD_VMLINUX_LIBS := $(filter-out %/, $(libs-y))
else
KBUILD_VMLINUX_LIBS := $(patsubst %/,%/lib.a, $(libs-y))
endif
KBUILD_VMLINUX_OBJS += $(patsubst %/,%/built-in.a, $(drivers-y))
export KBUILD_VMLINUX_OBJS KBUILD_VMLINUX_LIBS
export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
# used by scripts/Makefile.package
export KBUILD_ALLDIRS := $(sort $(filter-out arch/%,$(vmlinux-alldirs)) LICENSES arch include scripts tools)
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_OBJS) $(KBUILD_VMLINUX_LIBS)
vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE
+$(call if_changed_dep,link-vmlinux)
可以看出vmlinux-deps 就是各个目录的 built-in.a
cmd_link-vmlinux = \
$(CONFIG_SHELL) $< "$(LD)" "$(KBUILD_LDFLAGS)" "$(LDFLAGS_vmlinux)"; \
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)
CONFIG_SHELL就是 sh,$< 就是 scripts/link-vmlinux.sh,这个脚本的注释说明该脚本完成:
- 将 built-in.a lib.a 链接成vmlinux
- 生成 System.map,内容是所以内核符号表
3.2 built-in.a
# The actual objects are generated when descending,
# make sure no implicit rule kicks in
$(sort $(vmlinux-deps) $(subdir-modorder)): descend ;
PHONY += descend $(build-dirs)
descend: $(build-dirs)
$(build-dirs): prepare
$(Q)$(MAKE) $(build)=$@ \
single-build=$(if $(filter-out $@/, $(filter $@/%, $(KBUILD_SINGLE_TARGETS))),1) \
need-builtin=1 need-modorder=1
可见编译vmlinux时,首先生成目标 descend,而descend依赖为 $(build-dirs)、
而 $(build-dirs) 根据编译内核或编译模块而不同,以编译内核为例
core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/
core-$(CONFIG_BLOCK) += block/
# 先找出形如 %/的,再去掉 /,得到%
vmlinux-dirs := $(patsubst %/,%,$(filter %/, \
$(core-y) $(core-m) $(drivers-y) $(drivers-m) \
$(libs-y) $(libs-m)))
build-dirs := $(vmlinux-dirs)
所以 build-dirs 为 kernel certs mm fs ipc security ...
$(build-dirs): prepare
$(Q)$(MAKEjjjj) $(build)=$@ \
single-build=$(if $(filter-out $@/, $(filter $@/%, $(KBUILD_SINGLE_TARGETS))),1) \
need-builtin=1 need-modorder=1
./scripts/Kbuild.include:75:build := -f $(srctree)/scripts/Makefile.build obj
展开后为
kernel certs mm fs ipc security block crypto:prepare
$(Q)make -f $(srctree)/scripts/Makefile.build obj=$@ \
single-build=$(if $(filter-out $@/, $(filter $@/%, $(KBUILD_SINGLE_TARGETS))),1) \
need-builtin=1 need-modorder=1
$(srctree)/scripts/Makefile.build
src := $(obj)
# 定义默认目标为 __build
PHONY := __build
__build:
# 先清空 obj-m obj-y
obj-y :=
obj-m :=
# 根据传入的obj,加载不同Makefile,以设置obj-y obj-m
# The filename Kbuild has precedence over Makefile
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 $(kbuild-file) 根据 obj 的不同而不同,如obj为kernel,则 include kernel/Makefile。
子目录下的Makefile最关键定义了 obj-y 和 obj-m ,然后构建 __build
ifdef single-build
KBUILD_SINGLE_TARGETS := $(filter $(obj)/%, $(KBUILD_SINGLE_TARGETS))
curdir-single := $(sort $(foreach x, $(KBUILD_SINGLE_TARGETS), \
$(if $(filter $(x) $(basename $(x)).o, $(targets)), $(x))))
# Handle single targets without any rule: show "Nothing to be done for ..." or
# "No rule to make target ..." depending on whether the target exists.
unknown-single := $(filter-out $(addsuffix /%, $(subdir-ym)), \
$(filter-out $(curdir-single), $(KBUILD_SINGLE_TARGETS)))
single-subdirs := $(foreach d, $(subdir-ym), \
$(if $(filter $(d)/%, $(KBUILD_SINGLE_TARGETS)), $(d)))
__build: $(curdir-single) $(single-subdirs)
ifneq ($(unknown-single),)
$(Q)$(MAKE) -f /dev/null $(unknown-single)
endif
@:
ifeq ($(curdir-single),)
# Nothing to do in this directory. Do not include any .*.cmd file for speed-up
targets :=
else
targets += $(curdir-single)
endif
else
__build: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \
$(if $(KBUILD_MODULES), $(targets-for-modules)) \
$(subdir-ym) $(always-y)
@:
endif
构建vmlinux时,则 signal-build为 0,所以执行
__build: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \
$(if $(KBUILD_MODULES), $(targets-for-modules)) \
$(subdir-ym) $(always-y)
@:
构建vmlinux时,__build的依赖为 $(targets-for-builtin)$(subdir-ym) $(always-y)
targets-for-builtin := $(extra-y)
ifneq ($(strip $(lib-y) $(lib-m) $(lib-)),)
targets-for-builtin += $(obj)/lib.a
endif
ifdef need-builtin
targets-for-builtin += $(obj)/built-in.a
endif
这里只分析 targets-for-builtin += $(obj)/built-in.a 情况。
# combine symversions for later processing
ifeq ($(CONFIG_LTO_CLANG) $(CONFIG_MODVERSIONS),y y)
cmd_update_lto_symversions = \
rm -f [email protected] \
$(foreach n, $(filter-out FORCE,$^), \
$(if $(shell test -s $(n).symversions && echo y), \
; cat $(n).symversions >> [email protected]))
else
cmd_update_lto_symversions = echo >/dev/null
endif
quiet_cmd_ar_builtin = AR $@
cmd_ar_builtin = rm -f $@; $(AR) cDPrST $@ $(real-prereqs)
quiet_cmd_ar_and_symver = AR $@
cmd_ar_and_symver = $(cmd_update_lto_symversions); $(cmd_ar_builtin)
$(obj)/built-in.a: $(real-obj-y) FORCE
$(call if_changed,ar_and_symver)
可见 built-in.a 的规则最关键的是 rm -f $@; $(AR) cDPrST $@ $(real-prereqs)
而依赖是 $(real-obj-y)
suffix-search = $(strip $(foreach s, $3, $($(1:%$(strip $2)=%$s))))
real-search = $(foreach m, $1, $(if $(call suffix-search, $m, $2, $3 -), $(call suffix-search, $m, $2, $3), $m))
real-obj-y := $(call real-search, $(obj-y), .o, -objs -y)
real-obj-m := $(call real-search, $(obj-m), .o, -objs -y -m)
以上实现找到真正的 .o文件,比如 obj-y 是 hello,且 hello-objs 是 a.o b.o c.o ,则找到真正依赖的.o文件是 a.o b.o c.o
所以下面构建.o文件
$(obj)/%.o: $(src)/%.S FORCE
$(call if_changed_rule,as_o_S)
$(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
$(call if_changed_rule,cc_o_c)
$(call cmd,force_checksrc)
if_changed_rule是
if_changed_rule = $(if $(if-changed-cond),$(rule_$(1)),@:)
所以 cc_o_c 为 rule_cc_o_c, as_o_S 为 rule_as_o_S
define rule_cc_o_c
$(call cmd_and_fixdep,cc_o_c)
$(call cmd,gen_ksymdeps)
$(call cmd,checksrc)
$(call cmd,checkdoc)
$(call cmd,gen_objtooldep)
$(call cmd,modversions_c)
$(call cmd,record_mcount)
endef
define rule_as_o_S
$(call cmd_and_fixdep,as_o_S)
$(call cmd,gen_ksymdeps)
$(call cmd,gen_objtooldep)
$(call cmd,modversions_S)
endef
说明 rule_cc_o_c 不仅进行编译,还进行检查,和生成其他辅助文件,其中编译部分为
quiet_cmd_cc_o_c = CC $(quiet_modtag) $@
cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< $(cmd_objtool)
quiet_cmd_as_o_S = AS $(quiet_modtag) $@
cmd_as_o_S = $(CC) $(a_flags) -c -o $@ $< $(cmd_objtool)
所以%.o的构建依赖 .S .c 文件,规则是编译。
4. zImage 的构建
arch/$(ARCH)/Makefile
boot := arch/arm/boot
ifeq ($(CONFIG_XIP_KERNEL),y)
KBUILD_IMAGE := $(boot)/xipImage
else
KBUILD_IMAGE := $(boot)/zImage
endif
BOOT_TARGETS = zImage Image xipImage bootpImage uImage
$(BOOT_TARGETS): vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
@$(kecho) ' Kernel: $(boot)/$@ is ready'
zImage: Image
当构建 Image时,
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@ 展开
make -f script/Makefile.build obj=arch/arm/boot MACHINE=$(MACHINE) arch/arm/boot/Image
所以 使用 script/Makefile.build 进行构建,目标为 arch/arm/boot/Image
script/Makefile.build 先 include arch/arm/boot/Makefile
且 $(obj)/Image就是 arch/arm/boot/Image,所以规则为
$(obj)/xipImage: FORCE
@echo 'Kernel not configured for XIP (CONFIG_XIP_KERNEL!=y)'
@false
$(obj)/Image: vmlinux FORCE
$(call if_changed,objcopy)
$(obj)/compressed/vmlinux: $(obj)/Image FORCE
$(Q)$(MAKE) $(build)=$(obj)/compressed $@
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
if_changed
if-changed-cond = $(newer-prereqs)$(cmd-check)$(check-FORCE)
cmd = @set -e; $(echo-cmd) $($(quiet)redirect) $(cmd_$(1))
if_changed = $(if $(if-changed-cond), \
$(cmd); \
printf '%s\n' 'cmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)
所以最终执行的规则是
cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@
所以 Image的生成命令为
$(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) vmlinux Image
然后生成$(obj)/compressed/vmlinux
HEAD = head.o
OBJS += misc.o decompress.o
$(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.o \
$(addprefix $(obj)/, $(OBJS)) $(lib1funcs) $(ashldi3) \
$(bswapsdi2) $(efi-obj-y) FORCE
@$(check_for_multiple_zreladdr)
$(call if_changed,ld)
@$(check_for_bad_syms)
$(obj)/piggy_data: $(obj)/../Image FORCE
$(call if_changed,$(compress-y))
$(obj)/piggy.o: $(obj)/piggy_data
可见 $(obj)/compressed/vmlinux 是 将 Image 进行压缩,并链接上 head.o misc.o decompress.o 。
然后构建 $(obj)/zImage
$(obj)/zImage: $(obj)/compressed/vmlinux FORCE
$(call if_changed,objcopy)
可见 zImage 就是 $(obj)/compressed/vmlinux 进行 objcopy
5. uImage
BOOT_TARGETS = zImage Image xipImage bootpImage uImage
bootpImage uImage: zImage
$(BOOT_TARGETS): vmlinux
$(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
@$(kecho) ' Kernel: $(boot)/$@ is ready'
找到
$(obj)/uImage: $(obj)/zImage FORCE
@$(check_for_multiple_loadaddr)
$(call if_changed,uimage)
quiet_cmd_uimage = UIMAGE $@
cmd_uimage = $(BASH) $(MKIMAGE) -A $(UIMAGE_ARCH) -O linux \
-C $(UIMAGE_COMPRESSION) $(UIMAGE_OPTS-y) \
-T $(UIMAGE_TYPE) \
-a $(UIMAGE_LOADADDR) -e $(UIMAGE_ENTRYADDR) \
-n $(UIMAGE_NAME) -d $< $@
所以最终命令为
mkimage -A arm -O linux -T kernel -C none -a 0x60003000 -e 0x60003000 -d zImage uImage
-T image类型
-C 压缩方式,none gzip gzip2 等
-a 内核加载地址
-e 内核入口地址
uImage 和 zImage 的差别,uImage 多了 0x40 长度的头部。
uImage 的相对zImage 的优点,uboot也可以加载运行zImage,只是zImage的加载地址就是运行地址,而uImage可以加载到任何地址,uboot会保证运行地址。
7. modules
# 这里三个依赖都是准备工作,用于生成 module.mod文件 module.order 文件等
modules: $(if $(KBUILD_BUILTIN),vmlinux) modules_check modules_prepare
# 关键从这里的规则开始
modules: modules_check
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost
PHONY += modules_check
modules_check: $(MODORDER)
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/modules-check.sh $<
quiet_cmd_depmod = DEPMOD $(MODLIB)
cmd_depmod = $(CONFIG_SHELL) $(srctree)/scripts/depmod.sh $(DEPMOD) \
$(KERNELRELEASE)
modules_install:
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modinst
$(call cmd,depmod)
modules_prepare: prepare
$(Q)$(MAKE) $(build)=scripts scripts/module.lds
$(srctree)/scripts/Makefile.modpost 的默认目标是 __modpost
__modpost: $(output-symdump)
ifneq ($(KBUILD_MODPOST_NOFINAL),1)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modfinal
endif
$(srctree)/scripts/Makefile.modfinal的默认目标是 __modfinal
# find all modules listed in modules.order
modules := $(sort $(shell cat $(MODORDER)))
__modfinal: $(modules)
@:
其中 modules.order 的内容 为
a.ko
b.ko
quiet_cmd_ld_ko_o = LD [M] $@
cmd_ld_ko_o += \
$(LD) -r $(KBUILD_LDFLAGS) \
$(KBUILD_LDFLAGS_MODULE) $(LDFLAGS_MODULE) \
-T scripts/module.lds -o $@ $(filter %.o, $^); \
$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)
# Re-generate module BTFs if either module's .ko or vmlinux changed
$(modules): %.ko: %$(mod-prelink-ext).o %.mod.o scripts/module.lds $(if $(KBUILD_BUILTIN),vmlinux) FORCE
+$(call if_changed_except,ld_ko_o,vmlinux)
ifdef CONFIG_DEBUG_INFO_BTF_MODULES
+$(if $(newer-prereqs),$(call cmd,btf_ko))
endif
其中 mod-prelink-ext 通常为空
ifeq ($(CONFIG_LTO_CLANG),y)
# With CONFIG_LTO_CLANG, .o files in modules might be LLVM bitcode, so we
# need to run LTO to compile them into native code (.lto.o) before further
# processing.
mod-prelink-ext := .lto
endif
将目标进行替换,找到依赖,如 a.ko
a.ko: a.o a.mod.o scripts/module.lds $(if $(KBUILD_BUILTIN),vmlinux)