X86記憶體區段

x86架構中,記憶體區段(英語:Memory Segmentation)是在不改變16位元段選擇子时,使用單个索引暫存器(保存了段内地址偏移值)所能夠定址的的記憶體範圍部份。也指在英特尔x86指令集体系结构下記憶體區段的实现方式。

8086开始到随后的各款x86架构CPU,无论是实模式还是保护模式,内存寻址时都使用16位段寄存器(segment register)。段寄存器默认使用情况为:

  • 代码段寄存器CS与寄存器IP相配合获得当前线程代码执行到的内存位置;
  • 数据段寄存器DS与各通用寄存器配合访问内存中的数据;
  • 栈段寄存器SS与寄存器(E)SP、(E)BP配合访问线程的调用栈(call stack);
  • 扩展段寄存器ES用于特定字符串指令(如MOVS或CMPS)。
  • 80386引入了2个额外的段寄存器FS与GS,并无特定的硬件用途。

这些段寄存器除了有16位的可见部分,还有不可见的隐藏部分,称为描述符缓存“descriptor cache”或隐藏寄存器“shadow register”[1]。当一个段选择符(segment selector)装入段寄存器的可见部分,处理器同时也把该段描述符的其它数据装入到段寄存器的隐藏部分,这包括段开始的基地址、段长度、访问控制信息等。这些信息缓存到段寄存器中,避免了处理器在转址(translate address)时花费额外的总线周期从段选择符表中读入数据。处理器指令中可以明示使用哪些段寄存器,这将替换掉默认使用的段寄存器。[2]

历史

1978年的Intel 8086开始引入了内存分段。这使得16位CPU可以访问超过64 KB (65,536字节)的内存,实际上8086 CPU到内存的地址总线是20位,即可访问220=1MB内存。在16位元模式,要讓應用程式使用多個記憶體區段(為了存取比任一64K區段還要大的記憶體)是相當複雜。這個問題的根源在於沒有適當的適合做整個記憶體範圍的平面定址的位址算術指令。平面定址方式也可以乘法指令來完成,但这會導致較慢的程式執行速度。

1982年面世的80286處理器的真實模式保護模式,以及80386及其後的處理器的虛擬86模式,一個區段的大小是64 KiB(使用16位元索引暫存器)。在Intelx86真實模式下的區段架構的内存空间會有所重疊,这是一種不好的設計。 80286的保护模式下,16位的段寄存器中的13位(称作段选择符segment selector)是描述符表的条目(descriptor table entry)的索引;该条目包含了24位的段开始地址以及16位的段长度;段开始地址与段内偏移地址相加即为内存物理地址。16位段寄存器中的剩余3位分别是全局/局部描述符表指示位、请求特权级(request privilege level)。

1985年面世的80386及其後续處理器的32位元保護模式下,一個區段长度上限是220个粒度单位,粒度可以是1字节或4K字节,因此分段长度上限可以是4 GB,这与索引暫存器是32位元相配合。

随着32位元作業系統的推出,以及更舒適的32位元平面記憶體模式,到1990年末期幾乎淘汰了使用區段定址。然而,使用32位平面記憶體模式產生的最多只能访问4 GB地址空间的限制并没有远离日常的使用。區段允許作業系統对每個行程虛擬定址空間的限制,最大可利用64 GB的系統記憶體,但这种最終回歸到區段的尴尬,经常被引述为朝着64位元處理器发展的动机。

2003年问世的x86-64架构下,强制实现了平面記憶體模式,但保留了使用段寄存器FS或GS的64位下的分段寻址。

实模式 (Real Mode)

實模式虛擬86模式下,可访问内存固定为1 MB。对于8086处理器,可使用寄存器长度为16字节,但是拥有20字节的到内存总线地址。为了使用16字节寄存器访问20字节地址,整个内存被划分为多个区段。一个区段长65,536字节即64 KB。在实模式下,访问内存必须通过segment base address和segment offset(分别储存在任意segment寄存器和任意通用寄存器里。) 真实访问的物理地址可由以下公式得到:

物理地址=(segment base address*0x10) + segment offset [3]

注意同样的物理地址可以由多个不同对的selector,offset组成。 实模式下,任何程序都可以访问全部内存空间。没有对内存的访问权限保护。

80286保护模式

Intel 80286处理器仍然使用16位段寄存器与16位的段内偏移地址,但保护模式下支持访问224(16M)字节的内存。16位段寄存器内不再是段地址,16位段寄存器的高13位被称作段选择符(segment selector),其值是到段描述符英语segment descriptors表的索引值。段描述符中包含了24位的段开始的基地址,20位的段长度。段开始地址与段内偏移地址相加即为内存物理地址。段的长度上限为220=1M字节。

80386保护模式

Intel 80386处理器继续使用286的分段保护模式,但段描述符中包含了32位的段开始的基地址。段内偏移地址也是32位。在分段转址与物理地址之间又增加了一层分页(paging)转址。分段寻址是不能关闭的。分页可以开启或关闭(enabled or disabled),如果关闭就与286保护模式一样。如果使用分页机制,则由段开始的基地址与段内偏移地址相加得到的是线性地址(虚地址),线性地址还需要分页转址才得到内存物理地址。

386的段描述符中的段长度为20位,段长度的粒度可设为1字节或212字节。因此段长度可以为1字节-1M字节,或者为1×4K字节-1M×4K字节。段描述符的数据结构为;

  1. Byte offset inside entry.
  2. First range is the bit offset inside entry; second range is the bit offset inside byte.


386处理器增加了两个段寄存器FS、GS,这两个寄存器并无硬件绑定的用途。Windows操作系统在地址FS:0中保存了当前线程信息块。Linux中GS指向了线程局部存储

通过清除控制寄存器CR0中的最低位,可由386保护模式转为实模式。

Linux操作系统在386保护模式下把段基址设为0,段长度设为4GiB,从而模拟了平面内存模型。

段寄存器名字 描述 基地址 段长度 段描述符特权级
__KERNEL_CS 内核代码段 0 4 GiB 0
__KERNEL_DS 内核数据段 0 4 GiB 0
__USER_CS 用户代码段 0 4 GiB 3
__USER_DS 用户数据段 0 4 GiB 3

x86-64的64位模式

在x86-64体系结构64位的long mode,段寄存器CS, SS, DS, ES强制为0。段长度强制为264。形式上还有内存分段,但实际上所有内存都在唯一的一个分段中。段寄存器FS、GS可以有非0值,被操作系统用于其它用途。即硬件支持如“FS:[RAX]”这样的寄存器间接寻址。

参考文献

  1. ^ "Intel 64 and IA-32 Architectures Software Developer's Manual", Volume 3, "System Programming Guide", published in 2011, Page "Vol. 3A 3-11".
  2. ^ Intel Corporation (2004). IA-32 Intel Architecture Software Developer's Manual Volume 1: Basic Architecture页面存档备份,存于互联网档案馆
  3. ^ OsDev. OsDev Segmentation. osdev.org. [2019-08-08]. (原始内容存档于2022-05-10).