2. Linux 内核源码结构:内核源码目录树解析、Kconfig 与 Makefile 基础、内核编译流程

理解 Linux 内核源码的组织结构是驱动开发的第一步。本章将深入解析内核源码树的顶层目录布局、Kconfig 与 Makefile 的协作机制,并梳理从配置到编译的完整流程。掌握这些基础,你才能精准定位驱动代码、正确配置内核选项并高效完成编译。

2.1 内核源码目录树解析

Linux 内核源码采用分层模块化结构,顶层目录按功能领域划分。以下是最核心的目录及其职责:

目录名 功能描述 驱动开发者关注点
arch/ 体系结构相关代码,如 x86、arm、arm64、riscv 板级初始化、DTS 文件、内存映射
drivers/ 设备驱动代码,按子系统分类(gpio、i2c、spi、usb 等) 驱动开发主战场,GPIO 驱动位于 drivers/gpio/
include/ 内核头文件,包含公共 API 和数据结构定义 linux/gpio.h、linux/platform_device.h 等
kernel/ 核心调度、进程管理、中断处理 中断注册、工作队列、定时器
mm/ 内存管理子系统 DMA 缓冲区分配、mmap 实现
Documentation/ 内核文档,包含设备树绑定、API 说明 gpio.txt、pinctrl.txt 等规范文档
scripts/ 编译辅助脚本、Kconfig 解析工具 checkpatch.pl 代码风格检查
驱动开发提示: 90% 的驱动代码编写都在 drivers/ 目录下完成。当你需要为某个 GPIO 控制器编写驱动时,首先查看 drivers/gpio/ 下已有的同类芯片驱动作为参考模板。

下图展示了内核源码顶层目录之间的依赖关系与数据流:

Linux 内核源码顶层目录关系图 Linux 内核源码顶层目录关系图 arch/ (体系结构) drivers/ (设备驱动) kernel/ (核心子系统) include/ (公共头文件) Documentation/ scripts/ (工具) 驱动开发者入口 drivers/gpio/ drivers/i2c/ drivers/spi/

2.2 Kconfig 与 Makefile 基础

内核的配置与编译系统由 Kconfig 和 Makefile 两大核心文件驱动。Kconfig 定义配置选项的菜单结构,Makefile 根据配置结果决定哪些文件参与编译。

2.2.1 Kconfig:配置选项的定义

每个支持配置的子系统或驱动目录下都有一个 Kconfig 文件。以下是一个典型的 GPIO 驱动 Kconfig 条目:

config GPIO_PL061
    tristate "ARM PrimeCell PL061 GPIO support"
    depends on ARM || ARM64
    select GPIOLIB
    help
      Say yes here to support the ARM PrimeCell PL061 GPIO device.
      This driver can be built as a module.
Kconfig 关键字 含义 常用值
config 定义一个新的配置选项 选项名称(如 GPIO_PL061)
tristate 三态选项:不编译/编译进内核/编译为模块 "提示文字"
bool 布尔选项:编译或不编译 "提示文字"
depends on 依赖条件,满足后才显示该选项 体系结构、其他配置选项
select 反向依赖,选中本选项时自动选中另一选项 GPIOLIB、REGMAP 等
help 帮助信息,描述驱动功能 自由文本
常见错误: 新手常忘记添加 depends on 或 select 依赖,导致配置界面中选项不可见,或编译时因缺少符号而失败。务必检查你的驱动依赖了哪些核心子系统(如 GPIOLIB、PINCTRL)。

2.2.2 Makefile:编译规则

与 Kconfig 对应的 Makefile 根据配置变量决定编译行为。驱动目录下的 Makefile 通常只有一行核心规则:

# drivers/gpio/Makefile 示例片段
obj-$(CONFIG_GPIO_PL061)    += gpio-pl061.o
obj-$(CONFIG_GPIO_OMAP)     += gpio-omap.o
obj-$(CONFIG_GPIO_SYSFS)    += gpiolib-sysfs.o

编译系统的工作机制:

  • obj-y:无条件编译进内核镜像
  • obj-m:编译为可动态加载的内核模块(.ko 文件)
  • obj-n:不编译(配置未选中时自动为 n)
  • obj-$(CONFIG_XXX):根据配置结果自动替换为 y、m 或 n

下图展示了 Kconfig 与 Makefile 在配置编译流程中的协作关系:

Kconfig 与 Makefile 协作流程图 Kconfig 与 Makefile 协作流程图 Kconfig 定义选项 make menuconfig 生成 .config Makefile 读取配置 obj-$(CONFIG_XXX) 编译 .o / .ko 核心逻辑:Kconfig 定义“是什么”,Makefile 定义“怎么编”,.config 是两者的桥梁 驱动开发者只需在 Kconfig 中添加选项,在 Makefile 中添加编译目标

2.3 内核编译流程

从源码到可启动内核镜像,需要经过配置、编译、安装三个主要阶段。以下是标准编译流程:

  1. 清理工作区:make distclean 彻底清理,make clean 仅清理编译产物
  2. 加载默认配置:make defconfig 或 make ARCH=arm64 defconfig
  3. 定制配置:make menuconfig 图形化配置,修改驱动选项
  4. 编译内核:make -j$(nproc) 并行编译内核镜像和模块
  5. 编译设备树:make dtbs 编译设备树二进制文件(.dtb)
  6. 安装模块:make modules_install 将模块安装到指定目录
  7. 安装内核:make install 将内核镜像复制到 /boot
嵌入式交叉编译示例: 对于 ARM 目标板,使用交叉编译工具链:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4

下表总结了不同编译目标的用途:

make 目标 输出文件 用途
zImage arch/arm/boot/zImage 压缩后的内核镜像(ARM 平台)
Image arch/arm64/boot/Image 未压缩内核镜像(ARM64 平台)
dtbs arch/arm/boot/dts/*.dtb 设备树二进制文件
modules drivers/ 下各目录的 .ko 文件 可动态加载的驱动模块
all 上述全部 默认目标,编译内核+模块+dtbs

下图展示了从源码到部署的完整编译与安装流程:

内核编译与部署完整流程图 内核编译与部署完整流程图 源码树 make menuconfig .config make -jN vmlinux / zImage / Image *.ko 内核模块 *.dtb 设备树 make modules_install make install 目标板启动与驱动加载
效率技巧: 开发驱动时,不需要每次都完整编译整个内核。使用 make drivers/gpio/ 可以只编译 GPIO 子系统下的驱动,大幅缩短编译时间。调试阶段推荐将驱动编译为模块(.ko),通过 insmod / rmmod 快速验证。