今天的任務是完成內核搬遷的最後一塊拼圖:將 GDT (Global Descriptor Table) 移至虛擬地址高位。同時,我們實現了物理記憶體管理員(Physical Memory Manager),利用 BIOS 提供的記憶體映射資訊,建立位圖(Bitmap)來追蹤系統中每一頁物理記憶體的使用狀態。
雖然內核代碼已經在 3GB 以上運行,但如果 GDT 仍留在低位物理地址,我們在邏輯上就不算完成了完全的隔離。
GDT 的指針(GDTR)儲存的是線性地址。在開啟分頁後,為了確保內核在任何情況下都能正確訪問段描述符,我們將 GDT 搬遷到內核數據段中,並更新 GDTR 指向高位虛擬地址。
重新加載 GDT 後,必須立即刷新 cs, ds, es, fs, gs 與 ss。特別是 cs,需要透過一次遠跳轉(Far Jump)來更新。
void init_gdt() {
gdt_ptr.limit = (sizeof(gdt_entry_t) * 3) - 1;
gdt_ptr.base = (u32)&gdt;
// 1. Null segment
gdt_set_gate(0, 0, 0, 0, 0);
// 2. Code segment: Base=0, Limit=4GB, Type=Code/Exec/Read, Ring 0
gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);
// 3. Data segment: Base=0, Limit=4GB, Type=Data/Read/Write, Ring 0
gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);
// 重新加載 GDTR 並刷新段暫存器
gdt_flush((u32)&gdt_ptr);
}
void gdt_flush(u32 gdt_ptr_addr) {
__asm__ __volatile__ (
"lgdt (%0) \\n\\t"
"mov $0x10, %%ax \\n\\t"
"mov %%ax, %%ds \\n\\t"
"mov %%ax, %%es \\n\\t"
"mov %%ax, %%fs \\n\\t"
"mov %%ax, %%gs \\n\\t"
"mov %%ax, %%ss \\n\\t"
"ljmp $0x08, $1f \\n\\t" // 修正點:去掉引用的點,改為 $1f
"1: \\n\\t" // 修正點:標籤改為 1:
:
: "r" (gdt_ptr_addr)
: "eax", "memory" // 建議加上 "memory" 屏障
);
}
要管理數 GB 的記憶體,我們需要一種高效的數據結構來標記哪些頁面(Page, 4KB)是可用的,哪些是被佔用的。
我們透過 BIOS 中斷 int 0x15, eax=0xE820 獲取了系統的物理記憶體分佈圖。這張圖告訴我們:
我們使用一個連續的字節數組作為位圖,每個 bit 代表一個 4KB 的物理頁:
Bit = 1: 該頁已佔用。