Linux内核内存管理引用

Linux内核内存管理引用

我很难理解 Linux 设备驱动程序书中的这段摘录(抱歉,这篇文章文字较多):

内核(在 x86 架构上,默认配置)在用户空间和内核之间分割 4 GB 虚拟地址空间;两种上下文中都使用同一组映射。典型的分割将 3 GB 专用于用户空间,1 GB 用于内核空间。

好的,我知道了。

内核的代码和数据结构必须适合该空间,但是内核地址空间的最大消耗者是物理内存的虚拟映射。

这是什么意思?内核的代码和数据结构不是也在“映射到物理地址空间的虚拟内存”中吗?否则这些代码和数据结构存储在哪里?

或者说内核需要虚拟地址空间来映射它通过驱动程序、IPC 或其他方式操作的随机非内核相关数据?

内核无法直接操作未映射到内核地址空间的内存。换句话说,内核需要它自己的虚拟地址来存储它必须直接接触的任何内存。

这是真的吗?如果内核在进程的上下文中运行(处理系统调用),进程的页表仍然会被加载,那么为什么内核不能直接读取用户模式进程内存呢?

因此,多年来,内核可以处理的最大物理内存量是可以映射到虚拟地址空间的内核部分的量,减去内核代码本身所需的空间。

好的,如果我对引文 #2 的理解是正确的,那么这是有道理的。

因此,基于 x86 的 Linux 系统最多可以使用略低于 1 GB 的物理内存。

????这看起来完全是不合逻辑的。为什么它不能使用 4GB 内存,而只是根据需要将不同的内容映射到内核可用的 1GB 空间中?为什么内核空间只有~1GB就意味着系统无法用4GB运行?不必一次全部映射。

答案1

为什么它不能使用 4GB 内存,而只是根据需要将不同的内容映射到内核可用的 1GB 空间中?

它可以,这就是HIGHMEM配置选项对不适合直接映射的内存所做的事情。但是当你需要访问内存中的任意位置时,这就很麻烦了更轻松如果您可以直接指向它,而不需要每次都设置映射,那么就可以做到这一点。为此,您需要一个始终映射到所有物理内存的虚拟内存区域,如果虚拟地址空间小于物理地址空间,则无法完成此操作。

直接访问也更快,vm/highmem.txt在内核文档中说:

创建临时映射的成本可能相当高。架构必须操作内核的页表、数据 TLB 和/或 MMU 的寄存器。

当然,您可以通过用户空间映射来访问正在运行的进程的内存,也许您可​​以避免访问其他进程的内存。但是,如果有任何大型的内核数据结构(如页面缓存),那么能够为它们使用所有内存就太好了。

整个事情有点像银行转换,这是在 16 位机器和 DOS 时代的 386/486 系统中使用的东西(HIMEM系统)。我认为即使在那时,也没有人特别喜欢这样访问内存,因为如果您需要同时“打开”物理内存的多个区域,事情就会变得相当困难。发展到 32 位系统,然后发展到 64 位系统已经解决了这个问题。

答案2

如果内核在进程的上下文中运行(处理系统调用),进程的页表仍然会被加载,那么为什么内核不能直接读取用户模式进程内存呢?

在这种情况下,“内核地址空间”一词不应被解释为与用户地址空间相反。相反,它的意思是内核需要访问的内存必须映射到一些虚拟地址。这就是本书作者在这里试图表达的观点。因此“内核的地址空间”就是整个映射。

答案3

它可以。 “多年来”却没有;最初没有理由这样做,因为没有人有那么多内存。

你需要继续读下去,仔细看。

然而,有多少内存可以直接与逻辑地址映射的限制仍然存在。只有内存的最低部分(最多 1 或 2 GB,具体取决于硬件和内核配置)具有逻辑地址;[2] 其余部分(高端内存)则没有。在访问特定的高内存页面之前,内核必须设置显式虚拟映射以使该页面在内核地址空间中可用。因此,许多内核数据结构必须放置在低内存中;高内存往往被保留给用户空间进程页面。


如果内核在进程的上下文中运行(处理系统调用),进程的页表仍然会被加载,那么为什么内核不能直接读取用户模式进程内存呢?

确实如此

https://www.quora.com/Linux-Kernel-How-does-copy_to_user-work


了解通常使用kmalloc()在内核中分配结构会返回约 1GB 直接映射范围内的内存也可能很有用。所以这是可爱且易于访问的。

(代价是它以这些不同类型的分配的形式引入了复杂性。

如果您希望标准kmalloc()分配能够使用超过 25% 的 RAM,那么您会做一些相当要求的事情...在更特殊的情况下,您可以设置 GFP_HIGHMEM 标志并根据需要映射和取消映射内存。但官方的答案是,您不应该尝试在装有超过 30 位物理 RAM 的传统 32 位系统上运行如此苛刻的工作负载。

如果您真的对这个具体细节感兴趣,我注意到另外两件事。

1. 1GB限制对RAM施加限制,但要高一点。

https://www.redhat.com/archives/rhl-devel-list/2005-January/msg00092.html

谷歌搜索表明,具有大量 RAM(例如 32 GB 或更多)的系统需要 4G:4G 补丁,因为内核内存表随着物理内存的大小而缩放,而 32 GB 系统使用 0.5 GB表,3G:1G 系统可用内核空间的一半。 64 GB 系统无法启动,因为该表需要所有内核内存。

4G:4G补丁是另一回事,但你可能可以忽略它,它不在主线Linux中。

听起来这个限制也已被克服,因为现在可以启用 CONFIG_HIGHMEM64G (在 i386 上,即 32 位)。可能最好不要依赖于此。或者过于认真地思考它必须做什么。

2. 页表并不严格需要直接映射。

许多流行的操作系统编写教程和演练都使用了一种令人兴奋的技巧,称为“递归页表”。

https://www.google.co.uk/search?q=recursive+page+tables

Linux没有使用这种方式,所以传统的Linux更容易理解。 ~1GB“低内存”的直接映射是在初始页表中设置的,并且永远不会改变。页表是从“低内存”中分配的。

(你在想 CONFIG_HIGHMEM64G 现在做了什么吗?停下来,这对你不好。)

我想 Linus 根本就没有想到递归技巧。 IIRC 还有其他缺点,即没有可用的大小合适的直接映射,但我不确定具体的示例。

我说“传统Linux”。我还没有听说 KPTI 是否实际上已合并为 32 位...但无论如何,KPTI 不应该改变总体想法。一旦从用户页表切换到内核页表,内核就可以访问直接映射。切换过程是一些很棒的黑魔法,但它只是在每次上下文切换时执行。用户空间页表不包括直接映射,但用户空间不也不应该访问页表等,所以一切都很好。

相关内容