Linux 内核如何知道物理内存基址?

Linux 内核如何知道物理内存基址?,linux,linux-kernel,kernel,cpu-architecture,ram,Linux,Linux Kernel,Kernel,Cpu Architecture,Ram,我试图了解两个密切相关的问题 在启动后和启用MMU之前运行的内核代码在物理/标识映射虚拟内存中运行。该代码如何在不同物理地址范围内可能具有DRAM的CPU之间进行移植 为了让内核管理页表,它需要知道哪些物理内存资源可用,包括物理内存基址和可用物理内存,因此它不会分配超出DRAM范围的物理地址 我认为这在某种程度上取决于实现,但如果能参考不同的体系结构如何处理这个问题,我们将不胜感激。到目前为止,我有一些想法: 物理地址DRAM范围,或者至少是基址,在内核编译时烘焙。这意味着即使使用相同的IS

我试图了解两个密切相关的问题

  • 在启动后和启用MMU之前运行的内核代码在物理/标识映射虚拟内存中运行。该代码如何在不同物理地址范围内可能具有DRAM的CPU之间进行移植

  • 为了让内核管理页表,它需要知道哪些物理内存资源可用,包括物理内存基址和可用物理内存,因此它不会分配超出DRAM范围的物理地址

  • 我认为这在某种程度上取决于实现,但如果能参考不同的体系结构如何处理这个问题,我们将不胜感激。到目前为止,我有一些想法:

  • 物理地址DRAM范围,或者至少是基址,在内核编译时烘焙。这意味着即使使用相同的ISA,不同的CPU也需要重新编译。这是受到这个答案的启发,如果我理解正确的话,它描述了内核基址的相同解决方案。由于基址在编译时已知,因此内核代码引用的是文本地址,而不是DRAM/内核基址的偏移量

  • DRAM信息与物理内存映射的其余部分一起从设备树中读取和学习。这是我对Xilinx Zynq SoC的印象,基于类似的论坛帖子。虽然此解决方案提供了更大的灵活性,允许我们只重新编译引导加载程序,而不是将整个内核移植到CPU,但它确实让我想知道我的X86个人计算机如何在运行时检测我安装了多少DRAM。用于管理页表的代码仅引用DRAM基址的偏移量,并且可以移植,无需跨具有不同DRAM物理地址范围的CPU重新编译


  • 在引导时可用的整个物理内存DIMM可能不会也通常不会映射到物理内存地址空间的单个连续范围,因此没有“基址”。在硬复位时,CPU固件完成执行后,会执行平台固件,通常是旧版BIOS或UEFI。给定的主板仅与有限的CPU集合兼容,这些CPU集合通常具有相同的方法来发现物理内存,包括DIMM和平台固件内存设备。平台固件的实现使用此方法构建内存描述条目表,其中每个条目描述物理内存地址范围。有关此处理器外观的详细信息,请参阅:。此表存储在主内存(DIMM)中的一个地址,已知该地址是为此目的保留的,并且应该由实际内存支持(系统可以在没有任何DIMM的情况下启动)

    自90年代中期以来,x86 PC BIOS的大多数实现都提供实模式
    INT 15h E820h
    功能(15h是中断号,E820h是在
    AX
    寄存器中传递的参数)。这是一个特定于供应商的BIOS功能,最初在PhoenixBIOS v4.0(1992-1994,我无法确定确切年份)中引入,后来被其他BIOS供应商采用。1996年发布的ACPI 1.0规范以及PhoenixBIOS支持的ACPI的更高版本扩展了该接口。相应的UEFI接口是
    GetMemoryMap()
    ,这是一个UEFI启动时服务(意味着它只能在UEFI规范中定义的启动时调用)。内核可以使用其中一个接口来获取描述所有NUMA节点上内存的地址映射。x86平台上的其他(旧的)方法将在中讨论。ACPI规范都是从版本开始的吗?UEFI规范从版本开始?支持DRAM DIMM和NVDIMM内存范围类型

    例如,考虑与ACPI兼容的Linux内核如何确定在支持x86 ACPI的BIOS平台上可用(即由实际内存支持)和可用(即可用)的物理地址范围。BIOS固件将引导加载程序从指定的可引导存储设备加载到专用于此目的的内存位置。固件完成执行后,它跳转到引导加载程序,引导加载程序将在存储介质上找到内核映像,将其加载到内存,并将控制权转移到内核。引导加载程序本身需要知道当前内存映射,并为其操作分配一些内存。它试图通过调用
    E820h
    函数来获取内存映射,如果不受支持,它将求助于较旧的PC BIOS接口。定义引导加载程序可以使用哪些内存范围,以及内核必须保留哪些内存范围

    引导加载程序本身不修改内存映射或向内核提供映射。相反,当内核开始执行时,它调用
    E820h
    函数,并将一个20位指针(在
    ES:DI
    中)传递给一个缓冲区,根据引导协议,内核知道该缓冲区在x86平台上是空闲的。每个调用返回一个内存范围描述符,其大小至少为20字节。有关更多信息,请参阅最新版本的ACPI规范。大多数BIOS实现都支持ACPI

    假设Linux内核具有上游默认引导参数,您可以使用BIOS提供的命令“\code>dmesg | grep”e820”查看返回的内存范围描述符表。在我的系统上,它如下所示:

    [    0.000000] BIOS-provided physical RAM map:
    [    0.000000] BIOS-e820: [mem 0x0000000000000000-0x00000000000917ff] usable
    [    0.000000] BIOS-e820: [mem 0x0000000000091800-0x000000000009ffff] reserved
    [    0.000000] BIOS-e820: [mem 0x00000000000e0000-0x00000000000fffff] reserved
    [    0.000000] BIOS-e820: [mem 0x0000000000100000-0x00000000d2982fff] usable
    [    0.000000] BIOS-e820: [mem 0x00000000d2983000-0x00000000d2989fff] ACPI NVS
    [    0.000000] BIOS-e820: [mem 0x00000000d298a000-0x00000000d2db9fff] usable
    [    0.000000] BIOS-e820: [mem 0x00000000d2dba000-0x00000000d323cfff] reserved
    [    0.000000] BIOS-e820: [mem 0x00000000d323d000-0x00000000d7eeafff] usable
    [    0.000000] BIOS-e820: [mem 0x00000000d7eeb000-0x00000000d7ffffff] reserved
    [    0.000000] BIOS-e820: [mem 0x00000000d8000000-0x00000000d875ffff] usable
    [    0.000000] BIOS-e820: [mem 0x00000000d8760000-0x00000000d87fffff] reserved
    [    0.000000] BIOS-e820: [mem 0x00000000d8800000-0x00000000d8fadfff] usable
    [    0.000000] BIOS-e820: [mem 0x00000000d8fae000-0x00000000d8ffffff] ACPI data
    [    0.000000] BIOS-e820: [mem 0x00000000d9000000-0x00000000da718fff] usable
    [    0.000000] BIOS-e820: [mem 0x00000000da719000-0x00000000da7fffff] ACPI NVS
    [    0.000000] BIOS-e820: [mem 0x00000000da800000-0x00000000dbe11fff] usable
    [    0.000000] BIOS-e820: [mem 0x00000000dbe12000-0x00000000dbffffff] reserved
    [    0.000000] BIOS-e820: [mem 0x00000000dd000000-0x00000000df1fffff] reserved
    [    0.000000] BIOS-e820: [mem 0x00000000f8000000-0x00000000fbffffff] reserved
    [    0.000000] BIOS-e820: [mem 0x00000000fec00000-0x00000000fec00fff] reserved
    [    0.000000] BIOS-e820: [mem 0x00000000fed00000-0x00000000fed03fff] reserved
    [    0.000000] BIOS-e820: [mem 0x00000000fed1c000-0x00000000fed1ffff] reserved
    [    0.000000] BIOS-e820: [mem 0x00000000fee00000-0x00000000fee00fff] reserved
    [    0.000000] BIOS-e820: [mem 0x00000000ff000000-0x00000000ffffffff] reserved
    [    0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000041edfffff] usable
    [    0.002320] e820: update [mem 0x00000000-0x00000fff] usable ==> reserved
    [    0.002321] e820: remove [mem 0x000a0000-0x000fffff] usable
    [    0.002937] e820: update [mem 0xdd000000-0xffffffff] usable ==> reserved
    [    0.169287] e820: reserve RAM buffer [mem 0x00091800-0x0009ffff]
    [    0.169288] e820: reserve RAM buffer [mem 0xd2983000-0xd3ffffff]
    [    0.169289] e820: reserve RAM buffer [mem 0xd2dba000-0xd3ffffff]
    [    0.169289] e820: reserve RAM buffer [mem 0xd7eeb000-0xd7ffffff]
    [    0.169289] e820: reserve RAM buffer [mem 0xd8760000-0xdbffffff]
    [    0.169290] e820: reserve RAM buffer [mem 0xd8fae000-0xdbffffff]
    [    0.169291] e820: reserve RAM buffer [mem 0xda719000-0xdbffffff]
    [    0.169291] e820: reserve RAM buffer [mem 0xdbe12000-0xdbffffff]
    [    0.169292] e820: reserve RAM buffer [mem 0x41ee00000-0x41fffffff]
    
    该表描述了以“BIOS-e820”开头的内存范围。第一行清楚地告诉您这些信息的来源。此信息的确切格式取决于Linux内核版本。在任何情况下,您都会在每个条目中看到一个范围和一个类型。以“e820”(不带“BIOS-”部分)开头的行是内核本身对表所做的更改。
    E820h
    的实现可能有缺陷,或者可能存在重叠
    .text
    entry:
        call reloc  /* call is pc relative */
    reloc:
        pop %r0     /* r0 now contains physical address of reloc */
        sub $reloc, %r0, %r14  /* r14 contains difference between link address of reloc */
    /* At this point, r14 is a relocation register.  A virtual address + r14 == the corresponding physical address. */
        add $proot, %r14, %r0  /* physical address of page table root */
        add $entry, %r14, %r1  /* entry is where we were loaded into ram */
        test $0xfff, %r1   /* someone is being funny and not page aligning us */
        jnz bad_alignment
        or   $0x7, %r1     /* put mythical page protection bits in r1 */
        mov $1024, %r2     /* number of pages in r2 */
    loop:
        store %r1, (%r0)   /* store a page table entry */
        add $0x1000, %r1   /* setup next one 4096 bytes farther */
        add $4, %r0        /* point to next page table entry */
        sub $1, r2         /* are we done? */
        cmp %0, r2
        jne loop           /* nope, setup next entry */
        add $proot, %r14, %r0
        loadsysreg %r0, page_table_base_register
        mov $1, %r0
        mov $v_entry, %r1
        loadsysreg %r0, page_table_enabled
        jmp %r1
    v_entry:
            /* now we are virtually addressed */
        call main
    1:  jmp 1b   /* main shouldn't return. */
    
    
    .data
    .align 12   /* 4096 byte pages */
    proot:
    .zero 4096
    .text