今天的目標是啟動 x86 的 分頁機制 (Paging)。這不僅是為了更靈活地管理記憶體,更是為了未來實現多工隔離與虛擬位址空間。我們成功創建了第一個 4MB 的映射空間,將虛擬地址映射至實體地址 0x0000 到 0x3FFFFF 區間。
在 32 位元保護模式下,分頁機制將 4GB 的位址空間切分為多個 4KB 的頁面。
我們在 mem.c 中配置了第一個 Page Directory 和相關的 Page Table,實現了內存映射(Identity Mapping)。
#ifndef MEM_H
#define MEM_H
#include "types.h"
#include "isr.h"
#define ENTRIES_COUNT 1024
#define PAGE_SIZE 4096
typedef struct {
u32 present :1; //存在位元
u32 rw :1; //讀寫位元
u32 user :1; //用戶位元
u32 accessed :1; //是否被存取過
u32 dirty :1; //是否被寫入過(僅PT)
u32 reserved :7; //保留位元(未被使用)
u32 frame_addr :20;//物理地址高20位,不需要低12位,因為4KB對齊的關係
} page_entry_t;
extern u32 page_directory[ENTRIES_COUNT];
extern u32 first_page_table[ENTRIES_COUNT];
void init_paging();
void enable_paging();
void page_fault_handler(registers_t *regs);
#endif
#include "mem.h"
#include "display.h"
#include "isr.h"
u32 page_directory[ENTRIES_COUNT] __attribute__((aligned(4096))) = {0x1};
u32 first_page_table[ENTRIES_COUNT] __attribute__((aligned(4096))) = {0x1};
void init_paging()
{
// 初始化頁目錄,將所有入口設置成不存在(屬性0x02)
for (int i = 0; i < ENTRIES_COUNT; i++)
{
page_directory[i] = 0x00000002;
}
// 填充第一個頁表,mapping 整個0x00000--0x3ffff (4M空間)
// 為什麼是4MB,因為每個頁表目錄對應4KB內存,一共1024項
// 地址的開始就是ith * 4kb, 屬性3 (rw and present)
for (int i = 0; i < ENTRIES_COUNT; i++)
{
first_page_table[i] = (i * PAGE_SIZE) | 0x3;
}
// 將第一個頁表放入頁目錄第一項,同時重新設置屬性為0x03
page_directory[0] = ((u32)first_page_table) | 0x3;
// register_interrupt_handler(14, page_fault_handler);
}
void enable_paging() {
// 將頁目錄的位址載入到 CR3 暫存器
__asm__ __volatile__("mov %0, %%cr3" : : "r"(page_directory));
// 讀取 CR0,將最高位 (PG bit, 第 31 位) 設為 1
u32 cr0;
__asm__ __volatile__("mov %%cr0, %0" : "=r"(cr0));
cr0 |= 0x80000000;
__asm__ __volatile__("mov %0, %%cr0" : : "r"(cr0));
}
void page_fault_handler(registers_t *) {
u32 faulting_address;
__asm__ __volatile__("mov %%cr2, %0" : "=r"(faulting_address));
kprint("PAGE FAULT! at virtual address: ");
kprintf("%x", faulting_address);
kprint("\\nHalting system.");
while(1)
{
__asm__ __volatile__("hlt");
}
}
在開發過程中,我們遇到了三個極具代表性的硬核問題:
問題描述:隨著內核功能的增加(尤其是加入 Paging 代碼後),kernel.bin 的體積超過了 18 個扇區(約 9KB)。傳統的 CHS (Cylinder-Head-Sector) 尋址模式在讀取軟碟或模擬磁碟時會因為跨磁頭/磁道問題導致加載失敗。
解決方案:我們將引導程式讀取磁碟的邏輯從 int 0x13, ah=0x02 切換到了 LBA (Logical Block Addressing) 模式(int 0x13, ah=0x42)。LBA 將磁碟視為線性排列的扇區,徹底解決了尋址限制。