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

Problems removing identity mapping in x86 kernel

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

进入内核后,我使用递归页面 tables 访问(我得到它们的虚拟地址:What happens when you lose the virtual address of the page directory?)我的页面目录和所有其他页面 table秒。递归页tables:页目录的最后一项指向自身。

我想重新映射堆栈并给它一个虚拟地址。由于递归页面技术使用 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-monitor 来 info tlb... 它似乎不是酡。之前的所有条目都存在..

我哪里出错了?

注意:当我使用 gdb 进行调试(单步执行)时,在我刷新 tlb 之后不可能 访问地址 0xFFC00000...但这确实使感觉,我只是删除了页面 table 0..

对应的页面目录条目

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

嗯,看来我忘了重新映射显存!我仍在为 VGA 文本模式访问 0xb8000。问题的原因是我没有识别出这个1M以下的访问。如果有,我就不会问问题了 ;)。

因此,对于希望在启用分页后删除身份映射的人来说,这可能是一个很好的教训:

  • 重新映射 gdt
  • 重新映射 idt
  • 重新映射堆栈
  • 重新映射video_memory