3. Linux内核源码结构:内核源码树概览、arch/arm与arch/riscv目录对比、驱动代码在源码中的位置
在进行驱动移植之前,深入理解Linux内核源码的组织结构是必修课。本章将带领你从宏观上俯瞰内核源码树,重点对比 arch/arm 与 arch/riscv 两个架构目录的异同,并精准定位驱动代码在源码中的栖息地。
3.1 内核源码树顶层概览
Linux内核源码采用分层、模块化的目录结构。顶层目录按照功能子系统、架构抽象、构建系统、文档等维度进行划分。下表列出了核心顶层目录及其职责:
| 目录名 | 职责描述 | 驱动开发者关注度 |
|---|---|---|
| arch/ | 存放各CPU架构(ARM、RISC-V、x86等)的平台相关代码 | ⭐⭐⭐⭐⭐ |
| drivers/ | 所有设备驱动代码的集中地,按设备类型划分子目录 | ⭐⭐⭐⭐⭐ |
| include/ | 内核头文件,包含架构无关(linux/)和架构相关(asm-*/)的API定义 | ⭐⭐⭐⭐ |
| kernel/ | 核心内核代码,如进程调度、中断管理、时间管理等 | ⭐⭐⭐ |
| mm/ | 内存管理子系统 | ⭐⭐ |
| fs/ | 文件系统实现,包括VFS抽象层和各种具体文件系统 | ⭐⭐⭐ |
| net/ | 网络协议栈 | ⭐⭐⭐ |
| Documentation/ | 内核文档,包括设备树绑定、驱动API说明等 | ⭐⭐⭐⭐ |
| scripts/ | 构建、配置、分析等辅助脚本 | ⭐⭐ |
| tools/ | 用户空间工具,如性能分析、测试工具 | ⭐ |
移植视角提示: 驱动移植的核心战场集中在
drivers/、arch/ 和 include/ 三个目录。理解它们之间的依赖关系,是避免“编译通过、运行崩溃”的关键。
下图展示了内核源码树的顶层模块依赖关系,帮助你建立全局视图:
3.2 arch/arm 与 arch/riscv 目录深度对比
arch/ 目录是内核移植的核心。ARM 和 RISC-V 作为两种截然不同的指令集架构,其 arch/ 子目录的组织方式既有共性,也有显著差异。下表从关键维度进行对比:
| 对比维度 | arch/arm | arch/riscv | 移植影响 |
|---|---|---|---|
| 平台/SoC 目录 | mach-* (如 mach-imx, mach-sunxi) | soc/ (如 soc/sifive, soc/starfive) | RISC-V 统一使用 soc/,更清晰 |
| 设备树文件 | boot/dts/ 下按厂商分目录 | boot/dts/ 下按厂商分目录 | 结构相似,但 RISC-V 的 dts 更简洁 |
| 启动入口 | kernel/head.S + kernel/head-common.S | kernel/head.S + kernel/head-dt.S | RISC-V 更依赖设备树传递参数 |
| 内存管理 | mm/ 包含大量 VMSA 相关代码 | mm/ 相对精简,依赖 Sv39/Sv48 页表 | RISC-V 页表结构更规整 |
| 中断控制器 | kernel/irq.c + 大量 GIC 代码 | kernel/irq.c + PLIC/CLINT 代码 | 中断模型差异大,需重写中断处理 |
| Kconfig 选项 | 大量平台/SoC 选择项,历史包袱重 | 选项相对精简,以 CPU 特性为主 | RISC-V 配置更清爽 |
| Makefile 组织 | 按 mach-* 目录分散,依赖复杂 | 按 soc/ 目录集中,依赖清晰 | RISC-V 更易维护 |
关键发现: RISC-V 的
arch/riscv 在设计上吸收了 ARM 多年的经验教训,目录结构更扁平、命名更规范、依赖更清晰。从 ARM 向 RISC-V 移植时,你通常会发现 RISC-V 的架构代码更容易理解和修改。
下图直观对比了 arch/arm 和 arch/riscv 的目录结构差异:
3.3 驱动代码在源码中的位置
驱动代码主要位于 drivers/ 目录下,按设备类型进行二级分类。理解这个分类体系,能让你在移植时快速找到参考实现。以下是驱动开发者最常打交道的子目录:
- drivers/tty/:串口(UART)驱动,移植调试的“生命线”。
- drivers/spi/:SPI 总线控制器驱动。
- drivers/i2c/:I2C 总线控制器驱动。
- drivers/mmc/:MMC/SD/SDIO 卡控制器驱动。
- drivers/net/:网络接口控制器(MAC/PHY)驱动。
- drivers/gpio/:GPIO 控制器驱动。
- drivers/clk/:时钟控制器驱动。
- drivers/pinctrl/:引脚复用和配置驱动。
- drivers/dma/:DMA 控制器驱动。
- drivers/usb/:USB 主机/设备/OTG 驱动。
- drivers/video/:显示控制器和帧缓冲驱动。
- drivers/input/:输入设备(触摸、键盘、鼠标)驱动。
移植陷阱: 不要直接复制 ARM 的驱动代码到 RISC-V 平台就期望它能工作。即使驱动逻辑本身是架构无关的,它依赖的
include/asm-* 头文件、内存屏障指令、中断处理方式、DMA 缓存一致性操作等,在 ARM 和 RISC-V 上都有本质区别。你必须检查并适配这些底层差异。
下图展示了驱动代码与内核其他子系统的调用关系,帮助你理解驱动在源码中的“生态位”:
3.4 实战:在源码树中定位一个具体驱动
假设我们要移植一个 I2C 控制器驱动,从 ARM 到 RISC-V。以下是定位和识别驱动代码的标准流程:
- 确定驱动类型: I2C 控制器 →
drivers/i2c/busses/ - 查找 ARM 参考实现: 例如
drivers/i2c/busses/i2c-imx.c(i.MX 平台) - 查找 RISC-V 目标平台: 例如
drivers/i2c/busses/i2c-dw.c(DesignWare IP) - 对比架构相关头文件:
- ARM:
include/asm-arm/io.h→ 包含readl/writel等 - RISC-V:
include/asm-riscv/io.h→ 包含readl/writel等(实现不同)
- ARM:
- 检查设备树绑定:
Documentation/devicetree/bindings/i2c/下的 YAML 文件 - 修改 Kconfig 和 Makefile: 确保驱动在 RISC-V 平台下可被选中和编译
移植口诀: 驱动逻辑在
drivers/,硬件差异在 arch/,接口定义在 include/,配置绑定在 Documentation/。改驱动不动 arch/,等于白忙活。
下面是一个典型的 I2C 驱动在源码树中的位置示例:
linux/
├── arch/
│ ├── arm/
│ │ ├── boot/dts/ # ARM 设备树
│ │ └── mach-imx/ # i.MX 平台代码
│ └── riscv/
│ ├── boot/dts/ # RISC-V 设备树
│ └── soc/sifive/ # SiFive 平台代码
├── drivers/
│ └── i2c/
│ ├── busses/
│ │ ├── i2c-imx.c # ARM i.MX I2C 驱动
│ │ ├── i2c-dw.c # DesignWare I2C 驱动 (通用)
│ │ └── i2c-riscv-foo.c # 新移植的 RISC-V I2C 驱动
│ ├── i2c-core.c # I2C 核心框架
│ └── Kconfig # I2C 驱动配置选项
├── include/
│ ├── linux/i2c.h # I2C 子系统 API
│ └── asm-generic/io.h # 通用 IO 操作
└── Documentation/
└── devicetree/bindings/i2c/ # 设备树绑定文档
通过本章的学习,你应该已经掌握了 Linux 内核源码的宏观结构,理解了 arch/arm 与 arch/riscv 的核心差异,并能够快速定位驱动代码在源码树中的位置。这是后续进行实际驱动移植工作的基础。