繁体   English   中英

kernel 如何知道物理 memory 基地址?

[英]How does kernel know physical memory base address?

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

  1. Kernel 代码在引导加载程序后和启用 MMU 之前在物理/身份映射虚拟 memory 中运行。 这段代码如何在不同的 CPU 之间移植,可能在不同的物理地址范围内有 DRAM?

  2. For the kernel to manage the page table, it needs some awareness of what physical memory resources are available, including the physical memory base address and available physical memory, so it doesn't assign physical addresses that are out of DRAM range.

我想这在某种程度上依赖于实现,但对不同架构如何处理这个问题的参考将不胜感激。 到目前为止我的一些想法:

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

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

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

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

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

引导加载程序本身不会修改 memory map 或将 map 提供给 Z50484C19F21AFDAF3842ZA0D8。 Instead, when the kernel starts executing, it calls the E820h function and passes to it a 20-bit pointer (in ES:DI ) to a buffer that the kernel knows to be free on x86 platforms according to the boot protocol. 每次调用都会返回一个 memory 范围描述符,其大小至少为 20 字节。 有关详细信息,请参阅 ACPI 规范的最新版本。 大多数 BIOS 实现都支持 ACPI。

假设 Linux kernel 具有上游默认引导参数,您可以使用命令dmesg | grep 'BIOS-provided\|e820' dmesg | grep 'BIOS-provided\|e820'查看返回的 memory 范围描述符表。 在我的系统上,它看起来像这样:

[    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”开头的 memory 范围。 第一行清楚地告诉你这些信息的来源。 此信息的确切格式取决于 Linux kernel 版本。 在任何情况下,您都会在每个条目中看到一个范围和一个类型。 以“e820”开头的行(没有“BIOS-”部分)是 kernel 本身对表所做的更改。 E820h的实现可能有问题,或者在不同条目中获得的范围之间可能存在重叠。 kernel 执行必要的检查并进行相应的更改。 kernel 可以免费使用标记为“可用”的范围,但 ACPI 规范中讨论的例外情况以及 kernel 知道的例外情况除外。 绝大多数 PC BIOS 实现最多返回 128 个 memory 范围描述符。 旧版本的 Linux kernel 最多只能处理 128 个 memory 范围,因此从第 128 个E820h返回的任何条目都将被忽略。 从 version? 开始,这个限制被放宽了。 有关更多信息,请参阅 kernel 系列补丁,标题为“x86 boot: pass E820 memory map entries more than 128 via setup data.”链接列表。

usable类型和ACPI data的范围。 reserved的类型范围由 DRAM DIMM 支持,或者由 CPU 或平台固件为 MMIO 截断。 ACPI NVS类型的范围由固件 memory 支持。 据固件所知,实际 memory 并未支持所有其他范围。 请注意,固件可能会选择不对所有已安装的 DRAM DIMM 或 NVDIMM 进行 map。 如果不支持物理 memory 配置,或者如果固件由于 DIMM 中的问题而无法从已安装的 DIMM 获取信息,则可能会发生这种情况。

您可以计算出安装的 DRAM DIMM 和 NVDIMM 中有多少 memory 可通过固件提供给 kernel。 在我的系统上,我安装了 16 GB 的 DRAM DIMM。 因此,除非某些 DIMM 安装不正确、运行不正常、固件中存在错误,或者平台或处理器不支持,否则 kernel 的可用空间应该少于 16 GB。

所有usable范围加起来为 0x3FA42B800 字节。 请注意,范围的最后一个地址是包含在内的,这意味着它指向一个字节位置,该位置是该范围的一部分。 物理安装的 DIMM 总量为 16 GB 或 0x400000000 字节。 因此,对于 kernel 不可用的已安装 memory 的总量为 0x400000000 - 0x3FA42B800 或总共 16 GB 的大约 92 MB。 这个 memory 被一些reserved范围和所有ACPI data范围占用。 如果 DRAM DIMM 或 NVDIMM 中的某些位置被平台固件确定为不可靠,它们也将被截断为reserved

请注意,根据 ACPI 规范, E820 memory map 中没有描述范围 0x000a0000-0x000fffff。 这是 640KB-1MB 上部 memory 区域。 kernel 打印一条消息,表示它已从可用的 memory 区域中删除了此范围,以保持与古代系统的兼容性。

此时,尚未分配用于大多数 PCIe 设备的 MMIO 的 memory。 我的处理器支持 39 位物理地址空间,这意味着 0 到 2^39 之间的地址可用于映射。 到目前为止,只有这个空间的最底部 16.5 GB 被映射到某个东西。 请注意,此范围内仍有未映射的差距。 kernel可以利用这些间隙(几百MB)和物理地址空间的rest(约495.5GB)为ZCF3882F1C43AB22BFF2DB0BD9D8设备分配地址范围。 kernel 最终会发现 PCIe 设备,并且对于每个设备,如果可用,它将尝试加载兼容的驱动程序。 The driver then determines how much memory the device needs and any restrictions on the memory addresses imposed by the device and request from the kernel to allocate memory for the device and configure it as an MMIO memory owned by the device. 您可以使用命令sudo cat /proc/iomem查看最终的 memory map。

在某些情况下,您需要手动更改现有 memory 范围的 memory 类型(例如,用于测试),创建一个新范围(例如, 用于模拟持久性 ZCD69B4957F06CD818D7BF3D61980E2或如果固件无法发现所有available memory for whatever reason), reduce the amount of memory usable by the kernel (eg, to prevent a bare-metal hypervisor for using memory beyond a limit and make the rest available for guests), or even completely override the entire table returned from E820h memmemmap kernel 参数可用于此类目的。 当这些参数中的一个或多个指定为有效值时,kernel 将首先读取 BIOS 提供的 memory map 并进行相应更改。 kernel 会将最终的 memory map 打印为“用户定义的物理 RAM Z1D78DC8ED51214E5018B5114”。 在 kernel 消息环形缓冲区中。 您可以使用dmesg | grep user:查看这些消息。 dmesg | grep user:每个 memory 范围行以“用户:”开头)。 这些消息将在“BIOS-e820”消息之后打印。

On an x86 platform booted with UEFI firmware that supports the Compatibility Support Module (refer to the CSM specification for more information, which is separate from UEFI), the legacy real-mode E820h interface is supported and the Linux kernel by default still uses it. 如果内核运行在具有不支持 CSM 的 UEFI 的 x86 平台上,则E820h接口可能无法提供所有或任何 memory 范围。 在此类平台上可能需要使用add_efi_memmap kernel 参数。 可以在UEFI Memory V E820 Memory中找到一个示例。 当从GetMemoryMap()提供一个或多个 memory 范围时,kernel 会将这些范围与来自E820h接口的范围合并。 生成的 memory map 可以使用dmesg | grep 'efi:'查看 dmesg | grep 'efi:' Another UEFI-related kernel parameter that affects the memory map is efi_fake_mem .

当 IO 或 DIMM 设备在任何 S 状态下从系统中插入或移除时,ACPI 规范(第 6.3 节)提供通知机制以通知 kernel。 (不过,我不知道是否有任何主板支持在任何 S 状态下移除 DIMM。这通常仅在 G3 state 和可能 S4 和/或 S5 中才有可能)发生此类事件时,kernel 或固件相应地对 memory map 进行更改。 这些更改反映在sudo cat /proc/iomem

pc 相对寻址是指一种编程技术,您的程序可以在任何地址上运行。 由于重定位寄存器(例如段)已经过时,大多数 pc 相关编程都是显式执行的。 这是通用机器代码中的示例:

.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

这个神话机器非常简单,只有一个平面页表,kernel 链接在地址 0,但可以从前 4M(1024 * 4096)中的任何位置运行。 真正的机器只是这个的更详细的版本。 通常,在您完成初始地址空间设置之前,您甚至无法信任C之类的系统语言。 一旦它是,其中的代码可以构建更复杂的页表,并查询设备树等数据库,甚至像 apic/uefi 这样的怪物,以获取有关 ram 布局等的更多信息。

在内部节点采用与叶节点兼容的格式(例如 x86-classic)的前向映射页表体系结构中,您可以递归地使用单个页表以允许更灵活的链接地址。 例如,如果您将 proot 中的最后一个条目(即 proot[1023])指向 proot,那么您可以将您的操作系统链接到 0xffffc000,并且此代码将正常工作(一旦转换为 x86)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM