内容摘要 在这篇 Linux® 内存模型指南中,我们将学习如何构建和管理内存方面的基础知识。本指南介绍了内存控制单元、分页模型方面的内容,并详细介绍了物理内存区域方面的知识。
内容摘要 在这篇 Linux® 内存模型指南中,我们将学习如何构建和管理内存方面的基础知识。本指南介绍了内存控制单元、分页模型方面的内容,并详细介绍了物理内存区域方面的知识。
要计算 GDT 中最多可以存储多少条目,必须先理解 NR_TASKS(这个变量决定了 Linux 可支持的并发进程数 —— 内核源代码中的默认值是 512,最多允许有 256 个到同一实例的并发连接)。
GDT 中可存储的条目总数可通过以下公式确定:
|
在这 8192 个段描述符中,Linux 要使用 6 个段描述符,另外还有 4 个描述符将用于 APM 特性(高级电源管理特性),在 GDT 中还有 4 个条目保留未用。因此,GDT 中的条目数等于 8192 - 14,也就是 8180。
任何情况下,GDT 中的条目数 8180,因此:
2 * NR_TASKS = 8180 NR_TASKS = 8180/2 = 4090
(为什么使用 2 * NR_TASKS?因为对于所创建的每个进程,都不仅要加载一个 TSS 描述符 —— 用来维护上下文切换的内容,另外还要加载一个 LDT 描述符。)
这种 x86 架构中进程数量的限制是 Linux 2.2 中的一个组件,但自 2.4 版的内核开始,这个问题已经不存在了,部分原因是使用了硬件上下文切换(这不可避免地要使用 TSS),并将其替换为进程切换。
接下来,让我们了解一下分页模型。
分页单元负责将线性地址转换成物理地址(请参见图 1)。线性地址会被分组成页的形式。这些线性地址实际上都是连续的 —— 分页单元将这些连续的内存映射成对应的连续物理地址范围(称为 页框)。注意,分页单元会直观地将 RAM 划分成固定大小的页框。
正因如此,分页具有以下优点:
将这些页映射成页框的数据结构称为页表 (page table)。页表存储在主存储器中,可由内核在启用分页单元之前对其进行恰当的初始化。图 5 展示了页表。
注意,上图 Page1 中包含的地址集正好与 Page Frame1 中包含的地址集匹配。
在 Linux 中,分页单元的使用多于分段单元。前面介绍 Linux 分段模型时已提到,每个分段描述符都使用相同的地址集进行线性寻址,从而尽可能降低使用分段单元将逻辑地址转换成线性地址的需要。通过更多地使用分页单元而非分段单元,Linux 可以极大地促进内存管理及其在不同硬件平台之间的可移植性。
下面让我们来介绍一下用于在 x86 架构中指定分页的字段,这些字段有助于在 Linux 中实现分页功能。分页单元进入作为分段单元输出结果的线性字段,然后进一步将其划分成以下 3 个字段:
线性地址到对应物理位置的转换的过程包含两个步骤。第一步使用了一个称为页目录 (Page Directory) 的转换表(从页目录转换成页表),第二步使用了一个称为页表 (Page Table) 的转换表(即页表加偏移量再加页框)。图 6 展示了此过程。
开始时,首先将页目录的物理地址加载到 cr3 寄存器中。线性地址中的 Directory 字段确定页目录中指向恰当的页表条目。Table 字段中的地址确定包含页的页框物理地址所在页表中的条目。Offset 字段确定了页框中的相对位置。由于 Offset 字段为 12 位,因此每个页中都包含有 4 KB 数据。
下面小结物理地址的计算:
cr3 + Page Directory (10 MSB) = 指向 table_base
table_base + Page Table (10 中间位) = 指向 page_base
page_base + Offset = 物理地址 (获得页框) 由于 Page Directory 字段和 Page Table 段都是 10 位,因此其可寻址上限为 1024*1024 KB,Offset 可寻址的范围最大为 2^12(4096 字节)。因此,页目录的可寻址上限为 1024*1024*4096(等于 2^32 个内存单元,即 4 GB)。因此在 x86 架构上,总可寻址上限是 4 GB。
扩展分页是通过删除页表转换表实现的;此后线性地址的划分即可在页目录 (10 MSB) 和偏移量 (22 LSB) 之间完成了。
22 LSB 构成了页框的 4 MB 边界(2^22)。扩展分页可以与普通的分页模型一起使用,并可用于将大型的连续线性地址映射为对应的物理地址。操作系统中删除页表以提供扩展页表。这可以通过设置 PSE (page size extension) 实现。
36 位的 PSE 扩展了 36 位的物理地址,可以支持 4 MB 页,同时维护一个 4 字节的页目录条目,这样就可以提供一种对超过 4 GB 的物理内存进行寻址的方法,而不需要对操作系统进行太大的修改。这种方法对于按需分页来说具有一些实际的限制。
虽然 Linux 中的分页与普通的分页类似,但是 x86 架构引入了一种三级页表机制,包括:
PAGE_SIZE),该值包含某页的物理地址,还包含了说明该条目是否有效及相关页是否在物理内存中的位。 为了支持大内存区域,Linux 也采用了这种三级分页机制。在不需要为大内存区域时,即可将 pmd 定义成“1”,返回两级分页机制。
分页级别是在编译时进行优化的,我们可以通过启用或禁用中间目录来启用两级和三级分页(使用相同的代码)。32 位处理器使用的是 pmd 分页,而 64 位处理器使用的是 pgd 分页。
如您所知,在 64 位处理器中:
我们可以从架构中看到,实际上使用了 43 位进行寻址。因此在 64 位处理器中,可以有效使用的内存是 2 的 43 次方。
每个进程都有自己的页目录和页表。为了引用一个包含实际用户数据的页框,操作系统(在 x86 架构上)首先将 pgd 加载到 cr3 寄存器中。Linux 将 cr3 寄存器的内容存储到 TSS 段中。此后只要在 CPU 上执行新进程,就从 TSS 段中将另外一个值加载到 cr3 寄存器中。从而使分页单元引用一组正确的页表。
pgd 表中的每一条目都指向一个页框,其中中包含了一组 pmd 条目;pdm 表中的每个条目又指向一个页框,其中包含一组 pte 条目;pde 表中的每个条目再指向一个页框,其中包含的是用户数据。如果正在查找的页已转出,那么就会在 pte 表中存储一个交换条目,(在缺页的情况下)以定位将哪个页框重新加载到内存中。
图 8 说明我们连续为各级页表添加偏移量来映射对应的页框条目。我们通过进入作为分段单元输出的线性地址,再划分该地址来获得偏移量。要将线性地址划分成对应的每个页表元素,需要在内核中使用不同的宏。本文不详细介绍这些宏,下面我们通过图 8 来简单看一下线性地址的划分方式。
Linux 为内核代码和数据结构预留了几个页框。这些页永远不会 被转出到磁盘上。从 0x0 到 0xc0000000 (PAGE_OFFSET) 的线性地址可由用户代码和内核代码进行引用。从 PAGE_OFFSET 到 0xffffffff 的线性地址只能由内核代码进行访问。
这意味着在 4 GB 的内存空间中,只有 3 GB 可以用于用户应用程序。
编辑 webmaster