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/ 三个目录。理解它们之间的依赖关系,是避免“编译通过、运行崩溃”的关键。

下图展示了内核源码树的顶层模块依赖关系,帮助你建立全局视图:

Linux内核源码树顶层模块依赖关系 Linux 内核源码树顶层模块依赖关系 arch/ (架构层) drivers/ (驱动层) include/ (接口层) kernel/ + mm/ + fs/ + net/ 架构层 (arch/) 是驱动移植的“地基” 驱动层 (drivers/) 通过 include/ 调用架构层提供的 API 移植时需同步修改 arch/ 和 include/ 中的架构相关代码

3.2 arch/arm 与 arch/riscv 目录深度对比

arch/ 目录是内核移植的核心。ARM 和 RISC-V 作为两种截然不同的指令集架构,其 arch/ 子目录的组织方式既有共性,也有显著差异。下表从关键维度进行对比:

对比维度arch/armarch/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.Skernel/head.S + kernel/head-dt.SRISC-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 的目录结构差异:

arch/arm 与 arch/riscv 目录结构对比 arch/arm 与 arch/riscv 目录结构对比 arch/arm/ ├── mach-imx/ (平台代码) ├── mach-sunxi/ (平台代码) ├── mach-omap2/ (平台代码) ├── boot/dts/ (设备树) ├── kernel/ (启动/中断) ├── mm/ (内存管理) ├── include/ (架构头文件) └── Kconfig (配置选项) arch/riscv/ ├── soc/ (平台代码) │ ├── sifive/ │ └── starfive/ ├── boot/dts/ (设备树) ├── kernel/ (启动/中断) ├── mm/ (内存管理) ├── include/ (架构头文件) └── Kconfig (配置选项) ARM 使用 mach-* 分散管理平台,RISC-V 使用 soc/ 统一管理

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 上都有本质区别。你必须检查并适配这些底层差异。

下图展示了驱动代码与内核其他子系统的调用关系,帮助你理解驱动在源码中的“生态位”:

驱动代码在内核源码中的位置与调用关系 驱动代码在内核源码中的位置与调用关系 drivers/ (设备驱动层) arch/ (硬件抽象层) include/ (API 定义层) kernel/ (核心服务层) fs/ + net/ + /dev (用户接口层) 驱动位于中间层:向下调用 arch/ 和 include/ 的硬件接口,向上提供 fs/net 的抽象

3.4 实战:在源码树中定位一个具体驱动

假设我们要移植一个 I2C 控制器驱动,从 ARM 到 RISC-V。以下是定位和识别驱动代码的标准流程:

  1. 确定驱动类型: I2C 控制器 → drivers/i2c/busses/
  2. 查找 ARM 参考实现: 例如 drivers/i2c/busses/i2c-imx.c (i.MX 平台)
  3. 查找 RISC-V 目标平台: 例如 drivers/i2c/busses/i2c-dw.c (DesignWare IP)
  4. 对比架构相关头文件:
    • ARM: include/asm-arm/io.h → 包含 readl/writel 等
    • RISC-V: include/asm-riscv/io.h → 包含 readl/writel 等(实现不同)
  5. 检查设备树绑定: Documentation/devicetree/bindings/i2c/ 下的 YAML 文件
  6. 修改 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 的核心差异,并能够快速定位驱动代码在源码树中的位置。这是后续进行实际驱动移植工作的基础。