Linux 内核中 "HighMem" 不同含义的混​​淆

Confusion about different meanings of "HighMem" in Linux Kernel

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

我收集到的两个定义是:

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

    中没有显示任何 highmem 内存
  2. Highmem用于描述用户-space的虚拟内存地址space。这与 lowmem 形成对比,用于内核的地址 space 映射到每个用户 space 程序的地址 space。我看到这种措辞的另一种方式是名称 zone_highmem (highmem) 和 zone_normal (lowmem)。例如,在 32 位系统“3/1”user/kernel 内存拆分中,用于用户 space 的 3GB 将被视为高端内存(参见 , 5, 6, 7)。

一个定义比另一个更正确吗?

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

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

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

但是我们有 2 GB 物理 RAM 的 2 GB 物理 RAM 机器,我们想映射到我们的内核虚拟 lowmem 区域,但这太大了,它放不下因为最大内核虚拟地址 space 正如我们在开头所说的那样只有 1 GB。因此,为了解决这个问题 Linux 直接将一部分物理 RAM 映射到 lowmem 区域(我记得,它是 750 MB,但它在不同的 arch 之间有所不同)然后它设置了一个虚拟映射,它可以使用剩余 RAM 量的 临时 虚拟映射。这就是所谓的高端内存区域。

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

最后,这个区域与全局变量 high_memory 完全无关,全局变量 high_memory 只是告诉您 lowmem 区域的内核上限。这意味着要了解系统中的 RAM 数量,请计算 PAGE_OFFSEThigh_memory 全局变量之间的差异以获得以字节为单位的 RAM 大小。

希望现在天气晴朗了。

我认为您的用法 2 示例实际上是(有时是错误的)用法 1 或其后果的描述。没有单独的含义,这只是由于没有足够的内核虚拟地址 space 来保持所有物理内存一直映射的结果。

(因此,使用 3:1 user:kernel 拆分,您只有 1GiB 的低内存,其余的是高内存,即使您不需要启用 PAE 分页也能看到所有内存。 )

这篇文章 https://cl4ssic4l.wordpress.com/2011/05/24/linus-torvalds-about-pae 引用了一篇 Linux Torvalds 的咆哮,说虚拟地址 space 比物理地址(PAE 所做的)要少得多,而 highmem 是Linux 试图从无法保持映射的内存中获得一些使用。

PAE 是一个 32 位 x86 扩展,它将 CPU 切换为使用具有更宽 PTE 的备用页面-table 格式(,包括一个执行权限位,和最多 52 位物理地址的空间,尽管最初 CPUs 支持它只支持 36 位物理地址)。如果您完全使用 PAE,那么您的所有页面都会使用它 tables.


使用 a high-half-kernel memory layout 的普通内核会为自己保留虚拟地址 space 的上半部分,即使 user-space 是 运行ning。或留给用户-space更多空间,32位Linux移动到3G:1Guser:kernel拆分。

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

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

这是没有任何highmem的normal/good情况,也适用于32位Linux在显着减少的系统上超过 1GiB 的物理内存。 这就是 Linus 说的原因:

virtual space needs to be bigger than physical space. Not “as big”. Not “smaller”. It needs to be bigger, by a factor of at least two, and that’s quite frankly pushing it, and you’re much better off having a factor of ten or more.

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

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


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

很多时候内核必须访问该内存时任务是当前任务,使用用户指针。例如read/write 系统调用使用 user-space 地址调用 copy_to/from_user(复制 to/from 文件 read/write 的页面缓存),通过用户页面到达 highmem table 个条目。

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

但是,如果内核想要从非 运行ning 的进程换出一些页面怎么办? DMA 在物理地址上工作,所以它可能可以计算出正确的物理地址,只要它不需要实际加载任何用户-space 数据。

(但这通常不是那么简单,IIRC:32 位系统中的 DMA 设备可能不支持高物理地址。因此内核实际上可能需要低内存中的反弹缓冲区...我同意 Linus 的观点:highmem 糟透了,使用 64 位内核显然要好得多,即使你想 运行 纯 32 位用户-space。)

任何像 zswap 这样动态压缩页面的东西,或者任何 需要使用 CPU 复制数据的驱动程序,都需要页面的虚拟映射它正在复制 to/from.

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


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


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