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).