2. 内核编译流程

内核编译并非简单的 make 命令,而是一套由 Kconfig 与 Makefile 协同驱动的复杂构建系统。理解这一流程是进行高效裁剪与加速编译的前提。本章将拆解从配置到生成二进制镜像的完整链路。

2.1 Kconfig 与 Makefile 的交互机制

Kconfig 定义了内核功能的菜单树,而 Makefile 则根据配置结果决定哪些文件参与编译。两者通过 .config 文件中的 CONFIG_* 变量进行桥接。

核心交互流程: Kconfig 定义选项 → 用户通过 make menuconfig 选择 → 生成 .config → Makefile 读取 .config 中的 CONFIG_* 变量 → 条件编译对应源文件。
Kconfig 与 Makefile 交互流程 Kconfig 定义 menu / config / choice make menuconfig 可视化配置界面 .config 文件 CONFIG_*=y/m/n Makefile 解析 obj-$(CONFIG_*) += vmlinux / .ko

2.2 配置生成:从 Kconfig 到 .config

.config 是内核编译的唯一配置真相源。其生成过程遵循严格的依赖解析与冲突检测规则。

2.2.1 配置生成步骤

  1. 默认配置加载:make defconfig 或 make ARCH=arm64 defconfig 生成基于架构的默认配置。
  2. 交互式调整:make menuconfig / make nconfig / make xconfig 提供图形化界面修改。
  3. 依赖自动解析:Kconfig 的 depends on 和 select 规则被自动满足,缺失依赖的选项会被强制关闭或自动选中。
  4. 冲突检测:make olddefconfig 自动将未定义的选项设为默认值,并解决符号冲突。
  5. 保存输出:最终写入 .config,同时生成 include/config/auto.conf 供 Makefile 快速读取。
性能优化提示: 使用 make savedefconfig 可生成最小化 defconfig 文件,仅包含非默认值的选项,便于版本管理。

2.2.2 .config 文件结构示例

# 三态选项:编译进内核 (y)、编译为模块 (m)、禁用 (n)
CONFIG_NET=y
CONFIG_IPV6=m
CONFIG_IPV6_SIT=m
# CONFIG_IPV6_TUNNEL is not set

# 整型与字符串选项
CONFIG_HZ=250
CONFIG_CMDLINE="console=ttyS0,115200"

# 依赖自动生成的选项
CONFIG_IPV6_SIT_6RD=y          # 由 CONFIG_IPV6_SIT 自动 select

2.3 编译目标与模块编译

内核 Makefile 提供了丰富的编译目标,理解其分类有助于加速构建过程。

2.3.1 主要编译目标分类

目标 说明 典型耗时
all / vmlinux 编译完整内核镜像(包含所有 =y 的代码) 最长
modules 仅编译标记为 =m 的内核模块 中等
modules_install 将编译好的模块安装到 /lib/modules/$(uname -r)/ 短
dtbs 编译设备树二进制文件(ARM/ARM64/RISC-V 架构) 短
clean / mrproper 清理编译产物;mrproper 额外删除 .config 极短

2.3.2 模块编译的两种模式

  • 树内模块编译:make M=fs/btrfs 仅编译指定路径下的模块,依赖已构建好的内核树。
  • 树外模块编译:make -C /lib/modules/$(uname -r)/build M=$(pwd) modules,用于驱动开发,依赖已安装的内核头文件。
模块编译流程(树内 vs 树外) 树内模块编译 make M=fs/btrfs 生成 fs/btrfs/btrfs.ko 树外模块编译 make -C /lib/modules/.../build M=$(pwd) 生成 mydriver.ko(独立目录)

2.4 编译流程中的关键文件与变量

文件/变量 作用 生成时机
.config 用户配置的最终存储,Makefile 直接读取 每次 make *config 后
include/config/auto.conf .config 的预处理版本,供 Makefile 快速 include 配置生成后自动同步
include/generated/autoconf.h C 语言头文件,将 CONFIG_* 转为 #define 宏 配置生成后自动生成
Module.symvers 记录模块导出的符号及 CRC 校验值,用于模块间依赖 模块编译时生成
$(obj-y) Makefile 变量,包含所有 =y 的目标文件,链接入 vmlinux Makefile 解析时
$(obj-m) Makefile 变量,包含所有 =m 的目标文件,编译为 .ko Makefile 解析时
常见陷阱: 修改 .config 后必须执行 make olddefconfig 或 make menuconfig 并保存,否则 Makefile 不会识别手动编辑的配置。直接编辑 .config 而不更新 auto.conf 会导致编译使用旧配置。

2.5 编译流程的并行化与加速

内核编译天然支持并行,合理利用多核 CPU 可显著缩短编译时间。

  • 并行编译:make -j$(nproc) 使用所有 CPU 核心。建议 -j 参数设为 CPU 核心数的 1.5~2 倍以掩盖 I/O 等待。
  • 局部编译:make drivers/net/ 仅编译网络驱动子系统,适合调试时快速验证。
  • 编译缓存:使用 ccache 可缓存编译中间结果,二次编译加速明显。
  • LD 优化:使用 make LD=ld.lld 启用 LLVM 的 lld 链接器,链接阶段比 GNU ld 快 2~5 倍。
# 典型加速编译命令组合
make -j$(nproc) CC=clang LD=ld.lld LLVM=1

# 仅编译修改过的模块
make -j$(nproc) M=drivers/net/ethernet/intel/igb