cpu的内存管理

cpu的内存管理的总结

cpu运行模式下的内存管理

实际上Linux中的内存管理和cpu中的运行模式是紧密相关的。那么我们可以知道cpu有哪些运行模式了。这篇文件对运行模式进行了简单的总结。

下面这张图体现了各个模式下的内存模型,在除开64bit长模式下,其他模式的内存模型都是分段的。32 位虚拟内存空间被视为代码、堆栈和数据段的分段地址空间集,每个段都有自己的基地址和保护参数。通过将段选择器添加到地址来指定分段空间。

而长模式下,64 位虚拟内存空间被视为单个、平坦(未分段)地址空间。程序地址访问位置可以位于线性 64 位地址空间中的任何位置。出于内存保护的目的,操作系统可以对代码、堆栈和数据段使用单独的选择器,但所有这些段的基地址始终为 0

注意一个是64bit地址空间,另外几个都是32bit地址空间。

注意64bit这里是平坦内存模型,与之相对应的是多段内存模型,平坦内存模型中,每个寄存器中的值都是0,所以使得每一个段一开始都是指向同一个地方的。

分段内存模型的内存管理:

具体的:

平坦内存模型的内存管理:

Effective Address:有效地址

具体的:

分段内存管理

以兼容性著称的x86处理器,即便到了今天的64位时代,依然保留了对segmentation的向前兼容,而linux操作系统最开始也是基于i386写的,所以也保留了对segmentation的支持。了解下历史,往往可以让我们对后来出现的某些事物有更好的理解。

分段机制提供十个段寄存器,每个段寄存器定义一个段。其中六个寄存器(CS、DS、ES、FS、GS 和 SS)定义用户段。用户段保存软件、数据和堆栈,可供应用软件和系统软件使用。其余四个段寄存器(GDT、LDT、IDT 和 TR)定义系统段。系统段包含仅由系统软件初始化和使用的数据结构。段寄存器包含指向段起始位置的基地址、定义段大小的限制以及定义段保护特性的属性。

尽管分段在重新定位和保护软件和数据方面提供了很大的灵活性,但结合软件和硬件分页支持来处理内存隔离和重新定位通常会更有效。因此,大多数现代系统软件都会绕过分段功能。然而,分段不能完全禁用,了解分段机制对于实现长模式系统软件非常重要。这是由于在传统模式中仍然需要使用到分段功能,

下面我们通过对于各个cpu模式的分析,来学习cpu的分段机制。

分段所使用的数据结构、寄存器

段寄存器——六个段寄存器(CS、DS、ES、FS、GS 和 SS)用于指向用户段。当描述符被加载到段寄存器之一时,段选择器会选择该描述符。这导致处理器自动将选定的描述符加载到段寄存器的软件不可见部分。

描述符表寄存器—三个描述符表寄存器(GDTR、LDTR 和 IDTR)用于指向系统段。描述符表寄存器标识了该描述符表的虚拟内存位置和大小。

任务寄存器(TR)—描述当前任务状态段(TSS)的位置和限制。

上面两张图显示了分段所使用的寄存器和数据结构,其中第二张图的从左往右:
段选择器:这些数据都存储在段寄存器中如CS、DS等等。段选择寄存器的作用是去系统表(GDT、LDT、IDT)中查找的偏移值

关于系统表的详细描述都在AMD手册第二本的4.6节

实模式下的分段

8086的CPU是16位的,但intel的工程师希望在不改变CPU寄存器和指令集的基础上,让它可以可以寻址更大的内存范围。于是他们使用了一种叫segmentation的机制,即一个逻辑地址(logical address)由segment加上offset组成,一般表达为segement:offset的形式。

当segment的值放入segment register时,就是告诉CPU:现在要访问这个segment对应的内存区域,然后再根据offset的值,在这个segment内找到对应的字节。转换后形成的地址被称作线性地址(linear address),若offset为16位,linear address = segment<<4 + offset,则寻址范围就扩大成了20位,比如logical address为0xA000:0x5F00,转换成linear address就是0xA5F00。

这种内存访问被称为实模式(Real Mode),因为linear address是由segment基址移位加上一个偏移得到,因此实模式下的这种segmentation被称为shift-and-add segmentation。

8086虚拟模式下的分段

Virtual-8086模式支持在保护模式下运行的16位实模式程序。它使用简单形式的内存分段、可选分页和有限的保护检查。

段基址是给定段中的最低地址,等于段选择器 * 16。POP 和 MOV 指令的工作方式与实模式下完全相同,可用于将(可能)新的段选择器加载到其中一个段寄存器。

保护模式下的分段

80286也使用segmentation,但和8086不同的是,80286中segment register存的不再是segment的起始地址,而是一个segment selector,通过这个selector查找GDT表获得segment descriptor,segment descriptor存的才是segment的起始地址,因此保护模式下的这种segmentation被称为table-based segmentation(区别于实模式的shift-and-add方式)。

长模式中的分段

长模式又分为了两种子模式:

  • 64bit长模式
  • 兼容模式

64bit长模式:下禁用分段,创建平面64位虚拟地址空间。正如将要看到的,某些段寄存器(特别是系统段寄存器)的某些功能继续在 64 位模式下使用。

兼容模式:这个模式和传统模式中的分段处理是一致。

64bit下的段寄存器

CS:

64 位模式下的 CS 寄存器。在 64 位模式下,CS 寄存器的大部分隐藏部分被忽略。 64 位模式仅识别 L(长整型)、D(默认操作大小)和 DPL(描述符特权级别)属性。地址计算假定 CS.base 值为 0。CS 引用不检查 CS.limit 值,而是检查有效地址是否采用规范形式。

DS、ES、SS:

64 位模式下的 DS、ES 和 SS 寄存器。在 64 位模式下,ES、DS 和 SS 段寄存器的内容被忽略。段寄存器隐藏部分中的所有字段(基址、限制和属性)都将被忽略。64 位模式下引用 ES、DS 或 SS 段的地址计算被视为段基址为 0。处理器不执行限制检查,而是检查所有虚拟地址引用是否采用规范形式。

GS、FS:

CS、DS、ESSS 段不同,FSGS 段覆盖<span class=”hint–top hint–rounded” aria-label=”Segment Override 段覆盖 我们已经知道,有效地址是通过段寄存器值加上相应偏移量的值来计算的。但是,如果我们想使用特定代码的默认段之外的其他段寄存器怎么办?段覆盖提供了这样一种方法:

1
2
3
GS寄存器常用来访问进程的控制块(PCB)

FS寄存器常用来访问TLS本地线程存储
">[2]
可以在 64 位模式下使用。当在 64 位模式下使用 `FS` 和 `GS` 段覆盖时,它们各自的基地址将用于有效地址 (EA[1]) 计算。完整的 `EA` 计算则变为(`FS` 或 `GS​​`)。FS.base 和 GS.base 值也扩展到完整的 64 位虚拟地址大小。

fsgs 是唯一在 64 位模式下执行任何操作的段覆盖。

分页的管理

我们已经知道,现代cpu的内存管理方式。分段的方法使用的越来越少。下面我们学习cpu是如何分页的。

长模式

下面这张图显示了,长模式下两种子模式的内存管理方法。

可以看到,实际上在64bit模式下内存管理是直接通过分页机制进行的,而不再需要前文所述的哪些步骤。但是对应兼容模式来说是需要的,首先将逻辑地址转换为线性地址(虚拟地址),然后通过分页机制完成虚拟地址到物理地址的转换。

需要注意这里的paging是不可选的。

传统模式

由于传统模式主要是32bit地址。

由此我们可以看出在传统模式中,只有实模式不需要通过页转换就直接由线性地址变成物理地址,这是因为实模式一般用于操作系统启动时,这使得页映射操作不便进行。

需要注意保护模式和虚拟8086模式下的paging是可选的。不同于长模式都是不可选的。实模式不需要进行页转换。

寄存器划分

由于64bit长模式单独使用一种内存模型,所以我们需要分开讨论寄存器的使用。

64bit长模式

在该模式下只有CS、FS、GS可以使用,其中FS寄存器和GS寄存器的主要作用是:

1
2
MOV AX , [BX] ;这是没有使用段覆盖的情况, 有效地址等于 DS<<4 + BX
MOV AX, SS : [BX] ;这就是使用了段前缀的情况, 有效地址等于 SS<<4 + BX

其他模式

在其他模式下,寄存器是照常使用的。

关于segmentation和保护模式的关系

分段机制

由上我们已经知道了虽然分段机制的使用已经很少了,但是我们仍然需要了解它。

分页机制概述

不管是intel的IA32还是AMD的Long模式都需要使用分页来进行内存管理,并且很多时候使用分页大于使用分段。并且每个进程都可以应用自己的页表。

传统的 x86 架构支持将 32 位虚拟地址转换为 32 位物理地址(作为特殊模式支持更大的物理地址,例如 36 位或 40 位地址)。 AMD64 架构增强了这种支持,允许将 64 位虚拟地址转换为 52 位物理地址,尽管处理器实现可以支持更小的虚拟地址和物理地址空间。

页面翻译(转换)概述

下图显示了长模式中使用的页面翻译层次结构的概述。传统模式分页使用此转换层次结构的子集(传统模式中不存在页面映射级别 4 表,并且可能会或可能不会使用 PDP 表,具体取决于启用的分页模式)。第 5 级转换,即页面映射级别 5,仅在启用 5 级分页时才存在。如图所示,虚拟地址分为多个字段,每个字段用作转换表中的偏移量。完整的翻译链由虚拟地址字段引用的所有表条目组成。最低阶虚拟地址位用作物理页的字节偏移量。

关于页大小的支持:

支持以下物理页大小:4 KB2 MB4 MB1 GB

在长模式下,可以使用 4-KB2-MB1-GB 大小。

在传统模式下,可以使用 4KB2MB4MB 大小。

虚拟地址的长度为 32 位,并且可以使用最多支持的物理地址大小的物理地址。 AMD64 架构允许将最多 64 位长的虚拟地址转换为最多 52 位长的物理地址,从而增强了传统转换支持。

页面翻译设置选项:

  • Page-Translation Enable (CR0.PG) PTE
  • Physical-Address Extensions (CR4.PAE) PAE
  • Page-Size Extensions (CR4.PSE) PSE
  • Long-Mode Active (EFER.LMA) LMA

分别是页翻译使能、物理地址扩展、页大小扩展、长模式活动(自己乱翻译的)。下表体现了不同模式下的页面翻译选项的使用情况:

当开启了PAEPAE 分页将页转换表条目的大小加倍至 64 位,以便表条目可以容纳更大的物理内存。

长模式下的页翻译(转换)机制

长模式页面转换需要使用物理地址扩展(PAE)。在激活长模式之前,必须通过将 CR4.PAE 设置为 1 来启用 PAE。在启用 PAE 之前激活长模式会导致发生一般保护异常 (#GP)。并且在长模式下 PAE 是一直开启的。因此页目录项 (PDE.PS) 中的 PS 位在 4 KB 和 2 MB 页面大小之间进行选择,并且忽略 CR4.PSE 位。当支持 1 GB 页面时,PDPEPS 位选择 1 GB 页面大小。

开启长模式后,虚拟地址的位数从32位增长至64位。并且这个模式和传统模式的不同在于没有了两种页翻译模式:正常分页模式和PAE分页模式。

在长模式下,当 5 级分页禁用时(CR4[LA57]=0),CR3 寄存器用于指向 PML4 基地址;当 5 级分页使能时(CR4[LA57]=1),CR3 寄存器用于指向 PML5 基地址。 )。 CR3 在长模式下扩展为 64 位,允许 PML4 或 PML5 表位于 52 位物理地址空间中的任何位置。

以下是长模式下的CR3寄存器中的数值。

4KB 页转换

使用的是4级页表时的结构。

使用4级页表时,64位虚拟地址被划为6个部分,

使用5级页表的结构。

2MB 页转换

页所占的空间越大,所需要的页表级数就越低。使用2MB时,页转换结构如下:

当开启5级页表时:

2GB 页转换

在长模式下,1 GB 物理页转换是通过将虚拟地址划分为最多五个字段来执行的。最多三个字段用作级别页面翻译层次结构的索引。

这里需要注意的是,虽然是一个二级页转换,但是使用的是PML4T表和PDPE表,而不是靠近物理内存的页表。

当开启了5级页表时;

传统模式下的页翻译(转换)机制

传统模式下有两种翻译模式:

  • 正常分页(Non-PAE)
  • PAE分页

x86-64架构使用CR3寄存器存储页表的基地址,该寄存器中存储的格式和其使用两种页翻译模式有关:

从上图中我们可以看到:

  • 页表基地址字段(就是最长的白色的段)的长度取决于所使用的模式
    • Non-PAE模式下其占用了31:12位的空间
    • PAE模式下占用了31:5位的空间
  • Page-Level Writethrough (PWT) Bit.页级直写 (PWT) 位。位 3。页级写通指示最高级别的页转换表是否具有写回或写通缓存策略。当PWT=0时,表有写回缓存策略。当PWT=1时,表具有writethrough缓存策略。
  • Page-Level Cache Disable (PCD) Bit.页级高速缓存禁用 (PCD) 位。位 4。页级高速缓存禁用指示最高级别页转换表是否可高速缓存。当 PCD=0 时,表可缓存。当 PCD=1 时,该表不可缓存。
  • Reserved,保留位,写入CR3 时,保留字段应由软件清0。

正常分页(Non-PAE)

这个模式支持的4KB4MB的页大小。

4KB的页面转换

下图显示了这个模式下的4KB页面翻译(转换)

首先这是一个二级页表。4 KB 物理页转换是通过将 32 位虚拟地址划分为三个字段来执行的。上面两个字段中的每一个都用作两级页面翻译层次结构的索引。虚拟地址被划分三段,每一段都是对应页的偏移地址,最后一个是物理页的偏移地址。

从上图可以看到在页目录表、页表中存储的结构:PDE PTE。它们的具体结构如下:

PDE:

PTE:

4MB的页面转换

4 MB 页面转换。仅当启用页面大小扩展 (CR4.PSE=1) 且禁用物理地址扩展 (CR4.PAE=0) 时,才支持 4 MB 页面转换。

PSE 以 32 位 PDE 格式 (PDE.PS) 定义页面大小位。处理器在页面转换期间使用该位来支持 4 MB 和 4 KB 页面。当 PDE.PS 设置为 1 时,选择 4 MB 页面,并且 PDE 直接指向 4 MB 物理页面。 PTE 不用于 4 MB 页面转换。如果 PDE.PS 被清除为 0,或者如果禁用 4 MB 页面转换,则 PDE 指向 PTE。

上面我们说了4KB的页面转换,而4MB的页面转换实际上更加简单。如下图所示:

有一个二级页表直接变成了一级页表。虚拟地址不在被划分为三个段,而是两个段。并且仔细看的话PED旁边的数字从32变成了40,这是开启了PSE(页大小扩展)的结果。

AMD64 架构修改了 PSE 模式下的传统 32 位 PDE 格式,将物理地址大小支持增加到 40 位。地址大小的增加是通过使用位 20:13 来保存八个额外的高阶物理地址位来实现的。位 21 被保留,必须清除为 0。如下图PDE结构

关于PSE模式就是上面我们提到的四种页翻译选项之一。

此时的PDE结构又不相同了,因为少了一级页表:

PAE模式

上面我们说了非PAE模式下的页翻译机制,现在我们看PAE模式下又和之前的哪个模式有什么不一样的地方。

当启用物理地址扩展 (CR4.PAE=1) 时,使用 PAE 分页。 PAE 分页将页转换表条目的大小加倍至 64 位(所以我们在下面会看到每一个页表项的位数变成了64位),以便表条目可以容纳更大的52位的物理内存。

每个表的大小仍然是 4 KB,这意味着每个表可以容纳 51264 位条目。 PAE 分页还引入了第三级页转换表,称为页目录指针表 (PDPT)。

和非PAE模式不同还有:PAE 分页模式下大页的大小为 2 MB,而不是 4 MB。 PAE 使用页目录页大小位 (PDE.PS) 来允许在 4 KB2 MB 页大小之间进行选择。 PAE 自动使用页面大小位,因此 PAE 分页将忽略 CR4.PSE 的值。

4KB PAE页转换

直接使用上了三级页表,这也是传统模式下支持的最高级了。

可以看到虚拟地址被划为四段,多了一个Page-Directory-Pointer Offset页目录指针表。

  • 31-30位索引到四条页表项的PDPE
  • 29-21位索引到512条页表项的PDE
  • 10-12位索引到512条页表项的PTE
  • 11-9位索引到具体的物理页地址

具体的关于各种页的结构如下:

2MB PAE页转换

具体内容:

4KB的不同如下:

  • 使用二级表,而不是三级表
    其他和4KB基本一致。

其他知识点

限制检查

除 64 位模式外,所有引用内存的指令都会执行限制检查。限制检查检测尝试访问当前段边界之外的内存、尝试执行当前代码段之外的指令以及在当前描述符表之外进行索引。如果指令未通过限制检查,则所有其他段限制违规都会发生一般保护异常,或者堆栈段限制违规会发生堆栈故障异常。

在 64 位模式下,在访问 CS、DS、ES、FS、GS 和 SS 选择器寄存器引用的任何段期间,不会检查段限制。相反,处理器会检查用于引用内存的虚拟地址是否采用规范地址形式。在 64 位模式下,与传统模式和兼容模式一样,会检查描述符表限制。

参考资料

AMD64 Architecture Programmer’s Manual Volume 1:Application Programming

AMD64 Architecture Programmer’s Manual Volume 2:System Programming

https://zhuanlan.zhihu.com/p/67735248

https://zhuanlan.zhihu.com/p/67714693

https://www.includehelp.com/embedded-system/segment-override-prefix-8086-microprocessor.aspx

前置知识

  1. Effective address (EA) 中文又称之为偏移地址、有效地址。其实就是相对于基地址的偏移
  2. Segment Override 段覆盖 我们已经知道,有效地址是通过段寄存器值加上相应偏移量的值来计算的。但是,如果我们想使用特定代码的默认段之外的其他段寄存器怎么办?段覆盖提供了这样一种方法:
    undefinedundefined

cpu的内存管理
https://ysc2.github.io/ysc2.github.io/2024/01/02/cpu的内存管理/
作者
Ysc
发布于
2024年1月2日
许可协议