如何在 RISC-V 平台上通过解析设备树 (Device Tree) 获取内存大小
本内容分为两部分:
- xv6 是如何处理内存大小的?
- 真正的 RISC-V 系统中是如何动态发现内存的?
1. xv6 的方式:硬编码内存大小
xv6 是一个教学用操作系统,因此简化了许多机制。
在 kernel/memlayout.h 中,它硬编码了内存上限:
#define KERNBASE 0x80000000L
#define PHYSTOP (KERNBASE + 128*1024*1024) // 128MB
这意味着无论实际物理内存是多少,xv6 总认为只有 128MB。
优点: 简单、可控 缺点: 不具备移植性和动态适应能力,浪费真实硬件资源
2. 真实系统中的方式:解析设备树 (Device Tree)
2.1 什么是设备树?
设备树是一种用于描述硬件结构的数据结构(通常是 .dts → 编译成 .dtb):
.dts是源文件(人类可读).dtb是设备树 blob(二进制,供内核解析)
启动时,Bootloader(如 U-Boot)或固件(如 OpenSBI)会把 .dtb 加载进内存,并将地址传入 a1 寄存器,内核读取后进行解析。
2.2 示例设备树片段(.dts)
/ {
#address-cells = <2>;
#size-cells = <2>;
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x08000000>; // 128MB at 0x80000000
};
};
device_type = "memory":内核通过这个属性找到内存节点reg:内存起始地址和大小,格式根据#address-cells和#size-cells解释
2.3 内核中的解析步骤
以 C 语言伪代码展示解析逻辑:
#include <libfdt.h>
void parse_dtb_for_memory(void *dtb_base_ptr) {
if (fdt_check_header(dtb_base_ptr) != 0)
panic("Invalid DTB");
int mem_node = fdt_path_offset(dtb_base_ptr, "/memory");
if (mem_node < 0)
mem_node = fdt_node_offset_by_prop_value(dtb_base_ptr, -1, "device_type", "memory", sizeof("memory"));
if (mem_node < 0)
panic("Cannot find memory node");
int len;
const fdt32_t *reg = fdt_getprop(dtb_base_ptr, mem_node, "reg", &len);
if (!reg)
panic("No reg property in memory node");
int addr_cells = fdt32_to_cpu(*fdt_getprop(dtb_base_ptr, 0, "#address-cells", NULL));
int size_cells = fdt32_to_cpu(*fdt_getprop(dtb_base_ptr, 0, "#size-cells", NULL));
int entry_cells = addr_cells + size_cells;
int entries = len / (entry_cells * sizeof(fdt32_t));
for (int i = 0; i < entries; i++) {
const fdt32_t *entry = reg + i * entry_cells;
uint64_t base = fdt_read_u64_cells(entry, addr_cells);
uint64_t size = fdt_read_u64_cells(entry + addr_cells, size_cells);
printf("Memory region: base=0x%lx, size=0x%lx (%lu MB)\n", base, size, size >> 20);
}
}
2.4 总结:如何实现?
| 步骤 | 描述 |
|---|---|
| 1 | 在内核早期,从 a1 寄存器获取 DTB 的地址 |
| 2 | 使用 libfdt 或其他设备树解析库 |
| 3 | 查找 device_type = "memory" 的节点 |
| 4 | 根据根节点的 #address-cells 和 #size-cells 解析 reg |
| 5 | 获取所有内存区域的 (起始地址, 大小),累计计算总内存 |