启用分页导致QEMU重启,如何修复该分页初始化问题?
分页初始化导致QEMU重启的修复方案
问题背景
分页初始化代码中,执行设置CR3/CR0的内联汇编时QEMU触发重启,移除该汇编段后程序可正常运行,核心问题出在分页初始化逻辑或汇编指令的正确性上。相关代码如下:
分页初始化函数
void paging_init(){ //Enable Paging uint32 cr0; memset(g_page_directory, 0, sizeof(page_directory_t) * 1024); memset(g_page_tables, 0, sizeof(page_table_t) * 1024); // Set All page directories to have rw and u/s access (only by kernel) for(int i = 0; i < 1024; i++){ g_page_directory[i].user_supervisor = 1; g_page_directory[i].read_write = 1; } // fill all the entries in page table to map all 4MB memory for (int i = 0; i < 1024; i++) { g_page_tables[i].present = 1; g_page_tables[i].read_write = 1; g_page_tables[i].user_supervisor = 1; g_page_tables[i].frame = (i * PAGE_SIZE) >> 12; } // set first page directory to be accessed with frame 0x11a(kernel region address) g_page_directory[0].present = 1; g_page_directory[0].accessed = 0; g_page_directory[0].user_supervisor = 1; g_page_directory[0].frame = 0x11a; //Register Page Fault Handler isr_register_interrupt_handler(14, page_fault_handler); // set cr3 point to page directory asm volatile("mov %0, %%cr3" :: "r"(g_page_directory)); // set bit in cr0 to enable paging asm volatile("mov %%cr0, %0": "=r"(cr0)); cr0 = cr0 | 0x80000000; asm volatile("mov %0, %%cr0" :: "r"(cr0)); }
引发问题的内联汇编段
// set cr3 point to page directory asm volatile("mov %0, %%cr3" :: "r"(g_page_directory)); // set bit in cr0 to enable paging asm volatile("mov %%cr0, %0": "=r"(cr0)); cr0 = cr0 | 0x80000000; asm volatile("mov %0, %%cr0" :: "r"(cr0));
结构体定义
// Page Directory Struct typedef struct { uint32 present :1; uint32 read_write :1; uint32 user_supervisor :1; uint32 write_through :1; uint32 cache_disable :1; uint32 accessed :1; uint32 dirty :1; uint32 page_size :1; uint32 global :1; uint32 available :3; uint32 frame :20; } page_directory_t; // Page Table typedef struct { uint32 present :1; uint32 read_write :1; uint32 user_supervisor :1; uint32 write_through :1; uint32 cache_disable :1; uint32 accessed :1; uint32 dirty :1; uint32 page_size :1; uint32 global :1; uint32 available :3; uint32 frame :20; } page_table_t;
核心问题分析与修复步骤
1. CR3传入虚拟地址而非物理地址
x86架构中,CR3寄存器必须存储页目录的物理地址,但代码中直接传入g_page_directory的虚拟地址(即使分页未开启,若内核加载到高地址,虚拟/物理地址可能不重合)。
- 修复:将页目录的物理地址传入CR3。若内核采用身份映射(虚拟地址=物理地址),需确保
g_page_directory位于4KB对齐的物理内存区域;若内核有地址偏移,需减去偏移量后传入:// 假设内核虚拟地址 = 物理地址 + 0xC0000000 uint32 phys_page_dir = (uint32)g_page_directory - 0xC0000000; asm volatile("mov %0, %%cr3" :: "r"(phys_page_dir));
2. 页目录项的Frame地址错误
代码中g_page_directory[0].frame = 0x11a,但frame字段是物理页地址的高20位(即物理地址>>12)。0x11a对应的物理页地址是0x11a << 12 = 0x11A000,需确认该地址确实是页表的物理起始地址且已正确初始化。
- 修复:直接通过页表的物理地址计算frame值:
// 同样转换为物理地址后右移12位 uint32 phys_page_table = (uint32)g_page_tables - 0xC0000000; g_page_directory[0].frame = phys_page_table >> 12;
3. 缺失内存屏障与流水线刷新
修改CR3/CR0后,CPU可能存在缓存不一致,导致分页开启后指令访问异常。
- 修复:添加内存屏障指令,并通过跳转刷新CPU流水线:
// 设置CR3并添加内存屏障 asm volatile("mov %0, %%cr3\n\t" "mfence" :: "r"(phys_page_dir) : "memory"); // 开启分页并刷新流水线 asm volatile("mov %%cr0, %0": "=r"(cr0)); cr0 |= 0x80000000; asm volatile("mov %0, %%cr0\n\t" "jmp 1f\n\t" "1:" :: "r"(cr0) : "memory");
4. 页目录项的权限与Present位滥用
代码中循环设置所有页目录项的user_supervisor=1和read_write=1,但大部分项未设置present=1,会导致不必要的页错误风险。
- 修复:仅对已映射的页目录项设置权限与Present位,其余保持默认0:
// 仅初始化第一个页目录项 g_page_directory[0].present = 1; g_page_directory[0].user_supervisor = 1; g_page_directory[0].read_write = 1; // 其他项保持memset后的0值
5. 页表映射范围覆盖内核区域
代码中仅映射4MB内存,需确保内核加载的全部物理地址都被包含在映射范围内,避免分页开启后内核指令访问未映射区域触发重启。
- 修复:根据内核实际大小扩展页表映射范围,确保所有内核代码、数据所在的物理页都被正确映射。
内容的提问来源于stack exchange,提问作者xingharvey
相关产品推荐
相关产品推荐

