U-Boot 和 Linux 内核的关系及设备树详解

🔥作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生,研究方向无线联邦学习

🎬擅长领域:驱动开发,嵌入式软件开发,BSP开发

❄️作者主页:一个平凡而乐于分享的小比特的个人主页

✨收录专栏:操作系统,本专栏为讲解各操作系统的历史脉络,以及各性能对比,以及内部工作机制,方便开发选择

欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖

U-Boot 和 Linux 内核的关系及设备树详解

一、U-Boot 和 Linux 内核的关系

系统启动流程全景图

复制代码
┌─────────────────────────────────────────────────────┐
│                 嵌入式系统启动流程                   │
├─────────────────────────────────────────────────────┤
│ 阶段 1:硬件复位 → BootROM(固化在芯片中)           │
│          ↓                                          │
│ 阶段 2:U-Boot(第一阶段:SPL)                      │
│          ↓                                          │
│ 阶段 3:U-Boot(第二阶段:主程序)                   │
│          ↓                                          │
│ 阶段 4:Linux 内核(内核初始化)                    │
│          ↓                                          │
│ 阶段 5:根文件系统 → 用户空间                       │
└─────────────────────────────────────────────────────┘

U-Boot 的作用(系统引导程序)

U-Boot(Universal Bootloader) 相当于嵌入式系统的"启动管家",主要负责:

主要功能:
  1. 硬件初始化 - CPU、内存、时钟、串口等
  2. 加载内核 - 从存储设备(eMMC、SD卡、Flash)读取内核镜像
  3. 传递参数 - 通过设备树和命令行参数告诉内核硬件信息
  4. 引导启动 - 跳转到内核入口点,移交控制权

类比理解:建筑工地开工

复制代码
┌─────────┐     ┌─────────┐     ┌─────────┐
│  毛坯房  │━━━━▶│ 施工队   │━━━━▶│ 精装房  │
│ (硬件)   │     │ (U-Boot) │     │ (Linux) │
└─────────┘     └─────────┘     └─────────┘
     │               │               │
 只有水泥墙      通水通电、         装修完成
  和地基        搬运建材           可入住

启动过程详细时序

c 复制代码
// 简化版的启动过程代码示意
void boot_process(void) {
    // 1. 硬件复位(不可控,芯片自动执行)
    
    // 2. U-Boot第一阶段(SPL)
    chip_hardware_init();      // 初始化最基础硬件
    load_uboot_image();        // 加载U-Boot主程序
    jump_to_uboot();           // 跳转到U-Boot
    
    // 3. U-Boot第二阶段
    init_all_hardware();       // 初始化所有外设
    load_device_tree();        // 加载设备树
    load_kernel_image();       // 加载Linux内核
    set_boot_args();           // 设置启动参数
    jump_to_kernel(0x80008000);// 跳转到内核
    
    // 4. Linux内核接管
    // U-Boot的使命结束,生命周期终止
}

二、设备树(Device Tree)全面解析

什么是设备树?

设备树是一种描述硬件配置的数据结构,相当于硬件的"身份证"和"说明书"。

设备树的演进历史

复制代码
2005年前:代码硬编码          → 2010年后:设备树标准
├─ ARM平台:板级文件           ├─ 一个.dts文件描述硬件
├─ 大量arch/arm/mach-*目录     ├─ 内核通用,无需修改
├─ 内核臃肿,移植困难          ├─ 内核精简,易于移植
└─ 每个板子需要内核修改        └─ 只需替换设备树文件

设备树文件类型

复制代码
├── 源文件(人类可读可编辑)
│   ├── dts(Device Tree Source)      - 具体板子的设备树
│   └── dtsi(Device Tree Source Include)- 公共部分,可被包含
│
├── 中间文件(编译过程生成)
│   └── dtb(Device Tree Blob)        - 二进制格式,由dts编译
│
└── 运行时(内存中)
    └── FDT(Flattened Device Tree)   - dtb加载到内存后的结构

三、U-Boot设备树 vs Linux内核设备树

详细对比表格

特性 U-Boot 设备树 Linux 内核 设备树 说明
主要目的 硬件初始化和配置 内核驱动识别硬件 U-Boot用来"点亮"硬件,内核用来"驱动"硬件
生命周期 启动阶段使用 整个系统运行期使用 U-Boot完成任务后销毁,内核持续使用
修改权限 可修改、可调整 只读参考 U-Boot可动态修改DTB再传给内核
包含内容 基础硬件描述+U-Boot专用节点 完整硬件描述+内核驱动绑定
典型差异 可能包含内存测试节点、引导参数 包含中断控制器、时钟、DMA等复杂外设
文件位置 U-Boot源码:arch/*/dts/*.dts Linux源码:arch/*/boot/dts/*.dts 通常同名但内容有差异

场景示例:SD卡控制器配置

dts 复制代码
// U-Boot 的 SD 卡设备树片段 (简化)
sdhci: sdhci@fe330000 {
    compatible = "snps,dwcmshc-sdhci";
    reg = <0x0 0xfe330000 0x0 0x10000>;
    clocks = <&cru SCLK_SDMMC>;
    clock-names = "core";
    u-boot,dm-spl;                    // ← U-Boot专用属性
    status = "okay";
};

// Linux 内核的 SD 卡设备树片段
sdhci: sdhci@fe330000 {
    compatible = "snps,dwcmshc-sdhci";
    reg = <0x0 0xfe330000 0x0 0x10000>;
    interrupts = <GIC_SPI 12 IRQ_TYPE_LEVEL_HIGH>; // ← 内核需要中断
    clocks = <&cru SCLK_SDMMC>, <&cru TMCLK_SDMMC>;
    clock-names = "core", "timeout";
    resets = <&cru SRST_SDMMC>;       // ← 内核需要复位控制
    reset-names = "reset";
    status = "okay";
};

四、设备树的传递流程

完整传递过程图示

复制代码
┌─────────────────────────────────────────────────────────────┐
│               设备树从源码到内核的完整流程                    │
├─────────────┬─────────────┬─────────────┬─────────────┤
│    阶段1    │    阶段2    │    阶段3    │    阶段4    │
│  源码准备   │  编译阶段   │   U-Boot    │  内核使用   │
├─────────────┼─────────────┼─────────────┼─────────────┤
│ 开发板.dts  │  dtc编译器  │  加载DTB    │  解析DTB    │
│   +         │     ↓       │    到内存   │     ↓       │
│  SoC.dtsi   │  board.dtb  │     ↓       │ 创建platform│
│   +         │  (二进制)   │ 可选:修改  │  设备       │
│ common.dtsi │             │  设备树     │     ↓       │
│             │             │     ↓       │ 匹配驱动    │
│             │             │ 传递给内核  │     ↓       │
│             │             │             │ 初始化硬件  │
└─────────────┴─────────────┴─────────────┴─────────────┘

实际启动示例:Raspberry Pi 4

bash 复制代码
# 编译过程
$ make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- dtbs
# 生成: bcm2711-rpi-4-b.dtb

# U-Boot 加载流程
U-Boot> load mmc 0:1 ${kernel_addr_r} Image
U-Boot> load mmc 0:1 ${fdt_addr_r} bcm2711-rpi-4-b.dtb
U-Boot> fdt addr ${fdt_addr_r}          # 设置设备树地址
U-Boot> fdt resize 8192                 # 调整大小(可选)
U-Boot> fdt set /chosen bootargs "console=ttyAMA0"  # 修改参数
U-Boot> booti ${kernel_addr_r} - ${fdt_addr_r}

# Linux内核启动日志片段(可以看到设备树解析)
[    0.000000] OF: fdt: Machine model: Raspberry Pi 4 Model B
[    0.000000] printk: console [ttyAMA0] enabled
[    0.123456] mmc0: SDHCI controller on fe340000.mmc [fe340000.mmc]

五、常见问题和调试技巧

1. 设备树不匹配的症状

复制代码
症状                         可能原因
──────────────────────────────────────────────────────
内核panic,找不到根文件系统      内存地址配置错误
某个外设不工作                  设备树节点缺失或配置错误
内核无法启动,卡在early boot    设备树格式错误或版本不兼容

2. 调试命令和工具

U-Boot 中的设备树操作:
bash 复制代码
# 查看设备树
U-Boot> fdt print /soc/mmc@fe330000

# 修改设备树(临时)
U-Boot> fdt set /soc/mmc@fe330000 status "disabled"

# 保存修改后的设备树
U-Boot> fdt save ${fdt_addr_r}

# 检查设备树完整性
U-Boot> fdt checks
Linux 内核中的设备树查看:
bash 复制代码
# 查看系统中的设备树
$ ls /proc/device-tree/

# 查看特定设备属性
$ cat /proc/device-tree/soc/mmc@fe330000/compatible

# 使用dtc工具反编译DTB
$ dtc -I dtb -O dts -o output.dts /boot/bcm2711-rpi-4-b.dtb

3. 设备树覆盖(Device Tree Overlay)

适用于动态修改硬件配置:

复制代码
原设备树DTB + 叠加overlay.dtbo = 新配置
     ↓              ↓
 基础硬件配置    特定扩展板配置

六、最佳实践和开发建议

设备树编写原则:

  1. 复用原则:相同SoC使用同一个.dtsi,具体板子.dts包含它
  2. 最小差异:板级设备树只描述与参考设计不同的部分
  3. 属性规范:严格按照bindings文档编写属性
  4. 版本控制:设备树与内核版本、U-Boot版本匹配

工作流程图解:

U-Boot测试 内核测试 硬件设计完成 编写设备树.dts 编译测试 U-Boot能初始化硬件 内核能识别所有外设 验证通过 产品发布 硬件变更 仅修改.dts文件 重新编译dtb 更新启动介质 无需重新编译内核

总结:核心要点回顾

  1. U-Boot是引导程序,内核是操作系统,两者接力完成启动
  2. 设备树是硬件描述文件,避免内核代码硬编码硬件信息
  3. U-Boot和内核各有设备树,前者用于初始化,后者用于驱动
  4. 设备树可以传递和修改,U-Boot可调整后再传给内核
  5. 设备树使内核通用化,同一内核支持不同硬件只需换设备树

一句话概括:

U-Boot用设备树初始化硬件,然后把"硬件说明书"(设备树)交给Linux内核,内核根据说明书加载驱动、管理硬件。

这种设计实现了硬件描述与内核代码的分离,大大提高了嵌入式系统的可移植性和可维护性,是现代嵌入式Linux系统的标准架构。

相关推荐
Sleepy MargulisItG6 小时前
【Linux网络编程】UDP Socket
linux·网络·udp
QT 小鲜肉7 小时前
【Linux命令大全】001.文件管理(理论篇)
linux·数据库·chrome·笔记
VekiSon7 小时前
Linux系统编程——进程进阶:exec 族、system 与工作路径操作
linux·运维·服务器
博语小屋7 小时前
Socket UDP 网络编程V2 版本- 简单聊天室
linux·网络·c++·网络协议·udp
一个平凡而乐于分享的小比特7 小时前
Linux 内核设计中的核心思想与架构原则
linux·架构·linux设计思想
BullSmall7 小时前
Shell脚本波浪号避坑指南
linux·bash
luoyayun3617 小时前
Linux下安装使用Claude遇到的问题及解决方案
linux·claude
[J] 一坚7 小时前
实用shell脚本学习分享一
linux·运维·编辑器
代码游侠7 小时前
学习笔记——进程控制函数
linux·运维·笔记·学习·算法