long mode 分页_x86 系列 CPU 内存寻址模式总结
說明:
S16 表示 16 位段寄存器
P16 表示 16 位的普通寄存器, 立即數, 結果為 16 位的表達式等等.
P32 同上, 只是擴展到 32 位.
一. CPU 概況
1.?8086: 8 位數據線, 16 位地址線. 8 位數據線和前8位地址線合用.
2. 8088: 16位數據線, 16位地址線. 數據線和地址線完全分時合用.
3. 80186:?16位數據線, 16位地址線. 數據線和地址線完全分時合用.
4: 80188:?16位數據線, 16位地址線. 數據線和地址線完全分時合用.
5. 80286: 16位數據線, 24位地址線. 數據線和地址線是完全分開的. 轉到保護模式的過渡 CPU.
6. 80386: 32位數據線, 32位地址線. 數據線和地址線是完全分開的(其中80386SX像80286).
7. 80486: 32位數據線, 32位地址線.
8. Pentium: 64位數據線, 32位地址線.
9. Pentium Pro: 64位數據線, 36位地址線.
二. ?實模式: 分段內存
1. 支持的 CPU: ?8086 以上
2. 啟用方式: 啟動后自動進入
3. 地址長度: 20
4. 尋址能力: 1M
5. ALU寬度: 16
6. 尋址過程:
引入了 CS, DS, SS, ES 這 4 個 16 位的段寄存器. 尋址時將段寄存器左移 4 位后再加上 16 位的偏移, 既: ?(S16 << 4) + P16. 得到 20 位的地址.
省略段寄存器時, 會使用默認的段寄存器:
段
偏移
用途
CS
IP
指令地址
DS
AX, BX, SI, DI, Disp8/16
數據地址
SS
SP, BP
堆棧地址
ES
DI
串操作目的地址
三. 保護模式: 分段內存
1. 支持的 CPU: 80286 以上
2. 啟用方式: ?將 CR0 寄存器的 PE 位置 1.
3. 地址長度: 32
4. 尋址能力: 4GB
5. ALU寬度: 32
6. 尋址過程:
從 32 位的數據寬度, 尋 32 位的地址, 看起來似乎是非常簡單的一件事情. 不過由于對內存保護的加入, 這個過程其實更為復雜. 而 Intel 又選擇了兼容之前的分段內存, 且分段機制在進入保護模式后是必須的, 不能關閉. 其實大多數的操作系統(tǒng)實現的時候都選擇繞過分段機制, 只使用分頁機制來進行內存管理.
保護模式的分段機制保留了以前的段寄存器, 并且增加了兩個 FS, GS. 這些段寄存器仍然為 16 位, 但是里面保存的不再是段的基地址了, 而是一個段選擇子, 段選擇子是一個如下的結構:
struct SegSelector {
unsigned int RPL : 2;
unsigned int PI ?: 1;
unsigned int Selector : 13;
};
既, 0-1 位表示請求的權限, 共有 0 - 3 四種. 但幾乎所有的操作系統(tǒng)都只使用 2 種. 0 表示內核的, 3 表示用戶空間的. ?第 2 位決定是使用 GDT 還是 LDT, 為 0 使用 GDT, 為 1 使用 LDT. 選擇繞過分段機制的操作系統(tǒng)是不會創(chuàng)建 LDT 的, 永遠只使用 GDT. 最后高 13 位表示在 GDT/LDT 中的索引號.
GDT/LDT 是段描述符的數組, 段描述符是描述段的基址, 邊界, 屬性的一個結構, 共 64 bit, 8 個字節(jié). GDT 是全局的, 只有一個. LDT 可以有多個. Intel 設想的是, GDT 供操作系統(tǒng)內核使用, LDT 每個進程一個, 但是基本上沒有人按這種方式來使用. 段描述符的結構如下:
struct SegDesc {
unsigned short limitLow;
unsigned int baseLow : 24;
unsigned int type : 4; ?// 根據 S 字段的不用, 有不同的含義. 包含讀寫權限等等.
unsigned int S : 1; ?// 系統(tǒng)段標志, 為 0 表示系統(tǒng)段
unsigned int DPL : 2; ?// 段的權限等級. ?0 為內核 ?3 位用戶
unsigned int P : 1; ?// 段是否在內存中, 為 1 表示在內存. 當 P 為 0 時, base 和 limit 都是無效的, 操作系統(tǒng)可以用來保存自己的數據.
unsigned int limitHigh : 4;
unsigned int AVL : 1; // CPU 不使用, 軟件自己決定表示什么.
unsigned int zero : 1; // 為 0
unsigned int DB : 1; ? // 根據段的使用方式, 有不同的含義.
unsigned int G : 1; ?// 粒度, 決定 Limit 的單位. ?為 0, 單位為字節(jié). 為 1, 單位是 4KB
unsigned char baseHigh;
};
之所以這么復雜, 是因為在 80286 中, 高 16 位是沒有使用的. 新的 CPU 為了兼容這個過渡產品, base 和 limit 只能擴展到高 16 位去.
GDT/LDT 這個數組是放在內存里面的, 所以還需要記錄它們在內存中的起始位置, 為此, 又增加了 2 個寄存器: GDTR 和 LDTR. ?GDTR 是一個 48 位的寄存器, 高 32 位是一個線性地址, 低 16 位是邊界. LDTR 是一個 16 位寄存器, 里面保存的是在 GDT 中的索引號.
struct {
unsigned short limit;
unsigned int base;
}?GDTR;
這樣, 段寄存器通過查詢 GDT/LDT 表獲得段的基址后, 再加上偏移得到一個 32 位的地址. 這個地址被稱為線性地址. 如果沒有啟用下面說的分頁機制, 那么線性地址就是物理地址. 如果啟用了分頁機制, 線性地址還需要經過一次映射才能得到物理地址. 綜上, 虛擬地址到線性地址的映射關系可用如下偽代碼來描述:
if(S16 & 0x04 == 0) ?// 取段寄存器的第 3 位, 判斷是使用 GDT 還是 LDT
{ ?// 使用 GDT
gdt =??(GDTR >> 16) & 0xFFFFFFFF; ?//?取 GDTR 寄存器的高 32 位, 既 GDT 表的起始地址.
sd = gdt + S16 & 0xFFF8; ?// 根據段寄存器中的索引號找到段描述符
sbase_l = (sd >> 12) & 0x00FFFFFF; ? // 取基址的低 24 位
sbase_h = (sd >> 32) & 0xFF000000; // 取基地址高 8 位
sbase = sbase_h | sbase_l;
return sbase + P32 ?// 基址加上偏移得到最終的線性地址
}
else
{ ?// 使用 LDT
gdt = (GDTR >> 16) &?0xFFFFFFFF;
sd_ldt = gdt + LDTR & 0xFFF8;
ldt =(sd_ldt >> 12) & 0x00FFFFFF +(sd_ldt >> 32) & 0xFF000000;
sd = ldt + S16 & 0xFFF8;
sbase =(sd >> 12) & 0x00FFFFFF +(sd >> 32) & 0xFF000000;
return sbase + P32;
}
或者, 我們使用上面定義的結構來表述:
if(SegSelector(S16)->PI)
{ ?// PI 為 1 使用 LDT
SegDesc* gdt = GDTR->base;
SegDesc* ldt= &gdt[LDTR >> 3];
SegDesc* sd = &ldt[SegSelector(S16)->Selector];
void* base = sd->baseHigh << 24 + sd->baseLow;
return base + P32
}
else
{ ?// PI 為 0 使用 GDT
SegDesc* gdt = GDTR->base;
SegDesc* sd = &gdt[SegSelector(S16)->Selector];
void* base = sd->baseHigh << 24 + sd->baseLow;
return base + P32
}
同樣, 在沒有指明段寄存器的情況下, 會使用一個默認的段寄存器:
段
偏移
用途
CS
EIP
指令地址
DS
EAX, EBX, ECX, EDX, ESI, EDI, ?Disp8/16/32
數據地址
SS
ESP, EBP
堆棧地址
ES
EDI
串操作目的地址
GS
無
一般地址
FS
無
一般地址
以下是網上搜集的描述分段內存的圖表:
四. 保護模式: 分頁內存
1. 支持的 CPU: 80386 以上, PSE 需要?Pentium 以上
2. 啟用方式: 將 CR0 寄存器的 PG 位置 1
3. 地址長度: 32
4. 尋址能力: 4GB
5. ALU寬度: 32
6. 尋址過程:
分頁機制是現代操作系統(tǒng)實現內存管理的主要方式. 它將線性地址空間劃分為固定大小的頁面, 每個頁面可以被映射到物理內存或外部存儲器的虛擬內存文件中, 并且進行權限檢查. 在沒有啟用 PAE 時, 內存頁面大小可以是 4KB 或 4MB. 如果 CR4 中的 PSE 位是 0, 則只支持 4KB 的內存頁. 如果 PSE 位是 1, 則根據 PDE 中的 PS 位來決定內存頁的大小.
1# ?4KB 頁面尋址
對于 4KB 的頁面, 32 位的線性地址不再表示物理地址, 而是變成如下的含義了:
struct LineAddress {
unsigned int offset : 12;
unsigned int table ?: 10;
unsigned int directory : 10;
};
其中高 10 位表示頁目錄的下標, 中間 10 位表示頁表中的下標. ?在系統(tǒng)中, 每一個進程有一個頁目錄, 其基址為 4KB 對齊, 低 12 位為 0, 高 20 位存放在 CR3 寄存器中的高 20 位里. 因此, CR3 寄存器又被稱為頁目錄基址寄存器(PDBR). CR3 的結構為:
struct CR3 {
unsigned int nouse1 : 3;
unsigned int PWT : 1; ? // Page Write Through 標志, CR0.CD 為 1 時忽略, CR0.CD 為 0 時: PWT = 1 使用 Write-Through 的緩存類型, PWT = 0 使用 Write-Back 的緩存類型.
unsigned int PCD : 1; ? ?// Page Cache Disable 標志, CR0.CD 為 1 時忽略, CR0.CD 為 0 時: PCD = 1 表示該物理頁不能被緩存
unsigned int nouse2 : 7;
unsigned int pdt_base : 20;
};
頁目錄(Page Directory)是一個 4KB 大小的數組, 里面包含 1024 個 4 字節(jié)的頁目錄表項(PDE, Page Directory Entry). 對于 4KB 的頁面, PDE 的結構如下:
struct PDE {
unsigned int P : 1; ?// Present, 是否在物理內存中, 1 在, 0 不在
unsigned int R/W: 1; ?// Read/Write, 為 1 表示可讀寫, 為 0 表示只讀
unsigned int U/S : 1; ?// User/Supervisor 為 0 表示管理權限, 為 1 表示用戶權限
unsigned int PWT: 1; // Write-through
unsigned int PCD : 1; // Cashe-Disabled
unsigned int A : 1; ? ?// Accessed 是否被訪問過, 1 表示訪問過
unsinged int ZERO: 1; // 固定為 0
unsigned int PS : 1; ? ?// ?PageSize, 頁大小, 0 表示 4KB, 1 表示 4MB. 當 CR4 的 PSE 為 0 時, 忽略該項, 頁大小始終為 4KB.
unsigned int G : 1; ? ? // Global Page, 全局頁.
unsigned int nouse : 3; // CPU 未使用, 供系統(tǒng)程序員使用.
unsigned int base : 20; // 頁表基址的高 20 位, 低 12 位固定為 0. 所以, 頁表基址一定是按 4KB 對齊的.
};
PDE 中最重要的, 是高 20 位表示的頁表基址, 它指向的是一個 4KB 的頁表(Page Table), 頁表包含了 1024 個 4 字節(jié)的頁表表項(Page Table Entry, PTE). PTE 的結構如下:
struct PTE {
unsigned int P : 1;
unsinged int R/W : 1;
unsigned int U/S : 1;
unsigned int PWT: 1;
unsigned int PCD : 1;
unsigned int A : 1;
unsigned int D : 1; ?// Dirty 表示內存頁是否被修改過, 1 修改過 0 未修改過
unsigned int PAT : 1; ?// Page Attribute Table, 在全局屬性表中的索引
unsigned int G : 1; ?// 全局頁
unsigned int nouse : 3; // 供系統(tǒng)程序員使用
unsigned int base : 20; // 內存頁起始物理地址的高 20 位, 低 12 位固定為 0, 所以必須按 4KB 對齊.
};
根據上面的結構, 線性地址到物理地址的映射過程, 可以用如下偽代碼來表示:
LineAddress laddr = P32;
PDE* pde = CR3.pdt_base << 12;
PTE* pte = pde[laddr.direcotry].base << 12;
char* pageStart = pte[laddr.table].base << 12;
return pageStart + laddr.offset;
2# ?4MB 頁面尋址 (PSE 模式)
對于 4MB 的頁面, 不需要使用頁表, 只需要頁目錄的一層映射. 要配置 4MB 的頁面, 需要設置 CR4 中的 PSE 位. 并在 PDE 中設置 PS 位. 使用 4MB 頁面時, 線性地址的結構如下:
struct LineAddress {
unsigned int offset : 22;
unsigned int directory : 10;
};
此時的 PDE 結構如下:
struct PDE {
/* 0 ? ? ?*/unsigned int P : 1;
/* 1 ? ? ?*/unsinged int R/W : 1;
/* 2 ? ? ?*/unsigned int U/S : 1;
/* 3 ? ? ?*/unsigned int PWT: 1;
/* 4 ? ? ?*/unsigned int PCD : 1;
/* 5 ? ? ?*/unsigned int A : 1;
/* 6 ? ? ?*/unsigned int D : 1;
/* 7 ? ? ?*/unsigned int PS : 1; // 4MB 頁面該位為 1
/* 8 ? ? ?*/unsigned int G : 1;
/* ?9-11 */unsigned int nouse : 3; // 供系統(tǒng)程序員使用
/* ?12 ? ?*/unsigned int PAT : 1;
/* 13-21 */unsigned int reserved : 9; // 保留未使用, 必須為 0
/* 22-31 */unsigned int base : 10; ? // 內存頁起始物理地址的高 10 位, 低 22 位固定為 0, 所以必須按 4MB 對齊.
};
此時的尋址過程偽代碼如下:
LineAddress laddr = P32;
PDE* pde = CR3.pdt_base << 12;
char* pageStart =pde[laddr.direcotry].base << 22;
return pageStart + laddr.offset;
以下是網上搜集的描述分頁內存的圖表:
1. 內存的二級分頁結構
2. 4KB 頁面尋址過程
3. 4MB 頁面尋址過程
五. 虛擬 86 模式 (V8086, V86)
1. 支持的 CPU: ?80386 以上
2. 啟用方式: 在保護模式下, 將標志寄存器中的 VM 位置 1
3. 地址長度: 20
4. 尋址能力: 1MB
5. ALU 寬度: 16
6. 尋址過程:
虛擬 86 模式是保護模式下, 某些任務的一種工作模式. 此模式是為了能夠在保護模式下運行實模式軟件. 在虛擬 86 模式下, 軟件的工作環(huán)境和實模式類似, 使用?(S16 << 4) + P16 的方式訪問 1M 的內存, 但是得到的地址不再是物理地址了, 而是由系統(tǒng)的虛擬 86 管理程序分配的內存. 虛擬 86 模式下也可以使用內存分頁(實模式下不行), 讓沒有使用的內存空間不占用物理內存. ?虛擬 86 模式下的中斷和特殊指令的訪問也由系統(tǒng)軟件進行模擬, 不能直接訪問硬件.
在虛擬 86 模式下, 是不能直接更改標志寄存器的 VM 位的, 所以進入和退出虛擬 86 模式是通過任務切換或中斷來完成的.
以下是網上搜集的描述 V86 模式的相關圖表:
1. 保護模式和 V86 模式的切換
六. PSE-36: Page Size Extension 36
1. 支持的 CPU:?Pentium III 以上 (另說為 Pentium II)
2. 啟用方式: 開啟 PSE 的情況下, 如果 CPU 支持即可使用
3. 地址長度: 36
4. 尋址能力: 64GB
5. ALU 寬度: 32
6. 尋址過程:
在前面的分頁內存中, 處于 PSE 模式時, ?PDE 結構只使用了高 10 位作為基址. 在 PSE-36 模式里, 將使用其中的 14 位來作為基址, 這樣, 最后的地址位數將達到 36 位, 尋址能力提高到 64GB. 在 PSE 36 模式下, 4MB 頁面的 PDE 結構變?yōu)?
struct PDE {
/* 0 ? ? ?*/unsigned int P : 1;
/* 1 ? ? ?*/unsinged int R/W : 1;
/* 2 ? ? ?*/unsigned int U/S : 1;
/* 3 ? ? ?*/unsigned int PWT: 1;
/* 4 ? ? ?*/unsigned int PCD : 1;
/* 5 ? ? ?*/unsigned int A : 1;
/* 6 ? ? ?*/unsigned int D : 1;
/* 7 ? ? ?*/unsigned int PS : 1; // 4MB 頁面該位為 1
/* 8 ? ? ?*/unsigned int G : 1;
/* ?9-11 */unsigned int nouse : 3; // 供系統(tǒng)程序員使用
/* ?12 ? ?*/unsigned int PAT : 1;
/* 13-16 */unsigned int baseHigh : 4; ?// 內存起始地址的 32-35 位
/* 17-21 */unsigned int reserved : 5; ?// 保留未使用, 必須為 0
/* 22-31 */unsigned int baseLow : 10; // 內存頁起始物理地址的 22-31 位.
};
此時尋址過程偽代碼為:
LineAddress laddr = P32;
PDE* pde = CR3.pdt_base << 12;
INT64 pageStart =(INT6)pde[laddr.direcotry].baseLow << 22 + (INT64)pde[laddr.directory].baseHigh << 32;
return pageStart + laddr.offset;
對于 4KB 的頁面, 仍然和普通的分頁內存相同, 它可以表示的內存仍然只要 4GB. 所以, 在 PSE 36 模式下, 4KB 的頁面只能位于前 4GB 物理內存中. 4GB 以上的內存只能通過 PSE 方式訪問, 頁面大小只能為 4MB.
七. PAE: Physical Address Extension 物理地址擴展
1. 支持的 CPU: ?Pentium Pro 以上
2. 啟用方式: 設置 CR4 中的 PAE 位
3. 地址長度: 36
4. 尋址能力: 64GB
5. ALU 寬度: 32
6. 尋址過程:
在 PAE 模式中, 應用程序仍然為 32 位, 只能使用 4GB 的內存空間. 但是系統(tǒng)可以把不同的進程映射到 64GB 的物理內存中. 應用程序如果需要使用大于 4GB 的內存, 則需要操作系統(tǒng)的特殊支持(Windows 為 AWE, Address Windowing Extensions; ?Unix 存在多種, 比如 mmap ).
啟用 PAE 后, CR3 寄存器不再指向頁目錄基址, 而是指向一個頁目錄指針表 PDPT (Page Directory Pointer Table), 即包含 4 個頁目錄指針的表. ?頁目錄項和頁表項從原來的 4 字節(jié)變?yōu)?8 字節(jié), 占用的內存大小仍然為 4KB, 所以其中的表項從 1024 個變?yōu)?512 個. ?因此, 現在一共有 4 個頁目錄表, 頁目錄表和頁表的下標也只需要 9 位了, 于是線性地址的劃分也有了變化.
1# ?4KB 頁面尋址
在 4KB 頁面下, 線性地址被描述為:
struct LineAddress {
unsigned int offset : 12;
unsigned int table ?: 9;
unsigned int directory : 9;
unsigned int ptrindex ?: 2;
};
其中的高 2 位表示在頁目錄指針表 PDPT 內的索引, CR3 寄存器里保存了 PDPT 的地址, CR3 結構為:
struct CR3 {
unsigned int nouse : 5;
unsigned int pdpt_base : 27;
};
因此, PDPT 的地址是 32 字節(jié)對齊的, 因為 PDPT 的大小是 32 字節(jié). PDPT 的表項 PDPTE 是 64 位的, 下面是 PDPTE 的結構:
struct PDPTE {
unsigned int P : 1;
unsigned int reseved1 : 2; // 保留, 必須為 0
unsigned int PWT : 1;
unsigned int PCD : 1;
unsigned int reserved2 : 4; ?// 保留, 必須為 0
unsigned int nouse : 3;
unsigned int pdt_base : 52;
};
PDPTE 從第 12 位開始的高位是 PDT 的基地址的高位, 隨著物理地址位數的不同, 使用的位也不同, 未使用的位需保持為 0. ?PDT 的低 12 位固定為 0, 按 4KB 對齊. 比如果物理地址是 52 位, 則 PDT 的高 40 位由 PDPTE[51:12] 提供, PDPTE[63:52] 必須為 0. 當物理地址是 40 位時, PDT[39:12] = PDPTE[39:12], 物理地址 36 位時 PDT[35:12] = PDPTE[35:12].
PDE 在 PAE 模式下是 64 位了, 結構中的 PS = 0 時表示 4 KB 的頁面, 此時的 PDE 結構如下:
struct PDE_4k {
unsigned int P : 1;
unsigned int R/W : 1;
unsigned int U/S : 1;
unsigned int PWD : 1;
unsigned int PCD : 1;
unsigned int A : 1;
unsigned int nouse1 : 1; // 忽略
unsigned int PS : 1; // 4KB 頁面這里是 0
unsigned int nouse2 : 4;
unsigned int pt_base : 51; // PDT 的基址, 和 PDPTE 類似, 隨著物理地址位數不同, 該結構中有效的位數也不同, 無效的位需要為 0
unsigned int XD : 1; ?// Execution Disable: 當寄存器 IA32_EFER.NXE 置位后有效, 否則為保留必須為 0 的位. 開啟 XD 功能后, PDE.XD = 1 或 PTE.XD = 1 則該頁面是數據頁, 不可執(zhí)行.
};
PDE 中從第 12 位開始的高位表示 PT 的基地址, 隨著物理地址的位數不同, 使用的 PDE 結構中的位數也不同. PT 基地址的低 12 位固定為 0, 以 4 KB 對齊. PT 中存放的 PTE 結構也是 64 位的了:
struct PTE {
unsigned int P : 1;
unsigned int R/W : 1;
unsigned int U/S : 1;
unsigned int PWD : 1;
unsigned int PCD : 1;
unsigned int A : 1;
unsigned int D : 1;
unsigned int PAT : 1;
unsigned int G : 1;
unsigned int nouse : 3;
unsigned int page_frame : 51; // 12~62 位是 4KB 頁面的基地址了, 和 PDPTE 一樣, 隨著物理地址位數的不同, 有效位數不同.
unsigned int XD : 1;
};
在上述擴展下, 尋址過程和 Non-PAE 模式下是類似的, 只是多了 PDPT 一個層次, 線性地址到物理地址轉換的偽代碼表示如下:
LineAddress laddr = P32;
PDPTE* pdpt = CR3.pdpt_base << 5;
PDE_4k* pde = pdpt[laddr.ptrindex].pdt_base << 12;
PTE* pt ? = pde[laddr.direcotry].pt_base << 12;
char* pageStart =pt[laddr.table].page_frame << 12;
return pageStart + laddr.offset;
其中的指針類型的位數不再是 32 位, 可以認為是 64 位或物理地址的位數.
2# ?2MB 頁面尋址
在 PDE 中, PS 位是 1 的話, 將會使用 2MB 的頁面, 由于不再使用 PTE 結構, 線性地址的含義如下:
struct LineAddress {
unsigned int offset : 21;
unsigned int directory : 9;
unsigned int ptrindex ?: 2;
};
CR3 以及 PDPTE 的結構都和 4KB 模式下相同, PDE 的結構有些區(qū)別:
struct PDE_2M {
unsigned int P : 1;
unsigned int R/W : 1;
unsigned int U/S ?: 1;
unsigned int PWT : 1;
unsigned int PCD : 1;
unsigned int A : 1;
unsigned int D : 1;
unsigned int PS : 1 // 2M 頁面該位是 1
unsigned int G : 1;
unsigned int nouse1 : 3;
unsigned int PAT : 1;
unsigned int reserved : 8; // 保留必須為 0
unsigned int frame_base : 43; // 21~63, 隨物理地址位數不同有效位不同, 無效的位必須為 0
unsigned int XD : 1;
};
其中頁面地址的低 21 位固定為 0, 基址按 2MB 對齊, 高位由 PDE_2M 的高位提供. 此時尋址過程如下:
LineAddress laddr = P32;
PDPTE* pdpt = CR3.pdpt_base << 5;
PDE_2M* pde = pdpt[laddr.ptrindex].pdt_base << 12;
char* pageStart =pde[laddr.directory].frame_base << 21;
return pageStart + laddr.offset;
附圖:
1. PAE 模式下 4KB 頁面的尋址
2. PAE 模式下 2MB 頁面的尋址
八. 長模式 (long-mode, IA-32e 模式)
1. 支持的 CPU: ?x86-64 的 CPU
2. 啟用方式:
同時滿足以下條件:
(1). 開啟保護模式 ?CR0.PE = 1
(2). 開啟分頁機制 ?CR0.PG = 1
(3). 開啟 PAE ? ? ?CR4.PAE = 1
(4). IA32_EFER.LME = 1 ?(Long Mode Enable)
(5). IA32_EFER.LMA = 1 ?(Long Mode Active)
3. 地址長度: 48
4. 尋址能力: 256 TB
5. ALU 寬度: 64
6. 尋址過程:
x86-64 體系, 也被稱為 x64 體系, 還被叫做 AMD 64 和 Intel 64 體系. 他們是 x86 體系向 64 位的擴展, 有別于純 64 位架構的 IA64 體系. x64 體系兼容 x86 的運行模式, 并增加一種新的長模式. x64 的運行模式如下:
x64 體系
子模式
資源
long-mode (IA-32e)
64-bit mode
64 位執(zhí)行環(huán)境
compatibility mode
內核為 64 位, 應用為 32 位的 legacy mode
legacy mode
protected mode
32 位
real mode
16 位
在長模式下, 內核只能為 64 位, 應用可以為 64 位或 32 位. 兼容模式(compatibility mode)和保護模式基本相同.
在 64 位模式下, 寄存器被擴展為 64 位, 默認的地址大小也是 64 位(可以使用 67H 前綴來使用 32 位地址, ?但是不能使用 16 位地址), 并增加了 RIP 相對尋址方式. 兼容模式下代碼段描述符中的 D 標志位決定了默認的地址大小: D = 0 默認為16位地址, D = 1 默認為32位地址, 可通過 67H 前綴來改變默認值.
1. 分段機制
長模式下的分段機制被進一步的弱化, 但是仍然被保留下來.
在 64 位模式下, 六個段寄存器仍然為 16 位, ?其含義和保護模式下相同, 包含 Index, TI, RPL.?除 CS 寄存器外,其余寄存器允許加載 Null selector(SS 只能在 Ring 0/1/2 下加載 Null selector).
GDTR/LDTR 的 base 擴展為 64 位, 所以 GDTR/LDTR 是 80 位的寄存器了:
struct {
unsigned short limit;
unsigned int64 base;
}?GDTR;
段描述符仍然為 8 字節(jié) 64 位, 但是里面的大多數字段都已經無效了. 因為在 64 位模式下, ?只有 FS 和 GS 可以使用非 0 的段基址, 其余的段的基址都被固定為 0, 長度被固定為 0XFFFFFFFF. 對于 FS 和 GS 的段 base 值, 新增了兩個 MSR 寄存器來表示, 分別是 IA32_FS_BASE, IA32_GS_BASE . 代碼段的描述符格式如下:
struct CodeSegDesc {
0 - 15: unsigned short limitLow; ? ? ? ? // 無效
16-39: unsigned int baseLow : 24; ? ? // 無效
40: unsigned int A : 1; ? ? ? ? ? ? ? ? // 無效
41: unsigned int R : 1; ? ? ? ? ? ? ? ? // 無效
42: unsigned int C : 1;
43: unsigned int C/D: 1;? ? ? ? ? ? ? // 固定為 1
44: unsigned int S : 1; ? ? ? ? ? ? ? ?// 固定為 1
45-46: unsigned int DPL : 2;
47: unsigned int P : 1;
48-51: unsigned int limitHigh : 4; ? ? ?// 無效
52: unsigned int AVL : 1; ? ? ? ? ? ?// 無效
53: unsigned int L : 1;
54: unsigned int D : 1;
55: unsigned int G : 1; ? ? ? ? ? ? ? // 無效
56-63: unsigned char baseHigh; ? ? ? // 無效
};
從上面可見, 64位模式下段描述符只有 C, DPL, P, L, D 五個可用的標志:
C 代碼一致性, 影響權限的檢查
DPL 標識段的權限
P 段是否在內存中
L 用于長模式下的子模式選擇, L=1 表示 64 位模式, L=0 表示兼容模式.
D 用于標識默認操作數的大小
數據段的描述符格式如下:
struct DataSegDesc {
0 - 15: unsigned short limitLow; ? ? ? ? // 無效
16-39: unsigned int baseLow : 24; ? ? // 無效
40: unsigned int A : 1; ? ? ? ? ? ? ? ? // 無效
41: unsigned int W : 1;
42: unsigned int E : 1; ? ? ? ? ? ? ? ?// 無效
43: unsigned int C/D: 1;? ? ? ? ? ? ? // 固定為 0
44: unsigned int S : 1; ? ? ? ? ? ? ? ?// 固定為 1
45-46: unsigned int DPL : 2;
47: unsigned int P : 1;
48-51: unsigned int limitHigh : 4; ? ? ?// 無效
52: unsigned int AVL : 1; ? ? ? ? ? ?// 無效
53: unsigned int L : 1; ? ? ? ? ? ? ? ?// 無效
54: unsigned int D : 1; ? ? ? ? ? ? ? // 無效
55: unsigned int G : 1; ? ? ? ? ? ? ? // 無效
56-63: unsigned char baseHigh; ? ? ? // 無效
};
其中, 只有 P 和 DPL 標志有效. W 標志只有作為堆棧段時要求必須為 1.
由于段的基址被強制為 0, 所以虛擬地址和線性地址是等價的, 這個地址進行分頁轉換后成為物理地址.
2. 分頁
Long Mode 下, 分頁是必須的. 而且只有一種模式, 使用 4 層映射, 在 PAE 的上面又增加了一層. 頁面的大小可以是 4K, 2M, 1G. 線性地址長度為 64 位, 目前使用的最高地址為 48 位. 對 4KB 的頁面, 線性地址結構如下:
struct LineAddress {
0-11: unsigned int offset : 12; ? ? ?// 偏移
12-20: unsigned int pte_index : 9;
21-29: unsigned int pde_index : 9;
30-38: unsigned int pdpte_index : 9;
39-47: unsigned int pml4te_index : 9;
48-63: unsigned int sign : 16; ? ? ? ?// 符號擴展位
};
其中的 48-63 位是符號擴展位, 要么全 0, 要么全 1, 必須與第 47 位相同. 這種地址被稱為規(guī)范化地址(canonical address), 是為了方便以后將 48 位的地址擴展到更高位數時無需進行修改. 4 個 9 位的 index 分別是 4 種表結構的下標, 這些表結構的元素都是 8 字節(jié) 64 位的, 由于索引為 9 位, 所以這些表結構的大小都是 4KB. ?第一個索引 pml4te_index 用于索引 PML4T(Page Map Level-4 Table, 表元素稱為 PML4E), ?PML4T 的基址由 CR3 寄存器提供. CR3 被擴展為 64 位, 在不支持 PCIDE 功能時, CR3 的結構為:
struct NormalCR3 {
0- 2: unsigned int no_use1 : 3;
3: unsigned int PWT : 1; ? ? ?// Page-level Write-Through
4: unsigned int PCD ?: 1; ? ? ?//?Page-level Cache Disable
5-11: ?unsigned int no_use2: 7;
12-47: ?unsigned int pml4t_base : ?36;
48-63: ?unsigned int receved : 16; // 保留為 0
};
PML4T 的基地址低 12 設置為 0, 按 4 KB 對齊. 高位從 CR3 的 12 位開始. 隨著物理地址長度不同, 使用的位數也不同, 沒有使用的位數則保留為 0.
啟用 PCID (CR4.PCIDE = 1, PCID = Process Context ID, 僅 Intel64 支持, AMD64 不支持) 后, CR3 的低 12 位表示 PCID 值, 第 63 位控制 CR3 切換時緩存的處理. 詳情從略.
根據 CR3 和 pml4t_index 可以尋到 PML4E 結構, ?該結構描述如下:
struct PML4E {
0: unsigned int P : 1;
1: unsigned int R/W : 1;
2: unsigned int U/S ?: 1;
3: unsigned int PWT : 1;
4: unsigned int PCD : 1;
5: unsigned int A : 1;
6: unsigned int no_use1 : 1;
7: unsigned int receved : 1; // ?PS 位 必須為 0
8-11: ?unsigned int no_use2 : 4;
12-47: ?unsigned int pdpt_base : 36;
48-62: ?unsigned int receved : 15; // 保留為 0, 隨物理地址大小擴展
63: ?unsigned int XD : 1; ?// Execution Disable
};
其中的 pdpt_base 和 pml4t_base 一樣, 第 12 位為 0, 高位可向保留位擴展, 以后的結構也都類似. XD 位和 PAE 模式中的 XD 位含義相同. 通過 PML4E 中的 pdpt_base 以及線性地址中的 pdpte_index 可以尋址到 PDPTE 結構, PDPTE 結構將控制 1G 頁面的轉換, 所以第 7 位 PS 位有效, 當 PS = 1 時直接通過 PDPTE 結構轉換 1G 的頁面. 此時 PDPTE 結構如下:
struct PDPTE_1G {
0: unsigned int P : 1;
1: unsigned int R/W : 1;
2: unsigned int U/S ?: 1;
3: unsigned int PWT : 1;
4: unsigned int PCD : 1;
5: unsigned int A : 1;
6: unsigned int D : 1;
7: unsigned int PS : 1; // ?PS 位, 為 1
8: unsigned int G : 1;
9-11: ?unsigned int no_use1 : 3;
12: unsigned int PAT : 1;
13-29: ?unsigned int receved : 17; // 保留, 必須為 0
30-47: ?unsigned int page_base : 18;
48-62: ?unsigned int receved : 15;
63: ?unsigned int XD : 1; ?// Execution Disable
};
其中的 page_base 的低 30 位為 0, 按 1GB 對齊, 高位可向保留位擴展. 線性地址中的 pde_index 和 pte_index 也用于表示偏移, 一個是 30 位的偏移. 由基址 + 偏移得到物理地址.
當 PS = 0 是, 頁面是 4K 或 2M 的頁面, PDPTE 需要提供 PDT 的基址, 此時的 PDPTE 結構如下:
struct PDPTE {
0: unsigned int P : 1;
1: unsigned int R/W : 1;
2: unsigned int U/S ?: 1;
3: unsigned int PWT : 1;
4: unsigned int PCD : 1;
5: unsigned int A : 1;
6: unsigned int no_use1 : 1;
7: unsigned int PS : 1; // ?PS 位, 為 0
8-11: ?unsigned int no_use2 : 4;
12-47: ?unsigned int pdt_base : 36;
48-62: ?unsigned int receved : 15; // 保留為 0, 隨物理地址大小擴展
63: ?unsigned int XD : 1; ?// Execution Disable
};
由其中的 pdt_base 和線性地址中的 pde_index 可尋找到 PDE 結構, PDE 中的 PS 位決定是 4KB 頁面還是 2MB 頁面, 當 PS = 1 時頁面為 2M, 此時 PDE 結構如下:
struct PDE_2M {
0: unsigned int P : 1;
1: unsigned int R/W : 1;
2: unsigned int U/S ?: 1;
3: unsigned int PWT : 1;
4: unsigned int PCD : 1;
5: unsigned int A : 1;
6: unsigned int D : 1;
7: unsigned int PS : 1; // ?PS 位, 為 1
8: unsigned int G : 1;
9-11: ?unsigned int no_use1 : 3;
12: unsigned int PAT : 1;
13-20: ?unsigned int receved : 8; // 保留, 必須為 0
21-47: ?unsigned int page_base : 27;
48-62: ?unsigned int receved : 15;
63: ?unsigned int XD : 1; ?// Execution Disable
};
page_base 的低 21 位為 0, 按 2M 對齊, 線性地址 pte_index 用于表示偏移, 一共 21 位偏移, 由基址 + 偏移得到物理地址.
PDE.PS = 0 時, 頁面為 4K, 繼續(xù)尋找 PTE 結構, 此時的 PDE 為:
struct PDE {
0: unsigned int P : 1;
1: unsigned int R/W : 1;
2: unsigned int U/S ?: 1;
3: unsigned int PWT : 1;
4: unsigned int PCD : 1;
5: unsigned int A : 1;
6: unsigned int no_use1 : 1;
7: unsigned int PS : 1; // ?PS 位, 為 0
8-11: ?unsigned int no_use2 : 4;
12-47: ?unsigned int pt_base : 36;
48-62: ?unsigned int receved : 15; // 保留為 0, 隨物理地址大小擴展
63: ?unsigned int XD : 1; ?// Execution Disable
};
由 pt_base + pte_index 最終得到 PTE 結構, 此時的 PTE 結構與 PAE 模式下是完全一致的:
struct PTE {
0: unsigned int P : 1;
1: unsigned int R/W : 1;
2: unsigned int U/S ?: 1;
3: unsigned int PWT : 1;
4: unsigned int PCD : 1;
5: unsigned int A : 1;
6: unsigned int D : 1;
7: unsigned int PAT : 1; // ?PS 位變?yōu)?PAT 標志
8: unsigned int G : 1;
9-11: ?unsigned int no_use1 : 3;
12-47: ?unsigned int page_base : 36;
48-62: ?unsigned int receved : 15;
63: ?unsigned int XD : 1; ?// Execution Disable
};
最終由 PTE 的 page_base 加上線性地址的 offset 得到物理地址, 漫長的尋址過程終于畫上了句號.
以下是網上搜集的描述長模式下尋址相關的圖表:
1. 線性地址到物理地址轉換, 灰色路線是 2M 頁面, 深灰色路線是 1G 頁面, 黑色路線是 4K 頁面的轉換.
總結
以上是生活随笔為你收集整理的long mode 分页_x86 系列 CPU 内存寻址模式总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: seo自动发外链_一套节约成本全网营销方
- 下一篇: 计算机主机接线视频教程,电脑主板跳线怎么