2. Linux内核源码结构
理解 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/ 下已有的同类芯片驱动作为参考模板。
下图展示了内核源码顶层目录之间的依赖关系与数据流:
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 在配置编译流程中的协作关系:
2.3 内核编译流程
从源码到可启动内核镜像,需要经过配置、编译、安装三个主要阶段。以下是标准编译流程:
- 清理工作区:
make distclean彻底清理,make clean仅清理编译产物 - 加载默认配置:
make defconfig或make ARCH=arm64 defconfig - 定制配置:
make menuconfig图形化配置,修改驱动选项 - 编译内核:
make -j$(nproc)并行编译内核镜像和模块 - 编译设备树:
make dtbs编译设备树二进制文件(.dtb) - 安装模块:
make modules_install将模块安装到指定目录 - 安装内核:
make install将内核镜像复制到 /boot
嵌入式交叉编译示例: 对于 ARM 目标板,使用交叉编译工具链:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfigmake 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 drivers/gpio/ 可以只编译 GPIO 子系统下的驱动,大幅缩短编译时间。调试阶段推荐将驱动编译为模块(.ko),通过 insmod / rmmod 快速验证。