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

Dra*_*eep 2 linux ram kernel cpu-architecture linux-kernel

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

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

  2. 对于管理页表的内核来说,它需要了解哪些物理内存资源可用,包括物理内存基地址和可用物理内存,因此它不会分配超出 DRAM 范围的物理地址。

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

  1. 物理地址 DRAM 范围,或者至少是基地址,是在内核编译时加入的。这意味着即使使用相同的 ISA,不同的 CPU 也需要重新编译。这是由这个答案的启发在这里,其中,如果我理解正确的话,介绍对内核的基址相同的解决方案。由于基地址在编译时已知,内核代码引用文字地址而不是 DRAM/内核基地址的偏移量。

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

Had*_*ais 5

在启动时可用的整个物理内存 DIMM 可能不会并且通常不会映射到物理内存地址空间的单个连续范围,因此没有“基地址”。在硬重置时,在 CPU 固件完成执行后,将执行平台固件,通常是传统 BIOS 或 UEFI。给定的主板仅与有限的一组 CPU 集合兼容,这些 CPU 集合通常具有相同的方法来发现物理内存,包括 DIMM 和平台固件内存设备。平台固件的实现使用此方法构建内存描述条目表,其中每个条目描述物理内存地址范围。有关此处理器外观的更多信息,请参阅:BIOS 如何初始化 DRAM?. 该表存储在主内存 (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 平台上的其他(旧)方法在检测内存 (x86)。ACPI 规范都以 version 开头?和 UEFI 规范从版本开始?支持 DRAM DIMM 和 NVDIMM 内存范围类型。

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

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

假设Linux内核具有upstream-default引导参数,您可以使用该命令dmesg | grep 'BIOS-provided\|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]
Run Code Online (Sandbox Code Playgroud)

该表中描述了以“BIOS-e820”开头的内存范围。第一行清楚地告诉您此信息的来源。此信息的确切格式取决于 Linux 内核版本。在任何情况下,您都会在每个条目中看到一个范围和一个类型。以“e820”开头的行(没有“BIOS-”部分)是内核本身对表所做的更改。的实施E820h可能有问题,或者不同条目中获得的范围之间可能存在重叠。内核执行必要的检查并相应地进行更改。标记为“可用”的范围大多可供内核免费使用,但 ACPI 规范中讨论的例外情况以及内核知道的例外情况。绝大多数 PC BIOS 实现最多返回 128 个内存范围描述符。旧版本的 Linux 内核最多只能处理 128 个内存范围,因此任何E820h超过128 个内存范围返回的条目都将被忽略。从版本 ? 开始,此限制已放宽。有关详细信息,请参阅标题为“x86 引导:通过设置数据的链接列表传递超过 128 个的 E820 内存映射条目”的内核补丁系列。

类型usable和 的范围ACPI data。类型范围reserved由 DRAM DIMM 支持,或者由 CPU 或平台固件为 MMIO 砍掉。类型范围ACPI NVS由固件存储器支持。就固件而言,所有其他范围都不会由实际内存支持。请注意,固件可能会选择不映射所有已安装的 DRAM DIMM 或 NVDIMM。如果物理内存配置不受支持,或者固件由于 DIMM 中的问题而无法从已安装的 DIMM 获取信息,则可能会发生这种情况。

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

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

请注意,E820根据 ACPI 规范,内存映射中未描述范围 0x000a0000-0x000fffff 。这是 640KB-1MB 的上层存储区。内核会打印一条消息,指出它已从可用内存区域中删除此范围以保持与古代系统的兼容性。

此时,大多数 PCIe 设备用作 MMIO 的内存尚未分配。我的处理器支持 39 位物理地址空间,这意味着 0 到 2^39 之间的地址可用于映射。到目前为止,只有这个空间的最底部 16.5 GB 已映射到某些内容。请注意,此范围内仍有未映射的间隙。内核可以使用这些间隙(几百 MB)和其余的物理地址空间(大约 495.5 GB)来为 IO 设备分配地址范围。内核最终会发现 PCIe 设备,并且对于每个设备,它会尝试加载兼容的驱动程序(如果可用)。然后驱动程序确定设备需要多少内存以及设备对内存地址施加的任何限制,并请求内核为设备分配内存并将其配置为设备拥有的 MMIO 内存。您可以使用命令查看最终的内存映射sudo cat /proc/iomem.

在某些情况下,您希望手动更改现有内存范围的内存类型(例如,用于测试)、创建新范围(例如,用于模拟 DRAM 上的持久内存,或者如果固件无法发现所有无论出于何种原因可用内存),减少内核可用的内存量(例如,防止裸机管理程序使用超出限制的内存并使剩余的内存可供来宾使用),甚至完全覆盖从E820h. 在memmemmap内核参数可用于此类目的。当这些参数中的一个或多个被指定为有效值时,内核将首先读取 BIOS 提供的内存映射并进行相应的更改。内核将最终的内存映射打印为“用户定义的物理 RAM 映射”。在内核消息环形缓冲区中。您可以查看这些消息dmesg | grep user:(每个内存范围行以“user:”开头)。这些消息将在“BIOS-e820”消息之后打印。

在使用支持兼容性支持模块的 UEFI 固件启动的 x86 平台上(有关更多信息,请参阅 CSM 规范,该规范与 UEFI 分开),E820h支持旧实模式接口,Linux 内核默认仍使用它。如果内核在具有不支持 CSM 的 UEFI 的 x86 平台上运行,则E820h接口可能不提供所有或任何内存范围。add_efi_memmap在此类平台上可能需要使用内核参数。可以在UEFI Memory V E820 Memory 中找到一个示例。当从 提供一个或多个内存范围时GetMemoryMap(),内核会将这些范围与来自E820h接口的范围合并。可以使用以下命令查看生成的内存映射dmesg | grep 'efi:'另一个影响内存映射的 UEFI 相关内核参数是efi_fake_mem.

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