一、背景知识

AMD和Intel的x64架构相关名称由来

由于IA-64(英特尔安腾)是Intel当初设计的不兼容IA-32处理器的一个全新架构,所以传统的32位程序无法在这个架构里运行,所以推出后卖的很不好。与此同时AMD公司却推出了兼容32位架构的64位处理器,所以推出后卖的很好,这个处理器刚开始AMD称之为x86-64后来又改为AMD64。而Intel公司看到这种情况就只好开发出了与IA-32兼容的64位处理器,称为IA-32e,最后又改名为EM64T、INTEL64,至于IA-32e这个名字后来又演化成了一种处理器的工作模式。这使得intel在与AMD的竞争中开始成为了跟跑着。

逻辑地址和线性地址和虚拟地址和物理地址:

其中有效地址就是段内偏移量,在实模式下逻辑地址是由逻辑段地址左移动4位加上有效地址组成;在保护模式,以及IA32-e模式下逻辑地址是由段选择子所指向的段描述符的段地址加上有效地址组成。

而线性地址是从处理器内部段部件发出的地址,如果不开启分页功能,线性地址就直接是物理地址;如果开启分页功能线性地址就是我们常说的虚拟地址。

处理器的位数和最大物理内存访问数量无关

x86处理器

地址线数

最大物理内存访问数量

8086(16bit处理器)

20

1MB

80286(16bit处理器)

24

16MB

80386(32bit处理器)

32

4GB

后续的32bit和64bit处理器

36

64GB

40

1TB

52

4PB

平坦模型(分段机制的内存管理模型)

1)在这种模型每个段的基地址都是0,界限为线性地址的最大值。一个程序内不分段,这种模型下加载内核和用户程序也不需要重定位。

2)在这种基本的平坦模型下,如下图所示,软件程序在编写的时候是不分段的,程序在一个大段内,代码和数据都在这个大段内,但是编址是连续的,所以将程序加载到内存空间后每条指令和每个数据在4G字节内存空间的偏移量也就确定了。也就是说最终映射完成后程序的汇编地址布局和虚拟内存空间布局是一样的

3)在程序中只需按往常的方式(段的基地址+段内偏移)来访问数据,处理器也按照往常的方式来执行指令。由于段的基地始终为0,所以就没必要再管基地址(因此程序中就不需要再去加载段寄存器,从这点来看,平坦模型大大简化了程序的设计)

x64架构的段寄存器

有6个段寄存器,其中CS用来执行代码、SS用来执行栈操作、DS/ES/FS/GS用来访问数据。每个段寄存器都有一个”隐藏部分”,可以将其看成是段寄存器的内部Cache,用来加快处理器的访问。

二、x64架构CPU支持的几种工作模式

x64架构CPU的工作模式有实地址模式(8086模式)、保护模式 、虚拟8086模式、系统管理模式(SMM) 和IA32-e模式(Long mode) 。其中16位实地址模式(8086模式)和32位保护模式称为传统模式。

各工作模式之间的切换

各模式对通用寄存器的访问

x64架构通用寄存器如下所示,其中蓝色部分是x64新增加的,如果处理器工作在保护模式或者兼容模式,则不能使用如下蓝色部分的寄存器。如果处理器工作在64位模式则不能在指令中同时使用传统的高字节寄存器和新增的字节寄存器

RAX/EAX/AX/AH/AL

RBX/EBX/BX/BH/BL

RCX/ECX/CX/CH/CL

RDX/EDX/DX/DH/DL

RSI/ESI/SI/SIL

RDI/EDI/DI/DIL

RBP/EBP/BP/BPL

RSP/ESP/SP/SPL

R8/R8D/R8W/R8B

R9/R9D/R9W/R9B

R10/R10D/R10W/R10B

R11/R11D/R11W/R11B

R12/R12D/R12W/R11B

R13/R13D/R13W/R13B

R14/R13D/R13W/R13B

R15/R15D/R15W/R15B

三、IA32-e(Long Mode)模式

x64独有的模式,Intel称之为IA-32e模式(AMD公司称之为Long Mode),现在市面上的64位操作系统都工作在这种模式下。64位操作系统可以运行之前的16/32位应用程序,这并不完全是操作系统的功劳,处理器的支持尤为重要,当初开发IA32-e就是为了兼容IA32,所以对于IA32-e包含了二种子模式:兼容子模式和64位子模式。

兼容子模式:类似于32位保护模式,允许大部分16/32位程序不用重新修和编译就可以直接运行在64位操作系统上(除了那些使用处理器硬件来进行任务切换的程序和工作在虚拟8086模式下的程序,因为在IA32-e下不支持处理器硬件任务切换和虚拟8086模式);

64位子模式:用来运行新型的64位操作系统和64位应用程序。

IA32-e的二种子模式如何并存呢?

如何进入IA32-e模式

在保护模式下通过开启IA-32e模式,开启PAE功能,开启分页就进入了IA32-e模式。

需要注意的是如果在保护模式下已经开启了分页则必须先关闭分页功能(这是因为保护模式使用的是传统的分页功能,而IA32-e模式必须使用4级或5级分页),然后初始化4级或5级分页,最后再重新开启分页从而最终激活IA32-e模式。

刚进入IA32-e的时候是进入到了其兼容子模式,这是因为在保护模式下代码段描述符和代码段寄存器的L位是保留的,必须清零,因此进入IA32-e模式后L位依然是0。通过执行使得CS.L=1后就会从兼容子模式切换到64位子模式。

四、x64架构各工作模式的存储器段描述符(代码段描述符和数据段描述符)

代码段描述符和数据段描述符都在全部描述符表GDT中

传统保护模式的存储器段描述符

段描述符的的结构如下所示

数据段描述符如下所示

IA32-e模式(Long Mode)的存储器段描述符

兼容子模式

代码段描述符如下所示:

数据段描述符结构和传统保护模式是一样的。对数据段描述符的解释和指令的动作与传统的保护模式相同

64位子模式

代码段描述符的结构如下所示

数据段描述符的结构和传统保护模式一样的

五、x64架构各工作模式的内存访问

传统实地址模式的内存访问

该模式下段寄存器用来加载一个16位的逻辑段地址,处理器将这个地址左移4位后和指令中的16位有效地址一起形成20位的地址,并且0扩展到32位。保存在段寄存器的”隐藏部分”用于后续的内存访问。

后续内存访问时,将该该线性地址左侧扩展到处理器实际的物理地址位数来访问内存。

传统保护模式的内存访问

在该模式下,段寄存器保存一个16bit的段选择子。

处理器使用段选择子在段描述符表中选择一个段描述符,将段描述符的内容加载到段寄存器的”隐藏部分”用于后续的内存访问。

段描述符中包括32位的线性基地址,它与指令中的32位有效地址一起生成32位的线性地址。

如果不开启分页那么线性地址就是物理地址。开启分页后,该模式下每个任务各自就有32位的线性地址/虚拟地址空间。当处理器地址线位数多余32位时便可以通过更大的分页(比如32位4MB分页技术,低22bit作为offset,高10bit作为PDE索引,不使用PTE)或者PAE(物理地址扩展)技术来访问超过4GB以上的物理内存区域;前者仍然使用32位的PDE/PTE,后者使用64位的PDE/PTE。

比如32位PAE-4KB的分页技术table walk的过程如下所示:

IA32-e模式(Long Mode)下的下的内存访问

该模式是x64架构新增的,在IA32-e模式下必须开启分页。

兼容子模式的内存访问

该子模式通常用来运行传统的保护模式的程序,段部件的的工作方式和上述传统实地址模式的访问方式相同。

段部件最终也是产生32位的线性地址。另外进入页部件前32位线性地址左侧用0扩展到64位。

64位子模式的内存访问

段部件的处理

在该子模式下处理器强制使用平坦模型,段部件部分功能被禁止:段寄存器除了FS和GS,其他的段寄存器的基地址和段界限都被忽略,处理器将他们的段基地址看成是0,在指令中除非是使用了段超越前缀FS和GS,要不处理器访问内存时使用0作为段的线性基地址。

1)CS:基地址部分除了被忽略视为0,段界限以及很多属性并不再检查(除了DPL、L、D等标志),但是要检查有效地址是否位扩高形式。另外段超越前缀”CS:”不起作用。例如MOV RAX, CS:[MYDATA]

2)DS和ES和SS:不再使用这些段寄存器。因此,相关的指令,比如LDS和POP ES等不再有效。如果访问内存时使用了这些段寄存器,则按段的基地址为0来对待。不检查段界限和属性,只检查生成的虚拟地址是否符合扩高形式。另外使用这些段寄存器作为段超越前缀不起作用,例如:MOV RAX, ES:[MYDATA]

3)FS和GS:基地址部分仍然用于地址计算(而不是直接看成0),而且它们的基地址部分用特殊的方法扩展到64位。常规的段寄存器加载指令比如MOV FS, AX或者POP GS等等,只能操作基地址的32位,高32位别自动清零。只能使用特殊的方法才能加载全部的64位段地址。使用这二个段寄存器访问内存时,不检查界限值和属性,但是要检查生成的线性地址是否符合扩高形式。

地址的扩高

段部件出来的地址是64bit的线性地址。可见每个任务都有自己独立的64bit的线性地址理论空间,最大为16EB,非常巨大也用不到这么多。

比如只用低48bit。而bit48-bit63要么是全0要么是全1(若线性地址的bit47为1,那么就为全1,bit47为0则为全0)即”扩高“,英文为canonical。由于这种”扩高“的特点,可以将线性地址空间分为如下三部分。这样的话处理器在进行地址转换时,会对64bit线性地址进行检查,若不是”扩高“形式处理器则会产生一个异常。在实际的编程工作中,可以将所有任务共有的部分放到FFFF800000000000-FFFFFFFFFFFFFFFF,把每个任务的私有部分放到0x0000000000000000-0x00007FFFFFFFFFFF

页部件的处理

页部件可以使用4级或5级分页机制将”扩高“形式的64bit线性地址的低48/57bit转为物理地址(物理地址的位数依处理器而定)。CPU控制寄存器CR4的bit 12为LA57(意思是57位线性地址),为0表示处理器使用4级分页,为1表明处理器采用5级分页,这一位不可修改,并且只能在64位子模式下读取。

通过5级分页,虚拟地址从48bit扩展到57bit,虚拟内存从256TB增加到128PB,处理器的物理地址增加到52bit,这种5级分页支持对当今日益强大的内存密集型的服务器非常重要。