Linux内核中“HighMem”的不同含义混淆

问题描述

我试图理解“highmem”是什么意思,但我已经看到它以两种不同的方式使用,我想知道其中一种或两种方式是否正确。

我收集到的两个定义是:

  1. Highmem 是指 32 位系统中的一种特定情况,系统可以容纳超过 4GB 的 RAM,但 32 位只允许内核直接寻址 4GB 内存,因此任何超过 4GB 的内存都需要使用物理地址扩展 (PAE) 并被称为“highmem”。当我看到这个版本的高内存讨论时,通常会提到64位系统不再有这个问题;它们可以完全寻址其物理内存,因此不需要“highmem”的概念(参见123)。我自己的 64 位系统在 /proc/zoneinfofree -ml 中没有显示任何 highmem 内存。

  2. Highmem 用于描述用户间的虚拟内存地址空间。这与 lowmem 形成对比,lowmem 是用于内核并映射到每个用户空间程序的地址空间的地址空间。我看到的另一种表达方式是使用名称 zone_highmem (highmem) 和 zone_normal (lowmem)。例如,在 32 位系统“3/1”用户/内核内存拆分中,用于用户间的 3GB 将被视为高内存(参见 456,7).

一个定义是否比另一个更正确?

两者都正确但在不同情况下有用吗(​​即定义 1 指的是物理内存,定义 2 指的是虚拟内存)?

解决方法

我认为您对用法 2 的示例实际上(有时会被破坏)是对用法 1 或其后果的描述。没有单独的含义,这只是由于没有足够的内核虚拟地址空间来始终保持所有物理内存映射的结果。

(因此,对于 3:1 的 user:kernel 拆分,您只有 1GiB 的 lowmem,其余的是 highmem,即使您不需要启用 PAE 分页即可查看所有内容。)

这篇文章 https://cl4ssic4l.wordpress.com/2011/05/24/linus-torvalds-about-pae 引用了 Linux Torvalds 的一篇关于虚拟地址空间少于物理地址空间(这就是 PAE 所做的)是多么糟糕的咆哮,而 highmem 是 Linux 试图从无法保持映射的内存。

PAE 是一个 32 位 x86 扩展,它将 CPU 切换为使用具有更宽 PTE 的替代页表格式(same one adopted by AMD64,包括一个 exec 权限位,以及最多可容纳 52 位物理地址的空间,虽然最初支持它的 CPU 只支持 36 位物理地址)。如果您完全使用 PAE,则将其用于所有页表。


使用 a high-half-kernel memory layout 的普通内核为自己保留虚拟地址空间的上半部分,即使在用户空间运行时也是如此。或者为了给用户空间留出更多空间,32 位 Linux 迁移到 3G:1G 用户:内核拆分。

例如现代 x86-64 Linux 的 virtual memory map (Documentation/x86/x86_64/mm.txt),请注意它包括所有物理内存的 64TB 直接映射(使用 1G 大页),因此给定一个物理地址,内核可以通过添加物理地址到该虚拟地址的开头。 (kmalloc 在这个区域保留了一个地址范围,实际上根本不需要修改页表,只是簿记)。

内核需要相同页面的其他映射,用于虚拟连续但不需要物理连续的 vmalloc 内核内存分配。当然,对于内核的静态代码/数据,这相对较小。

这是没有任何 highmem 的正常/良好情况,这也适用于显着小于物理内存的系统上的 32 位 Linux。这就是为什么莱纳斯说:

虚拟空间需要大于物理空间。没有“那么大”。不是“更小”。它需要更大,至少是两倍,坦率地说,这是在推动它,而且你最好拥有十倍或更多的倍数。

这就是为什么 Linus 后来说“即使在 PAE 之前,实际限制也是大约 1GB...” 以 3:1 的比例分割为用户空间留下 3GB 的虚拟地址空间,即只为内核留下 1GiB 的虚拟地址空间,足以映射大部分物理内存。或者使用 2:2 分割,映射所有内容并为 vmalloc 内容留出空间。

希望这个答案比 Linus 的有趣回答更能说明这个问题任何不明白这一点的人都是白痴。讨论结束。(从上下文来看,他实际上是在侮辱制作 PAE 的 CPU 架构师,而不是学习操作系统的人,别担心:P)


那么内核可以用 highmem 做什么呢?它可以使用它来保存用户空间虚拟页面,因为每个进程的用户空间页表可以毫无问题地引用该内存。

许多时候内核必须访问该内存是当任务是当前任务时,使用用户指针。例如读/写系统调用使用用户空间地址调用 copy_to/from_user(从页面缓存复制文件读/写),通过用户页表条目到达 highmem。

除非页面缓存中的数据不是热,那么 read 将在来自磁盘(或 NFS 或其他网络)的 DMA 排队时阻塞。但这只会将文件数据带入页面缓存中,我猜复制到用户拥有的页面会在上下文切换回带有暂停的 read 调用的任务之后发生。

但是如果内核想从一个没有运行的进程中换出一些页面怎么办? DMA 处理物理地址,因此它可能可以计算出正确的物理地址,只要它不需要实际加载任何用户空间数据。

(但通常没有那么简单,IIRC:32 位系统中的 DMA 设备可能不支持高物理地址。所以内核可能实际上需要在 lowmem 中的反弹缓冲区......我同意 Linus:highmem 被吸,并使用64 位内核显然要好得多,即使您想运行纯 32 位用户空间。)

任何像 zswap 动态压缩页面的东西,或者任何确实需要使用 CPU 复制数据的驱动程序,都需要它复制到/从的页面的虚拟映射。 >

另一个问题是 POSIX 异步 I/O,它让内核在进程不活动时完成 I/O(因此它的页表未在使用中)。如果有足够的可用空间,可以立即从用户空间复制到页面缓存/写入缓冲区,但如果没有,您希望在方便时让内核读取页面。特别是对于直接 I/O(绕过页面缓存)。


Brendan 还指出 MMIO(和 VGA 光圈)需要内核访问它们的虚拟地址空间;通常是 128MiB,所以你的 1GiB 内核虚拟地址空间是 128MiB 的 I/O 空间,896MiB 的 lowmem(永久映射内存)。


内核需要 lowmem 来处理每个进程的事情,包括每个任务(又名线程)的内核堆栈,以及页表本身。 (因为内核必须能够有效地读取和修改任何进程的页表。)当 Linux 转向使用 8kiB 内核堆栈时,这意味着它必须找到 2 个连续的页面(因为它们是从直接分配的-地址空间的映射区域)。对于一些在拥有大量线程的大型服务器上不明智地运行 32 位内核的人来说,lowmem 的碎片显然是一个问题。

,

例如,我们有两个 32 位 Linux,虚拟地址空间为 4 GB 可用虚拟内存地址空间的 3:1 GB(用户:内核),我们有两台机器,分别为 512 MB 和 2分别为 GB 物理内存。

对于 512 MB 物理内存的机器,Linux 内核可以在 lowmem 特定偏移量的 PAGE_OFFSET 虚拟区域中直接映射其内核空间中的整个物理内存。完全没问题。

但是对于 2 GB 物理 RAM 的机器,我们有 2 GB 的物理 RAM,我们希望将其映射到我们的内核虚拟 lowmem 区域,但这实在是太大了,因为 max 无法容纳它正如我们开头所说,内核虚拟地址空间只有 1 GB。所以,为了解决这个问题,Linux 直接将一部分物理 RAM 映射到 in 的 lowmem 区域(我记得,它是 750 MB,但它在不同的 arch 之间有所不同)然后它设置了一个可以使用的虚拟映射 剩余 RAM 量的临时虚拟映射。这就是所谓的高内存区域。

在 64 位模式下不再需要这种 hack,因为现在我们可以直接在 lowmem 区域中映射 128 TB 的内存

最后,这个区域与全局变量 high_memory 完全无关,它只是告诉你 lowmem 区域的内核上限。这意味着要知道系统中的 RAM 量,计算 PAGE_OFFSEThigh_memory 全局变量之间的差异,以获取 RAM 大小(以字节为单位)。

希望现在清楚了。