Kbuild

Kbuild(Kernel Build System)本身是Linux的一些脚本,用于管理和协调内核的编译过程。随着Linux的发展,它如今已经发展成了一个子系统,同时也被其他开源软件所采用,比如U-Boot、BusyBox等

Kbuild基于Makefile,并做了许多扩充:

  • 菜单式配置:Kconfig
  • 预定义目标:
    • xxx_defconfig,menuconfig
    • vmlinux、zImage、uImage
    • modules、install、modules_install
    • clean、mrproper、distclean
  • 预定义变量:
    • obj-y、obj-m、xxx-objs
    • ARCH、CROSS_COMPILE
  • 跨平台工具、递归式Makefile

Kbuild的本质

  • 一个可扩展、可配置的Makefile框架
  • 递归式Makefile、菜单式配置

Kbuild的构成

  • Makefile:顶层目录下的Makefile
  • .config:内核的配置文件
  • arch/$(ARCH)/Makefile:跟平台架构相关的Makefile
  • scripts/Makefile.*:通用编译规则 比如make install时实际上就调用了scripts/Makefile.install
  • Kbuild Makefile:分布在各个子目录下
  • Kconfig:配置菜单,定义每个config symbol的属性(类型、描述、依赖等)

预定义变量

下面介绍一些重要的预定义变量

1
2
3
4
5
6
7
8
9
10
obj-y:=hello.o	# 把hello.o直接链接进内核
obj-y+=stm/ # 递归读取stm/子目录中的Makefile进行编译

obj-m:=hello.o # 把hello.o链接成内核模块

obj-$(CONFIG_HELLO):=hello.o # 结合Kconfig动态配置到底编译成什么

# 如果hello.o由多个源文件组成,则需要定义一个叫hello-objs的变量
hello-objs:=hello1.c hello2.c ...
hello-$(CONFIG_HELLO) += hello_n.c #通过这种方式可以动态控制hello-objs包含什么东西

在新版内核的中,我们常常看见一些叫Kbuild的文件,它实际上做了以下改进

  • 在新版本的内核中,通常把上面这几个内核模块对象(如obj-y)的定义放到单独的文件kbuild
  • 内核在构建时,是先去Kbuild文件中找上面那几个变量,找不到再去Makefile找

预定义目标

Kbuild定义了许多预定义目标,比如make zImagemake modules…都算预定义目标,可通过make help查看

image-20260116165133297

下面整理一些Linux中makefile定义的常见命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 清理$(M)路径下编译产生的中间文件(不影响配置)
make clean

# 导出当前配置对应的最小defconfig
make savedefconfig

# 编译$(M)路径下的内核模块
make modules

# 将编译好的模块安装到 $(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)
# 如果采用这种方式安装,会自动完成模块签名的操作
make modules_install

# 编译设备树文件
make dtbs

配置变量

配置变量(Config Symbols)是我们在编译内核时,经常看到的类似CONFIG_xxx的变量,通常保存在.config文件,用来对于编译行为进行控制

在加载默认配置时,通常要指定ARCH比如

1
make ARH=ARM IMX6ULL_emmc_defconfig

因为IMX6ULL_emmc_defconfig放在arch/arm/configs下,如果不指定ARCH,系统会去arch/x86下找

Kconfig

  • 用来生成配置菜单,配置各种config symbol
  • 生成对应的配置变量:CONFIG_XXX
  • 每个目录下都有一个Kconfig文件
  • 各个Kconfig文件通过source命令构建多级菜单
  • 解析工具:scripts/kconfig/*conf

如果想快速知道配置菜单中某个选项位于哪里,可以看Kconfig文件中对应项的位置

语法

  • config:用来自义菜单选项
  • choice/endchoice
  • comment
  • if/endif
  • source:生成一个树型菜单

参考链接:

举个例子

1
2
3
4
config HELLO //会在.config中生成CONFIG_HELLO配置变量
bool "a hello module"
help
a hello test

image-20260118205022317

.config

如何生成

通过scripts/kbuild/*conf工具解析各级Kconfig文件,最后生成.config文件

image-20260118213015256

如何参与编译工作

生成的.config文件是不能直接参与编译的,需要进一步生成其他文件

  • include/config/auto.conf:用来配置Makefile

    • 顶层Makefile会include这个文件,这样内核Makefile中就有CONFIG_XXX那些变量了
  • include/generated/autoconf.h:供内核里的C程序条件编译(开发驱动时可能需要)

  • include/config/*.h:空头文件,用于构建依赖关系

执行流程

  • 根据用户(内核)的配置生成相应的.config文件
  • 将内核的版本号存入include/linux/version.h
  • 建立指向include/asm-$(ARCH)的符号链接,选定平台
  • 更新所有编译所需的文件
  • 顶层Makefile开始,递归地访问各个子目录,对相应的模块编译生成目标文件
  • 链接过程,在源代码的顶层目录链接生成vmlinux
  • 根据具体架构提供的信息添加相应符号,生成最终的启动镜像,往往不同架构之间的启动方式不一致。
    • 这一部分包含启动指令
    • 准备initrd镜像等平台相关的部分

内核中的Makefile文件

在linux中,由于内核代码的分层模型,以及兼容很多平台的特性,Makefile文件分布在各个目录中,对每个模块进行分离编译,降低耦合性,使编译方式更加灵活。

Makefile主要是以下五个部分:

  • 顶层Makefile : 在源代码的根目录有个顶层Makefile,顶层Makefile的作用就是负责生成两个最重要的部分:编译生成vmlinux和各种模块
  • .config文件 : 这个config文件主要是产生自用户对内核模块的配置,有三种配置方式:
    • 编译进内核
    • 编译成可加载模块
    • 不进行编译
  • arch/$(ARCH)/Makefile : 从目录可以看出,这个 Makefile 主要是根据指定的平台对内核镜像进行相应的配置,提供平台信息给顶层 Makefile
  • scirpts/Makefile. : 这些 Makefile 配置文件包含了构建内核的规则
  • kbuild Makefiles : 每一个模块都是单独被编译然后再链接的,所以这一种 kbiuld Makefile几乎在每个模块中都存在.在这些模块文件(子目录)中,也可以使用 Kbuild 文件代替 Makefile,当两者同时存在时,优先选择 Kbuild 文件进行编译工作,只是用户习惯性地使用 Makefile 来命名

层级关系处理

一个Makefile只负责处理本目录中的编译关系,自然地,其他目录中的文件编译由其他目录的Makefile负责,整个linux内核的Makefile组成一个树状结构,对于上层Makefile的子目录而言,只需要让kbuild知道它应该怎样进行递归地进入目录即可。

kbuild利用目录指定的方式来进行目录指定操作,举个例子:

1
obj-$(CONFIG_FOO) += foo/

当CONFIG_FOO被配置成y或者m时,kbuild就会进入到foo/目录中

  • 注意:这个信息仅仅是告诉kbuild应该进入到哪个目录,而不对其目录中的编译做任何指导

参考链接