Linux内核深度解析
上QQ阅读APP看书,第一时间看更新

3.6.3 物理内存信息

在内核初始化的过程中,引导内存分配器负责分配内存,问题是:引导内存分配器怎么知道内存的大小和物理地址范围?

ARM64架构使用扁平设备树(Flattened Device Tree, FDT)描述板卡的硬件信息,好处是可以把板卡特定的代码从内核中删除,编译生成通用的板卡无关的内核。驱动开发者编写设备树源文件(Device Tree Source, DTS),存放在目录“arch/arm64/boot/dts”下,然后使用设备树编译器(Device Tree Compiler, DTC)把设备树源文件转换成设备树二进制文件(Device Tree Blob, DTB),接着把设备树二进制文件写到存储设备上。设备启动时,引导程序把设备树二进制文件从存储设备读到内存中,引导内核的时候把设备树二进制文件的起始地址传给内核,内核解析设备树二进制文件后得到硬件信息。

设备树源文件是文本文件,扩展名是“.dts”,描述物理内存布局的方法如下:

    / {
        #address-cells = <2>;
        #size-cells = <2>;
        memory@80000000 {
            device_type = "memory";
            reg = <0x00000000 0x80000000 0 0x80000000>,
                  <0x00000008 0x80000000 0 0x80000000>;
        };
    };

“/”是根节点。

属性“#address-cells”定义一个地址的单元数量,属性“#size-cells”定义一个长度的单元数量。单元(cell)是一个32位数值,属性“#address-cells = <2>”表示一个地址由两个单元组成,即地址是一个64位数值;属性“#size-cells = <2>”表示一个长度由两个单元组成,即长度是一个64位数值。

“memory”节点描述物理内存布局,“@”后面的设备地址用来区分名字相同的节点,如果节点有属性“reg”,那么设备地址必须是属性“reg”的第一个地址。如果有多块内存,可以使用多个“memory”节点来描述,也可以使用一个“memory”节点的属性“reg”的地址/长度列表来描述。

属性“device_type”定义设备类型,“memory”节点的属性“device_type”的值必须是“memory”。

属性“reg”定义物理内存范围,值是一个地址/长度列表,每个地址包含的单元数量是由根节点的属性“#address-cells”定义的,每个长度包含的单元数量是由根节点的属性“#size-cells”定义的。在上面的例子中,第一个物理内存范围的起始地址是“0x00000000 0x80000000”,长度是“0 0x80000000”,即起始地址是2GB,长度是2GB;第二个物理内存范围的起始地址是“0x00000008 0x80000000”,长度是“0 0x80000000”,即起始地址是34GB,长度是2GB。

内核在初始化的时候调用函数early_init_dt_scan_nodes以解析设备树二进制文件,从而得到物理内存信息。

    start_kernel() ->setup_arch() ->setup_machine_fdt() ->early_init_dt_scan() ->early_
    init_dt_scan_nodes()
   drivers/of/fdt.c
    1   void __init early_init_dt_scan_nodes(void)
    2   {
    3    …
    4    /* 初始化size-cellsaddress-cells信息 */
    5    of_scan_flat_dt(early_init_dt_scan_root, NULL);
    6
    7    /* 调用函数early_init_dt_add_memory_arch设置内存 */
    8    of_scan_flat_dt(early_init_dt_scan_memory, NULL);
    9   }

第5行代码,调用函数early_init_dt_scan_root,解析根节点的属性“#address-cells”得到地址的单元数量,保存在全局变量dt_root_addr_cells中;解析根节点的属性“#size-cells”得到长度的单元数量,保存在全局变量dt_root_size_cells中。

第8行代码,调用函数early_init_dt_scan_memory,解析“memory”节点得到物理内存布局。

函数early_init_dt_scan_memory负责解析“memory”节点,其主要代码如下:

    drivers/of/fdt.c
    1   int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
    2                           int depth, void *data)
    3   {
    4    const char *type = of_get_flat_dt_prop(node, "device_type", NULL);
    5    const __be32 *reg, *endp;
    6    int l;
    7    …
    8
    9    /* 只扫描 "memory" 节点 */
    10   if (type == NULL) {
    11        /* 如果没有属性“device_type”,判断节点名称是不是“memory@0*/
    12        if (! IS_ENABLED(CONFIG_PPC32) || depth ! = 1 || strcmp(uname, "memory@0") ! = 0)
    13              return 0;
    14   } else if (strcmp(type, "memory") ! = 0)
    15        return 0;
    16
    17   reg = of_get_flat_dt_prop(node, "linux, usable-memory", &l);
    18   if (reg == NULL)
    19        reg = of_get_flat_dt_prop(node, "reg", &l);
    20   if (reg == NULL)
    21        return 0;
    22
    23   endp = reg + (l / sizeof(__be32));
    24   …
    25
    26   while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
    27        u64 base, size;
    28
    29        base = dt_mem_next_cell(dt_root_addr_cells, &reg);
    30        size = dt_mem_next_cell(dt_root_size_cells, &reg);
    31
    32        if (size == 0)
    33              continue;
    34        …
    35        early_init_dt_add_memory_arch(base, size);
    36        …
    37    }
    38
    39    return 0;
    40   }

第4行代码,解析节点的属性“device_type”。

第14行代码,如果属性“device_type”的值是“memory”,说明这个节点描述物理内存信息。

第17~19行代码,解析属性“linux, usable-memory”,如果不存在,那么解析属性“reg”。这两个属性都用来定义物理内存范围。

第26~37行代码,解析出每块内存的起始地址和大小后,调用函数early_init_dt_add_memory_arch。

函数early_init_dt_add_memory_arch的主要代码如下:

    drivers/of/fdt.c
    void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size)
    {
        const u64 phys_offset = MIN_MEMBLOCK_ADDR;
       if (! PAGE_ALIGNED(base)) {
              if (size < PAGE_SIZE - (base & ~PAGE_MASK)) {
                  pr_warn("Ignoring memory block 0x%llx - 0x%llx\n",
                      base, base + size);
                  return;
              }
              size -= PAGE_SIZE - (base & ~PAGE_MASK);
              base = PAGE_ALIGN(base);
        }
        size &= PAGE_MASK;
       if (base > MAX_MEMBLOCK_ADDR) {
              pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
                      base, base + size);
              return;
        }
       if (base + size - 1 > MAX_MEMBLOCK_ADDR) {
              pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
                      ((u64)MAX_MEMBLOCK_ADDR) + 1, base + size);
              size = MAX_MEMBLOCK_ADDR - base + 1;
        }
       if (base + size < phys_offset) {
              pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
                      base, base + size);
              return;
        }
        if (base < phys_offset) {
              pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
                      base, phys_offset);
              size -= phys_offset - base;
              base = phys_offset;
        }
        memblock_add(base, size);
    }

函数early_init_dt_add_memory_arch对起始地址和长度做了检查以后,调用函数memblock_add把物理内存范围添加到memblock.memory中。