在x86内核中删除标识映射时出现问题



我正在进行一些操作系统开发,这是我的目标。我已经启用了分页,并希望删除标识映射。以前,我有两个映射,0-4M的标识映射和虚拟地址0xC0000000映射到物理地址1M的内核。我的引导加载程序在跳转到内核之前执行此操作,内核负责删除标识映射。我的堆栈指针现在的值为0x90000。我的策略如下:

进入内核后,我使用递归页面表来访问我的页面目录和所有其他页面表(我得到它们的虚拟地址:当你失去页面目录的虚拟地址时会发生什么?(。递归页表:页目录的最后一个条目指向它自己。

我想重新映射堆栈并给它一个虚拟地址。由于递归页面技术使用了0xFFC00000-0xFFFFFFFF,因此我使用0xFFC00000作为堆栈的虚拟地址,并将其映射到0x90000物理地址。

然后我重新初始化gdt。

现在我的系统状态是:

(qemu) info registers 
EAX=000241a0 EBX=c00019de ECX=00000001 EDX=000241a0
ESI=00008137 EDI=00103800 EBP=ffc00000 ESP=ffc00000
EIP=c00019de EFL=00000087 [--S--PC] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     c0004100 00000027
IDT=     00000000 000003ff
CR0=80000011 CR2=00000000 CR3=0009c000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000

堆栈重映射代码:

STACK equ 0xFFC00000
STACK_PHY equ 0x90000
global refresh_stack ;A solution for switching stacks
refresh_stack:
mov ebx,[esp] ;Return address
mov esp,STACK
mov ebp,STACK
jmp ebx

我想删除这样的身份映射:

void remove_identity_map()  //This would remove the 4M identity map
{
if (entry_is_present(_page_directory[0]))
_page_directory[0] = 0;  //Unmapping the whole of 4M
flush_tlb();
}

其中flush_tlb:

flush_tlb:
mov eax,cr3
mov cr3,eax
ret

一个最小可重复的例子(大致(

void kmain()
{
set_recursive_map();
refresh_stack();
install_gdt(); //The standard 4 entries Data/Code * User/kernel
remove_identity_map();
.......
.......
}

它出错的地方是当我flush_tlb((时。如果我注释掉这一行,代码会按预期工作,但我们会看到缓存的副本。然而,当我保留它时,系统(模拟器(会从BIOS重新启动!(三重故障??(

此外,我正在使用qemu,在刷新tlb之后,我尝试了for(;;);将qemu监视器用于info tlb。。。它似乎没有脸红。以前的所有条目都存在。。

我哪里会出错?

注意:当我用gdb调试(单步(时,在我刷新tlb后,访问地址0xFFC00000是不可能的。。。但这确实有道理,我只是删除了与页面表0.相对应的页面目录条目

更新:我还没有做的一个重映射是视频内存!对于文本模式,我仍在访问0xB8000。由于我的异常处理程序也打印到屏幕上,这肯定是三重错误的来源!!!

看来我忘了重新映射视频内存了!我仍在访问0xb8000以获得VGA文本模式。这个问题的原因是我没有识别出低于1M的访问权限。如果我有,我就不会问任何问题了;(。

因此,对于那些希望在启用寻呼后删除身份图的人来说,这可能是一个很好的教训:

  • 重映射gdt
  • Remap idt
  • 重映射堆栈
  • 重映射视频记忆(_M(

最新更新