如何在 RISC-V 平台上通过解析设备树 (Device Tree) 获取内存大小

本内容分为两部分:

  1. xv6 是如何处理内存大小的?
  2. 真正的 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 获取所有内存区域的 (起始地址, 大小),累计计算总内存