x86保护模式分页启用后删除恒等映射致系统重启问题排查
问题分析与修复
1. 页表及其条目构造的问题
你的页表构造存在两处关键问题:
- 内核页表项缺少可写权限:在映射物理1MB到虚拟3GB的循环中,仅给页表项添加了
PAGE_STRUCT_ENTRY_PRESENT属性,未添加PAGE_STRUCT_ENTRY_WRITEABLE。x86分页权限遵循最严格匹配原则,即使页目录项(PDE)开了可写,页表项(PTE)未开的话,该页仍处于只读状态,会导致内核运行时无法修改数据触发错误。 - 页目录访问的地址问题:
alloc_page()返回的是物理地址,开启分页后,你通过pdir(物理地址)访问页目录条目时,依赖的是恒等映射。当你删除恒等映射的PDE后,这个物理地址对应的虚拟地址(低地址)不再有映射,访问pdir->entries[...]会直接触发页错误。
2. 内核高半区跳转的问题
你没有完成真正的高半区跳转:
当前的jmp 1f只是同一段内的短跳转,代码仍然运行在低地址虚拟空间(对应物理地址的恒等映射)。当删除恒等映射后,CPU当前执行的指令地址(低虚拟地址)失去映射,立即触发页错误,进入错误处理循环。
修复方案
步骤1:修正页表项权限
给内核映射和恒等映射的页表项添加可写属性:
// 恒等映射循环中补充 ENTRY_ADD_ATTRIBUTE(*entry, PAGE_STRUCT_ENTRY_WRITEABLE); // 内核映射循环中补充 ENTRY_ADD_ATTRIBUTE(*entry, PAGE_STRUCT_ENTRY_WRITEABLE);
步骤2:完成真正的高半区跳转
将代码执行流切换到高半区的虚拟地址,避免依赖恒等映射:
// 在VMM_init函数内,开启分页后添加 void (*high_half_continue)(pdirectory_t*) = (void (*)(pdirectory_t*))(0xC0000000 + ((uint32_t)&VMM_init_high_half - (uint32_t)&_begin)); high_half_continue(pdir); // 把删除恒等映射的逻辑拆分到单独函数 void VMM_init_high_half(pdirectory_t *pdir_phys) { // 将页目录的物理地址转换为高半区虚拟地址 pdirectory_t *pdir_virt = (pdirectory_t*)(0xC0000000 + (uint32_t)pdir_phys); // 删除恒等映射的PDE pdir_virt->entries[PAGE_DIR_INDEX(0x0)] = 0; // 刷新TLB生效 switch_pd(pdir_virt); }
通过计算内核代码在高半区的虚拟地址偏移,将后续执行逻辑转移到高半区,此时访问页目录使用虚拟地址,不再依赖恒等映射。
步骤3:映射页目录自身到高半区
在页目录中添加自身的映射,方便后续直接通过虚拟地址访问页目录:
// 创建页目录后添加 pd_entry_t *self_entry = &pdir->entries[PAGE_DIR_INDEX(0xFFFFF000)]; ENTRY_ADD_ATTRIBUTE(*self_entry, PAGE_STRUCT_ENTRY_PRESENT); ENTRY_ADD_ATTRIBUTE(*self_entry, PAGE_STRUCT_ENTRY_WRITEABLE); ENTRY_SET_FRAME(*self_entry, pdir);
之后可直接通过0xFFFFF000这个虚拟地址访问页目录,无需手动转换物理/虚拟地址。
内容的提问来源于stack exchange,提问作者user29935845
相关产品推荐
相关产品推荐

