2. Linux 内核源码获取与配置:从 kernel.org 获取内核源码、解压与配置、理解 Kconfig 与 Makefile
内核模块开发的第一步是获取并正确配置 Linux 内核源码树。源码树不仅是模块编译所需的头文件与构建系统的来源,更是理解内核架构与驱动接口的基础。本节将系统讲解从源码获取、解压、配置到理解 Kconfig / Makefile 机制的完整流程。
2.1 从 kernel.org 获取内核源码
Linux 内核官方发布站点为 kernel.org。开发者应优先选择 长期支持(LTS) 版本用于嵌入式产品开发,以获得长期的安全更新与稳定 API。
| 版本类型 | 特点 | 适用场景 |
|---|---|---|
| 主线(Mainline) | 最新特性,API 不稳定 | 开发测试、新硬件支持 |
| 稳定(Stable) | 基于主线,修复关键 Bug | 一般桌面/服务器 |
| 长期支持(LTS) | 2~6 年维护周期,API 稳定 | 嵌入式产品、工业控制 |
推荐做法:嵌入式开发优先选择 LTS 版本(如 6.1.x、5.15.x),并记录当前使用的精确版本号,便于后续环境复现与补丁管理。
获取源码的典型命令如下:
# 通过 wget 下载 LTS 内核源码包
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.76.tar.xz
# 校验 SHA256 签名(强烈建议)
sha256sum linux-6.1.76.tar.xz
2.2 解压与目录结构
解压源码包后,应了解内核源码树的顶层目录布局,这对后续配置与模块开发至关重要。
# 解压到 /home/developer/kernel/
tar -xf linux-6.1.76.tar.xz -C /home/developer/kernel/
cd /home/developer/kernel/linux-6.1.76
关键目录速查表:
| 目录 | 内容说明 | 模块开发关注度 |
|---|---|---|
| arch/ | 体系结构相关代码(arm64、x86、riscv 等) | 高(需匹配目标板架构) |
| drivers/ | 设备驱动源码(char、net、i2c、spi 等) | 极高(参考与复用) |
| include/ | 内核头文件(linux/、asm-generic/、uapi/) | 极高(模块编译依赖) |
| scripts/ | 构建脚本、Kconfig 解析工具、模块签名工具 | 中 |
| Documentation/ | 内核文档(含驱动开发指南) | 高(查阅 API 与规范) |
| Kbuild | 顶层构建描述文件 | 中 |
2.3 内核配置:Kconfig 与 Makefile 机制
内核配置系统由 Kconfig 和 Makefile 协同工作。Kconfig 定义配置选项的菜单结构、依赖与类型;Makefile 根据配置结果决定哪些文件参与编译。
2.4 配置内核的常用方法
嵌入式开发中,最常用的配置命令如下:
- make defconfig:使用当前架构的默认配置(适合快速起步)。
- make menuconfig:基于 ncurses 的文本菜单界面,最常用。
- make olddefconfig:基于现有 .config 更新,新选项使用默认值。
- make savedefconfig:将当前 .config 精简为最小化 defconfig。
常见陷阱:直接使用
make defconfig 后,务必检查 CONFIG_LOCALVERSION 和 CONFIG_CROSS_COMPILE 是否匹配目标板。交叉编译时,必须显式指定 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-。
2.5 理解 Kconfig 语法要点
Kconfig 文件位于每个子目录中,采用树形结构组织。以下是一个典型的驱动 Kconfig 条目:
# drivers/char/my_drv/Kconfig
config MY_DRV
tristate "My Custom Driver Support"
depends on ARM64 || COMPILE_TEST
select MISC_DEVICE
help
This is a sample driver for learning kernel module development.
Say Y to build it into the kernel, M to build as a module.
| 关键字 | 含义 | 示例值 |
|---|---|---|
| tristate | 三态:Y(编入内核)/ M(模块)/ N(不编译) | tristate "My Driver" |
| bool | 二态:Y / N | bool "Enable Feature" |
| depends on | 依赖其他配置选项 | depends on I2C |
| select | 反向选择(自动启用另一选项) | select MISC_DEVICE |
| help | 帮助文本,menuconfig 中显示 | help\n This driver ... |
2.6 理解 Makefile 条件编译
内核 Makefile 使用 obj-$(CONFIG_XXX) 模式实现条件编译。当 CONFIG_XXX 为 y 时,目标文件编入内核;为 m 时,编译为独立模块(.ko)。
# drivers/char/my_drv/Makefile
obj-$(CONFIG_MY_DRV) += my_drv.o
my_drv-objs := main.o ioctl.o probe.o
# 如果依赖多个源文件,使用 <module_name>-objs 或 <module_name>-y
模块开发关键点:外部模块(out-of-tree)的 Makefile 需要引用内核构建系统,典型写法如下:
# 外部模块 Makefile 示例
obj-m := hello.o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
2.7 配置验证与 .config 检查
配置完成后,应检查关键选项是否生效:
# 检查模块是否被配置为模块(m)
grep CONFIG_MY_DRV .config
# 检查架构与交叉编译链
grep -E "CONFIG_ARM64|CROSS_COMPILE" .config
# 生成精简 defconfig 用于版本管理
make savedefconfig
cp defconfig arch/arm64/configs/my_board_defconfig
最佳实践:将最终使用的 .config 文件纳入版本控制(Git),并配合
scripts/diffconfig 工具追踪配置变更。这能有效避免“编译通过但运行异常”的配置漂移问题。