U-Boot 内存、地址与数据操作
在 U-Boot 中启动 Linux,本质上是把几个文件放到内存中的合适位置,然后把控制权交给内核。这里的“合适位置”非常重要:地址放错了,轻则启动失败,重则覆盖 U-Boot 自己、覆盖设备树,或者让内核解压时踩到其他数据。
学习目标
读完本章后,你应该能够:
- 看懂 U-Boot 中常见的地址变量。
- 知道为什么 QEMU ARM64 的内存通常从
0x40000000附近开始。 - 使用
bdinfo、md、mw、cp、cmp做基本内存观察。 - 避免把内核、设备树和 initramfs 加载到互相覆盖的位置。
1. U-Boot 看到的是物理地址
Linux 启动后会启用 MMU,进程看到的是虚拟地址;但 U-Boot 运行时通常直接操作物理地址。你在命令行输入的地址,例如 0x40200000,表示内存总线上的真实地址范围。
这和你在 Linux 用户空间里写 C 程序很不一样。Linux 进程不能随便访问物理地址,访问非法地址通常会得到 segmentation fault;而 U-Boot 处在启动早期,它没有完整操作系统保护,命令行中的内存命令会直接读写目标地址。所以 U-Boot 的内存操作更接 近“带电调试”,你要知道自己正在碰哪里。
先查看当前板级内存信息:
# [U-Boot]
bdinfo
你需要重点看这些字段:
dram_start:DRAM 起始地址。dram_size:DRAM 大小。relocaddr:U-Boot 重定位后的运行位置。fdt_blob:U-Boot 当前使用的控制设备树地址。
不同版本和配置的 bdinfo 输出格式可能略有差异,但你可以按关键词查找。QEMU ARM64 上你通常会看到 DRAM 起点接近 0x40000000,这说明后续示例中的 0x40200000 落在 DRAM 内。
在 QEMU ARM64 virt 机器中,DRAM 通常从 0x40000000 开始。教程示例会使用下面这组地址:
# [U-Boot]
setenv kernel_addr_r 0x40200000
setenv fdt_addr_r 0x4fa00000
setenv ramdisk_addr_r 0x4fb00000
真实开发板应以板级配置、内存大小和厂商文档为准。
刚开始学习时,不要急着背地址。你只需要先建立一个判断:这个地址是否位于 DRAM 内,是否避开了 U-Boot 自己、DTB、initramfs 和其他已经加载的数据。
2. 常见地址变量
| 变量 | 常见用途 |
|---|---|
kernel_addr_r | Linux kernel 镜像加载地址 |
fdt_addr_r | 设备树 DTB 加载地址 |
ramdisk_addr_r | initramfs/initrd 加载地址 |
scriptaddr | 启动脚本加载地址 |
pxefile_addr_r | PXE/extlinux 配置文件加载地址 |
查看当前值:
# [U-Boot]
printenv kernel_addr_r fdt_addr_r ramdisk_addr_r scriptaddr pxefile_addr_r
如果某个变量不存在,可以临时设置:
# [U-Boot]
setenv kernel_addr_r 0x40200000
你也可以用 echo 观察变量展开:
# [U-Boot]
echo ${kernel_addr_r}
如果变量没有定义,很多命令并不会帮你猜一个地址,而是直接失败,或者把空字符串当作参数导致语法错误。因此,每次写启动脚本前,先确认关键地址变量是否存在,是一个很好的习惯。
setenv 默认只修改当前运行时环境。除非你执行 saveenv,否则复位后通常会恢复默认值。本教程在 QEMU 中优先使用临时变量,避免把错误配置永久写入存储介质。