之前一直在搞 RTOS 与 ARM 底层,对 linux 用的不多,现在是时候学习 linux 下的驱动框架与内核了,感谢网上那么多的学习资料(正点原子、知乎、CSDN...
)
linux 使用的编译工程框架为 Kbuild, 如果对 Kbuild 一无所知,在阅读本篇文章之前,建议阅读一遍 linux 内核工程中的下列文件(路径为 ./Documentation/kbuild/
):
top Makefile 为 liunx 内核根目录下的 Makefile 文件:
搭建交叉编译环境就不说了,直接在 SOC 原厂提供的 linux 工程根目录下运行如下命令:
# 清除工程
make CROSS_COMPILE=aarch64-none-linux-gnu- ARCH=arm64 distclean
# defconfig 为 linux 特有的工程配置文件,控制哪些模块需要编译(编译进镜像或编译成 .ko)
# 并会在工程根目录下生成 .config 文件
make CROSS_COMPILE=aarch64-none-linux-gnu- ARCH=arm64 defconfig
# 编译生成 linux 镜像, 在工程根目录下会生成 vmlinux 可执行文件
# 在 arch/(ARCH)/boot/ 目录下会生成 image.gz, 可由 uboot 下载启动
make CROSS_COMPILE=aarch64-none-linux-gnu- ARCH=arm64 -j4
help 命令可以帮助我们查看基本的编译目标与命令,如果对 makefile 工程不熟悉,建议先运行此命令,熟悉主要的编译目标与作用:
make help
作用为清除工程,包括所有中间文件,临时文件,目标文件,可执行文件:
make CROSS_COMPILE=aarch64-none-linux-gnu- ARCH=arm64 distclean
defconfig 文件为 linux 特有的工程配置文件,控制哪些模块需要编译(编译进镜像或编译成 .ko),并会在工程根目录下生成 .config 文件。
defconfig 文件在 arch/(ARCH)/config/
目录下:
执行该命令时,对应 Top-Makefile 的如下规则:
# 该规则会根据 defconfig 文件,在工程根目录下生成 .config 文件
%config: scripts_basic outputmakefile FORCE
$(Q)$(MAKE) $(build)=scripts/kconfig $@
build 变量为 Kbuild 框架里的一个非常重要的变量,用于调用 Kbuild 编译框架,并指定编译模块的路径.
该变量定义在 scripts/Kbuild.include
文件中:
###
# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj=
# Usage:
# $(Q)$(MAKE) $(build)=dir
build := -f $(srctree)/scripts/Makefile.build obj
对 build 变量解析如下:
-f
选项指定 make 要运行的 makefile 文件为 $(srctree)/scripts/Makefile.build(一般为 ./scripts/makefile.build)
. 这个 makefile 是 Kbuild 框架的总入口文件,所有模块的编译都需要通过这个 makefile 文件进行编译。obj
变量, obj 变量用来指示目标模块的路径,并实现包含目标模块下的makefile。在此只做简述,后续会详解 linux 的 Kbuild 编译框架。
对应的代码在 makefile.build 文件中:
通过如下代码实现包含目标模块下的 makefile:
# 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 文件,那么会使用 Kbuild 作为要执行的 makefile。
根据上述信息,可展开 defconfig 目标对应的规则:
# 该规则会根据 defconfig 文件,在工程根目录下生成 .config 文件
defconfig: scripts_basic outputmakefile FORCE
make -f ./scripts/Makefile.build obj=scripts/kconfig defconfig
其中,依赖的 scripts_basic
与 outputmakefile
对应其他的目标规则,但这两个依赖主要用于生成辅助工具,暂不细究。
命令展开为:
make -f ./scripts/Makefile.build obj=scripts/kconfig defconfig
即执行 scripts/kconfig/Makefile 文件,并生成 defconfig 目标,对应的命令如下:
defconfig: $(obj)/conf
ifeq ($(KBUILD_DEFCONFIG),)
$< $(silent) --defconfig $(Kconfig)
else
ifneq ($(wildcard $(srctree)/arch/$(SRCARCH)/configs/$(KBUILD_DEFCONFIG)),)
@$(kecho) "*** Default configuration is based on '$(KBUILD_DEFCONFIG)'"
$(Q)$< $(silent) --defconfig=arch/$(SRCARCH)/configs/$(KBUILD_DEFCONFIG) $(Kconfig)
else
@$(kecho) "*** Default configuration is based on target '$(KBUILD_DEFCONFIG)'"
$(Q)$(MAKE) -f $(srctree)/Makefile $(KBUILD_DEFCONFIG)
endif
endif
展开为 $(obj)/conf --defconfig=arch/$(arm64)/configs/defconfig Kconfig
,该命令的作用就是通过 defconfig 文件,生成 .config 文件。
当 make 不指定目标时,会使用 makefile 的第一个目标 _all
作为默认目标:
_all
目标会被会被覆盖:
此时默认编译目标为 all
, all
目标对应的规则为:
虽然指定了:
# The all: target is the default when no target is given on the
# command line.
# This allow a user to issue only 'make' to build a kernel including modules
# Defaults to vmlinux, but the arch makefile usually adds further targets
all: vmlinux
但是在 include arch/$(SRCARCH)/Makefile
文件中,会被覆盖为:
all: Image.gz $(KBUILD_DTBS)
通过如上的目标规则转换,make 默认目标的编译会生成 image.gz
, vmlinux
, *.dtb
文件。
主要分析 vmlinux 编译生成流程。
这部分需要通读一遍 Top-Makefile, 以下依次列出 vmlinux 的目标规则转换路径,并说明:
vmlinux-dep
目标:vmlinux: scripts/link-vmlinux.sh autoksyms_recursive $(vmlinux-deps) FORCE
ifdef CONFIG_HEADERS_CHECK
$(Q)$(MAKE) -f $(srctree)/Makefile headers_check
endif
ifdef CONFIG_GDB_SCRIPTS
$(Q)ln -fsn $(abspath $(srctree)/scripts/gdb/vmlinux-gdb.py)
endif
+$(call if_changed,link-vmlinux)
vmlinux-dep
变量包含所有的目标文件与目标模块子目录:# Externally visible symbols (used by link-vmlinux.sh)
export KBUILD_VMLINUX_INIT := $(head-y) $(init-y)
export KBUILD_VMLINUX_MAIN := $(core-y) $(libs-y2) $(drivers-y) $(net-y) $(virt-y)
export KBUILD_VMLINUX_LIBS := $(libs-y1)
export KBUILD_LDS := arch/$(SRCARCH)/kernel/vmlinux.lds
export LDFLAGS_vmlinux
# used by scripts/package/Makefile
export KBUILD_ALLDIRS := $(sort $(filter-out arch/%,$(vmlinux-alldirs)) arch Documentation include samples scripts tools)
vmlinux-deps := $(KBUILD_LDS) $(KBUILD_VMLINUX_INIT) $(KBUILD_VMLINUX_MAIN) $(KBUILD_VMLINUX_LIBS)
vmlinux-dep
目标依赖 vmlinux-dirs
目标:# The actual objects are generated when descending,
# make sure no implicit rule kicks in
$(sort $(vmlinux-deps)): $(vmlinux-dirs) ;
vmlinux-dirs
变量是一个包含目录各个目录的变量,用于对 build
变量赋值:vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) $(init-m) \
$(core-y) $(core-m) $(drivers-y) $(drivers-m) \
$(net-y) $(net-m) $(libs-y) $(libs-m) $(virt-y)))
vmlinux-dirs
目标实现对所有子模块的递归编译:# Handle descending into subdirectories listed in $(vmlinux-dirs)
# Preset locale variables to speed up the build process. Limit locale
# tweaks to this spot to avoid wrong language settings when running
# make menuconfig etc.
# Error messages still appears in the original language
PHONY += $(vmlinux-dirs)
$(vmlinux-dirs): prepare scripts
$(Q)$(MAKE) $(build)=$@ need-builtin=1
最终,通过第5步的 $(Q)$(MAKE) $(build)=$@ need-builtin=1
, 会调用 Kbuild 编译框架,对目标模块进行编译,而在 Makefile.build 文件中,同样会处理子目录的递归编译。此处暂不细究,需要通读 Kbuild 框架相关的 Makefile.
通过变量 init-y/m
, core-y/m
, drivers-y/m
,net-y/m
, libs-y/m
, virt-y
来实现需要编译的子目录
通过变量 obj-y/m
(以及其他变量),可以控制需要编译生成哪些目标文件
变量obj-y
所指定的目标文件会编译进内核;
变量 obj-m
所指定的目标文件会编译为模块。