x86 記憶體組織是在暫存器寬度、位址匯流排寬度、保護機制與多工處理的限制逐步變化下發展而來。實體記憶體仍然是以位元組定址的空間:完成轉換後,處理器會在記憶體介面上輸出實體位址。然而,抵達該位址的路徑已經大幅改變。
x86 指令首先計算一個有效位址(EA),也稱為偏移量。在支援可用分段的模式中,選取的區段提供基底,從而產生線性位址。啟用分頁後,頁表會將線性位址轉換為實體位址。因此,階段如下:
實位址模式沒有分頁,因此區段運算會直接產生實體位址。現代 64 位元系統仍保留相同的概念順序,但長模式會停用用於位址產生的一般分段,並要求分頁。
使用實體位址進行程式設計 #
Intel 8080 和 8085 等早期處理器將實體位址空間直接公開給指令。它們的指令長度最多為三個位元組:操作碼佔用一個位元組,最多剩下兩個位元組可用於立即運算元。由於這些處理器具有 16 條位址線,兩位元組的立即值可以表示任意實體位址。
組合語言可以透過兩種方式參照記憶體位置:
- 將已知位址編碼在指令中。 組譯器將標籤解析為目標位址,或由程式設計師提供數值位址。例如,
SHLD 0x0055會將HL暫存器對儲存至實體位址0x0055,而JMP foo會將控制權轉移至分配給foo的位址。標籤讓原始碼更容易維護,但產生的指令仍然包含固定位址。 - 解參照暫存器中保存的位址。 8080 和 8085 的通用暫存器為 8 位元,而位址為 16 位元。因此,暫存器對
BC、DE和HL用來保存位址。STAX B與STAX D分別透過BC和DE儲存A;MOV M,A則透過HL儲存。
直接模型很簡單,但固定位址會使程式碼綁定到其載入位置。重定位需要修改每個嵌入的位址,而獨立載入的程式必須協調各自的實體位址範圍。
8086/8088:分段 v1 及其原因 #
8086 和 8088 具有 20 個位址接腳,可以定址 1 MiB,但其通用暫存器和區段暫存器寬度為 16 位元。它們既沒有虛擬記憶體,也沒有分頁。記憶體參照仍然會到達實體記憶體,但指令編碼不再將 20 位元實體位址視為一般定址形式。
編碼 20 位元立即值會佔用完整的三個位元組,並浪費四個位元。它也會保留固定載入位址所造成的不靈活性。將位址載入暫存器可以表示動態計算的位置,因此,專用的 20 位元立即值形式只會增加硬體與編碼複雜度,卻不會涵蓋新的使用情境。後續部分 x86 指令保留了直接的偏移量形式(moffs),但它仍會結合區段來解釋;它不是未分段的實體位址運算元。
分段 v1 #
8086 的解決方案是將 16 位元區段值與 16 位元偏移量組合:
- 存在四個區段暫存器:
CS、SS、DS和ES。 - 每個記憶體參照都有預設區段。指令擷取使用
CS;一般資料參照使用DS;有效位址使用BP的參照,以及堆疊操作,使用SS。字串指令使用指定的來源與目的地區段。對許多參照而言,區段覆寫前綴可以選取另一個允許的區段。 - 指令會從選用的基底暫存器(
BX或BP)、選用的索引暫存器(SI或DI)以及選用的位移計算偏移量。合法的 16 位元定址組合受編碼限制,但結果運算是base + index + displacement。至少一個編碼元件會識別記憶體運算元。整數指令通常最多編碼一個明確記憶體運算元;字串指令是重要例外,因為它們包含隱含的來源與目的地存取。
例如,MOV AX, [4] 使用預設的 DS 區段。組譯器可以編碼如 MOV AX, CS:[4] 的覆寫形式;在常見的 moffs 編碼中,後者會額外帶有 0x2E 前綴。組譯器的拼寫方式可能不同,但前綴具有相同的架構意義。
執行單元計算 EA。接著,匯流排介面單元將選取的區段值左移四個位元,並加上 EA:
segment 4 5 0 0
offset F C 3 2
-------------------
sum 5 4 C 3 2
physical address = (segment << 4) + effective address8086 只公開 20 個位址位元。如果加法產生超出位元 19 的進位,匯流排位址會以 1 MiB 為模回繞;因此,在原始 8086 上,熟悉的 FFFF:FFFF 計算不是用來存取 1 MiB 以上唯一位址的方法。因此,即使偏移量看起來像絕對位址,以 [BX] 或 [40] 寫出的參照也永遠不會繞過區段運算。在實模式的描述中,「實體定址」表示分頁不會將最終位址虛擬化;它不表示指令可以在沒有區段的情況下編碼任意實體位址。
區段值是段落編號。它們提供 4,096 個可能的基底,每個基底相隔 16 個位元組,而每個偏移量可以涵蓋 64 KiB 視窗。區段可以相鄰、不相交、部分重疊或完全相同。因此,一個實體位置可能有多種 segment:offset 表示法。
結論:8086/8088 記憶體分段 #
分段主要用來補償暫存器寬度小於位址匯流排的情況。隱含區段也縮短了許多指令,因為只需編碼偏移量。Intel 曾考慮將區段值左移八個位元而不是四個位元,這會產生 24 位元位址空間與 256 位元組區段對齊。四位元移位在維持 1 MiB 設計目標的同時,保留了更細的配置粒度。
這種安排使軟體複雜化,因為指標不再是單一的扁平數值。當通用暫存器達到位址空間的寬度後,這項成本越來越難以合理化,但相容性要求使分段繼續保留在架構中。
80286:分段 v2 #
80286 保留 16 位元通用暫存器,同時將實體位址匯流排擴展至 24 位元,支援最多 16 MiB。它引入了實位址模式與保護模式。重設時,它會從實位址模式開始。
80286 的 MSW.PE 位元會啟用保護模式。與後續 x86 處理器不同,80286 不能只透過清除此位元從保護模式返回實位址模式;軟體通常會重設處理器以返回。這項限制對早期作業系統很重要,但不會改變任一模式中的位址轉換規則。
實位址模式 #
實位址模式保留與 8086 相容的 segment:offset 定址。區段暫存器包含 16 位元區段值,處理器將區段左移四位元,再加上 16 位元偏移量來形成位址。不同於原始 8086 封裝,80286 公開了位址線 A20。啟用 A20 後,FFFF:FFFF 附近的參照可以到達 1 MiB 以上的高記憶體區域;相容性硬體可以遮罩 A20,以重現 20 位元回繞。因此,為 8086 撰寫的程式可以在不修改原始碼的情況下執行,但仍受其他硬體假設的限制。
80286 也使新指令能在實位址模式中使用。這些指令不會將實位址分段轉換為保護分段,也不會加入分頁。
保護虛擬位址模式 #
80286 保護模式增加了受保護的可變大小區段,但尚未實作頁表虛擬記憶體。因此,「保護虛擬位址模式」指的是保護分段,而不是與 80386 及後續處理器相關的需求分頁虛擬記憶體模型。
兩個常駐記憶體的描述元表定義了區段:
- 全域描述元表(GDT)是系統範圍的。
- 區域描述元表(LDT)可以描述與某個工作相關聯的區段。
每個區段暫存器的可見部分現在是 16 位元的選取子,而不是移位後的基底。其格式如下:
index選取一個八位元組描述元。TI清除時選取 GDT,設定時選取目前的 LDT。RPL是請求者特權層級,並參與保護檢查。
載入選取子時,處理器會驗證它,並將選取的描述元載入與區段暫存器相關聯、在架構上不可見的描述元快取。快取包含後續參照所需的基底、界限與存取權限。讀取區段暫存器只會公開其選取子;軟體不能直接讀取或寫入其快取欄位。快取避免每次記憶體參照都存取描述元表。
全域描述元表(GDT) #
GDTR 定位 GDT。在 80286 上,它包含 24 位元實體基底與 16 位元界限:
界限是表格大小(以位元組計)減一,因此可編碼的最大大小為 65,536 位元組。由於每個描述元為八個位元組,有效的完整表會在描述元邊界結束。特權 LGDT 與 SGDT 會載入及儲存此暫存器;一般資料指令無法定址 GDTR。
GDT 是描述元陣列。S = 1 的描述元是程式碼或資料區段描述元。S = 0 的描述元是系統描述元,例如 LDT 或工作狀態描述元。80286 區段描述元儲存 24 位元基底、以位元組為粒度的 16 位元界限,以及一個存取位元組:
圖示摘要的是欄位配置,而不是最後四位元組群組內的位元組順序:描述元的低 16 位元是界限,接著是基底位元 15 至 0。區段現在是一個具有任意 24 位元基底與可變界限的連續範圍;不再使用 16 位元組移位。由於 80286 沒有頁面轉換,描述元基底識別的是實體記憶體。
GDT 的項目 0 必須是全零的空描述元。當 TI 選取 GDT 時,它會使選取子索引 0 無效。
區域描述元表(LDT) #
LDT 與 GDT 具有相同的描述元配置,但其第一個項目可以有效。記憶體中可以存在多個 LDT,而機器一次只使用一個 GDT。LDTR 的可見選取子識別 GDT 中一個 S = 0 的 LDT 描述元;其不可見快取提供該 LDT 的基底與界限。LLDT 和 SLDT 會載入及儲存可見的 LDTR 選取子。
80286 硬體工作機制可以在切換工作時重新載入 LDTR,使 LDT 成為工作專屬。作業系統通常避免使用硬體工作切換,因為軟體內容切換更具彈性且可攜性更高。在 IA-32e 模式中,硬體工作切換不可用,但工作狀態區段仍可用於特權堆疊與中斷堆疊管理等其他用途。
區段暫存器 #
在任一時刻,選取子可以參照 GDT,也可以參照由 LDTR 選取的 LDT。從架構角度看,CS、SS、DS 和 ES 各由一個可見的 16 位元選取子與一個不可存取的描述元快取組成。透過 MOV、POP、LDS、遠控制轉移或其他相關指令載入其中一個暫存器時,處理器會從以下位置選取:
快取不是第二種軟體可見的暫存器配置;圖示表示選取子成功載入後由處理器保留的值。後續記憶體參照會使用這些快取值,而不是重新讀取描述元表。
TI為零時使用 GDT;或TI為一時使用目前的 LDT。
接著,處理器會檢查型別、存在性、界限與特權規則,然後安裝描述元快取。遠 JMP、CALL、返回、中斷或閘門可以載入 CS;一般 MOV 則不能。
索引為零且 TI = 0 的選取子是空選取子。將空選取子載入 CS 或 SS 會引發錯誤。DS 和 ES 可以保存空選取子,但任何嘗試使用該暫存器進行記憶體參照的操作都會引發錯誤。後續處理器也對 FS 和 GS 套用相同且有用的無效資料區段慣例。
結論:沒有分頁的 80286 分段 #
80286 增加了以描述元為基礎的保護、特權檢查與可變區段大小。它也具有中斷描述元表與 IDTR,但中斷分派不會改變一般資料位址路徑。由於不存在頁表階段,保護模式記憶體仍然是分段的實體記憶體。
自 80386 起:分段與虛擬分頁 #
80386 引入了 32 位元通用暫存器、32 位元區段基底以及頁表虛擬記憶體。其模型也適用於後續處理器上的 32 位元保護模式軟體。它定義了三種相關的執行模式:
- 實位址模式仍與 8086 相容。
- 保護模式擴充 80286 的保護分段,並可啟用分頁。
- 虛擬 8086 模式將實模式風格的程式以保護模式工作執行。分頁和保護屬於周圍的保護模式環境,因此虛擬 8086 工作無法逃逸其他程序的映射。
更新後的保護模式 #
在 32 位元保護模式中,原始 80386 的通用暫存器、偏移量、線性位址空間、區段基底以及實體位址匯流排都具有 32 位元寬度。描述元項目將其基底欄位擴展為 32 位元,並加入粒度與預設運算元/位址大小等旗標。80286 中保留的描述元高位元組取得了這些意義。
分段仍然完整運作,包括非零基底、界限、向下擴展資料區段及特權檢查。然而,32 位元作業系統可以將其程式碼區段與資料區段的基底設為零,並將界限設為 4 GiB。這種扁平分段使偏移量等於線性位址,並讓分頁執行程序隔離。區段選取子本身並不是零:核心通常會為有效的扁平描述元載入如 0x08 或 0x10 的非空選取子。
80386 也加入 SIB(scale-index-base)編碼。它將 EA 的形成擴展為:
effective address = base + index × scale + displacement
scale = 1, 2, 4, or 8ModR/M 和選用的 SIB 位元組選擇參與運算的暫存器與位移大小。對於元素大小為 2、4 或 8 位元組的陣列,縮放很有用,但它只改變 EA 的形成,不改變分段或分頁模型。
分頁 #
設定 CR0.PG 會在保護模式中啟用分頁。之後,位址形成分為四個步驟:
- 指令根據適用的
ModR/M、SIB、暫存器與位移計算 EA。 - 所選區段的快取描述元驗證偏移量並提供基底。結果是 32 位元線性位址。
- 分頁使用該線性位址,從
CR3中的實體位址開始遍歷頁表。 - 所選頁項目提供實體頁框基底;加上頁面偏移量後產生實體位址。
處理器會在分段位址階段之前或期間檢查描述元權限,並在轉換期間檢查頁面權限。不存在的項目或被禁止的存取會產生頁面錯誤。TLB(轉譯後備緩衝區)會快取成功的線性位址到實體位址轉換,因此作業系統在變更作用中的頁表後,必須使快取的轉換失效,或以其他方式同步它們。
舊式 32 位元分頁 #
原始 80386 方案使用 4 KiB 頁面與兩層結構。CR3 識別目前位址空間的頁目錄;系統並不存在一個供全系統使用的頁目錄。內容切換可以載入另一個 CR3,而作業系統可以在多個目錄之間共用選定的核心映射。
頁目錄與每個頁表都佔用一個 4 KiB 頁面。每個表包含 1,024 個四位元組項目:
頁目錄項目(PDE)指向頁表;頁表項目(PTE)指向一個 4 KiB 實體頁框。它們的低位元攜帶相關但依層級而異的旗標,包括 present、read/write、user/supervisor、accessed 以及快取控制屬性。PTE 也有 dirty 狀態;啟用頁面大小擴充時,PDE 可以直接表示大型頁面。
32 位元線性位址分割如下:
directory 選取 PDE,table 選取其頁表中的 PTE,而 offset 選取所得 4 KiB 頁框內的位元組。啟用 CR4.PSE 後,設定頁面大小位元的 PDE 可以直接映射 4 MiB 頁面,取代頁表階段。
實體位址擴充(PAE) #
PAE 保留 32 位元線性位址,但變更分頁階層,並使用 64 位元分頁項目。它由 CR4.PAE 啟用。早期 PAE 實作將實體位址擴展至 36 位元;後續處理器的架構實體位址寬度取決於實作。
在 PAE 分頁中,CR3 指向包含四個項目的頁目錄指標表(PDPT)。每個選取的頁目錄包含 512 個項目,每個選取的頁表也包含 512 個項目。32 位元線性位址分割如下:
頁目錄指標選取 PDPTE,後面兩個欄位選取 PDE 與 PTE。設定頁面大小位元的 PAE PDE 會映射 2 MiB 頁面。PAE 也提供 EFER.NXE 啟用時 no-execute 權限位元所需的項目格式。沒有 NXE 時,高位元是保留位元,而不是可執行策略控制項。
IA-32e 與長模式分頁 #
IA-32e 模式是具備 64 位元能力的執行環境,包含 64 位元模式與相容模式。兩者都使用 IA-32e 分頁,而且分頁是強制的:IA-32e 模式作用中時不能清除 CR0.PG。
要進入 IA-32e 模式,特權軟體會建構有效的 PAE 格式頁表,設定 CR4.PAE,設定 EFER.LME,然後啟用 CR0.PG。在設定 LME 的情況下啟用分頁會鎖存 EFER.LMA;這個唯讀指示表示 IA-32e 模式已作用中。跳轉至 L 位元已設定的程式碼區段會進入 64 位元模式;L = 0 且具有適當預設大小的程式碼區段會進入相容模式。轉換程式碼必須在啟用分頁前建立映射,因為下一條指令的擷取會透過新的階層進行轉換。
四層分頁使用 PML4、頁目錄指標表、頁目錄及頁表。每個表包含 512 個八位元組項目,因此每個索引為九個位元:
四層實作使用 48 位元虛擬位址。位元 63 至 48 必須複製位元 47,使位址成為規範位址(canonical)。非規範位址會在頁表遍歷前引發錯誤。支援五層分頁(CR4.LA57)的處理器會增加九位元的 PML5 索引,支援 57 位元虛擬位址,並要求位元 63 至 57 複製位元 56。實體位址寬度仍取決於處理器,並由 CPUID 回報;不能從線性位址寬度推斷。
IA-32e 頁表可以在 PTE 層級映射 4 KiB 頁面,在設定頁面大小位元的 PDE 映射 2 MiB 頁面,並在處理器宣告支援時,在設定該位元的 PDPTE 映射 1 GiB 頁面。頁面項目包含實體頁框位址位元,以及權限、記憶體型別、accessed、適用時的 dirty、global 和 execute-disable 控制項。execute-disable 位元只有在啟用 EFER.NXE 時才會生效。
IA-32e 模式中的分段 #
長模式會改變分段,但不會移除區段選取子或描述元:
- 在64 位元模式中,
CS、DS、ES和SS在一般位址產生中提供零基底。一般參照不使用它們的界限。FS和GS保留有用的基底,這些基底由專用模型特定暫存器提供;作業系統使用它們存放執行緒區域或處理器區域資料。在加上FS或GS基底後,仍會套用規範位址檢查與分頁。 - 在相容模式中,16 位元與 32 位元保護模式應用程式保留其保護模式分段語意,同時使用 IA-32e 分頁。
- IA-32e 模式作用中時,虛擬 8086 模式與硬體工作切換不可用。特權轉換與中斷堆疊管理仍然需要工作狀態區段。
因此,長模式核心並不是只要載入零值區段暫存器就能取得扁平位址空間。它依據 64 位元模式規則取得一般的零基底,在必要處選取有效描述元,並依賴規範位址規則與分頁來實作位址空間隔離和權限。
跨模式的位址轉換 #
歷史演進解釋了今日仍可見的各個層次:
| 處理器與模式 | 有效位址與分段 | 分頁 | 結果 |
|---|---|---|---|
| 8086/8088 實模式 | 16 位元 EA 加上 segment << 4 | 無 | 20 位元實體位址 |
| 80286 實模式 | 與 8086 相容的區段運算 | 無 | 實位址實體存取 |
| 80286 保護模式 | 選取子解析為 24 位元基底描述元 | 無 | 受保護的分段實體存取 |
| 80386 保護模式 | 32 位元描述元基底加 EA | 可選的舊式分頁 | 線性位址轉換為實體記憶體 |
| 32 位元 PAE 模式 | 32 位元保護分段 | 必要的 PAE 階層 | 更寬的實體位址轉換 |
| IA-32e 64 位元模式 | 除 FS 和 GS 外皆為零基底 | 必要的四層或五層分頁 | 規範線性位址轉換為實體記憶體 |
因此,位址形成並不是泛用的「x86 記憶體模型」。8086 使用分段,將 16 位元偏移量擴展為 20 位元實體位址。80286 將區段值轉換為受保護的描述元選取子。80386 加入線性位址分頁。PAE 改變分頁項目的寬度與階層,而 IA-32e 強制要求分頁,並在 64 位元模式中減少一般分段的作用。
描述元細節請參閱 x86 保護模式與特權,而編碼有效位址的指令欄位請參閱 x86 指令編碼。
評論
在 GitHub 上閱讀或評論(發表評論需要登入)