🔥作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生,研究方向无线联邦学习
🎬擅长领域:驱动开发,嵌入式软件开发,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) 相当于嵌入式系统的"启动管家",主要负责:
主要功能:
- 硬件初始化 - CPU、内存、时钟、串口等
- 加载内核 - 从存储设备(eMMC、SD卡、Flash)读取内核镜像
- 传递参数 - 通过设备树和命令行参数告诉内核硬件信息
- 引导启动 - 跳转到内核入口点,移交控制权
类比理解:建筑工地开工
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 毛坯房 │━━━━▶│ 施工队 │━━━━▶│ 精装房 │
│ (硬件) │ │ (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 = 新配置
↓ ↓
基础硬件配置 特定扩展板配置
六、最佳实践和开发建议
设备树编写原则:
- 复用原则:相同SoC使用同一个.dtsi,具体板子.dts包含它
- 最小差异:板级设备树只描述与参考设计不同的部分
- 属性规范:严格按照bindings文档编写属性
- 版本控制:设备树与内核版本、U-Boot版本匹配
工作流程图解:
U-Boot测试 内核测试 硬件设计完成 编写设备树.dts 编译测试 U-Boot能初始化硬件 内核能识别所有外设 验证通过 产品发布 硬件变更 仅修改.dts文件 重新编译dtb 更新启动介质 无需重新编译内核
总结:核心要点回顾
- U-Boot是引导程序,内核是操作系统,两者接力完成启动
- 设备树是硬件描述文件,避免内核代码硬编码硬件信息
- U-Boot和内核各有设备树,前者用于初始化,后者用于驱动
- 设备树可以传递和修改,U-Boot可调整后再传给内核
- 设备树使内核通用化,同一内核支持不同硬件只需换设备树
一句话概括:
U-Boot用设备树初始化硬件,然后把"硬件说明书"(设备树)交给Linux内核,内核根据说明书加载驱动、管理硬件。
这种设计实现了硬件描述与内核代码的分离,大大提高了嵌入式系统的可移植性和可维护性,是现代嵌入式Linux系统的标准架构。