JIURL玩玩Win2k内存篇 分页机制 (三)
作者: JIURL
日期: 2003-7-30
8种转换
由于页表被映射到了0xc0000000 开始的4MB地址空间。
所以我们也可以象CPU那样完成虚拟地址到物理地址的转换。
系统按照对应虚拟空间的先后顺序,把一个进程的页表映射在0xc0000000 开始的4MB地址空间中,把页目录映射在0xc0300000 开始的4KB地址空间中。于是我们可以做如下几种地址的相互转换。
1 虚拟地址->虚拟地址对应的PDE地址
PDE_Address=(VirtualAddress>>22)*4+0xC0300000
2 虚拟地址->虚拟地址对应的PTE地址
PTE_Address=(VirtualAddress>>12)*4+0xC0000000
3 虚拟地址->物理地址
如果 虚拟地址大于等于0x80000000 并且小于0xa0000000(在 Large Page 部分),
直接用虚拟地址减去0x80000000就得到了物理地址。
其他情况
取得该虚拟地址的PDE,判断是否有效。
有效的话,取得该虚拟地址的PTE,判断是否有效。
有效的话,将PTE的低12位清0加上虚拟地址的低12位就得到了物理地址。
由于页表和页目录在系统地址空间中,访问需要程序运行在ring0,所以要测试的话,需要写驱动程序。
unsigned int PDE;
unsigned int PTE;
if(VirtualAddress>=0x80000000 && VirtualAddress<0xa0000000)
{
PhysicalAddress=VirtualAddress-0x80000000;
}
else
{
PDE=*(unsigned int*)((VirtualAddress>>22)*4+0xC0300000);
if(PDE&0x00000001)
{
PTE=*(unsigned int*)((VirtualAddress>>12)*4+0xC0000000);
if(PTE&0x00000001)
{
PhysicalAddress=((PTE&0xFFFFF000)+(VirtualAddress&0x00000FFF));
}
}
}
4 一个PDE的地址->相应的虚拟地址范围
VirtualAddressStart=((PDE_Address-0xC0300000)/4)<<22
VirtualAddressEnd=VirtualAddressStart+0x003FFFFF
5 一个PTE的地址->相应的虚拟地址范围
VirtualAddressStart=((PTE_Address-0xC0000000)/4)<<12
VirtualAddressEnd=VirtualAddressStart+0x00000FFF
6 物理地址->虚拟地址
一个物理地址常常会对应多个虚拟地址。转换方法就是遍历所有页表和页目录,如果有效就比较该项的高20bit是否等于我们提供的物理地址的高20bit,如果相等,就找到了一个。比如该项是第i个PDE的,第j个PTE,那么虚拟地址等于,i*4M+j*4K+物理地址的低12bit。遍历所有页表和页目录找到每一个。
7 一个PTE的地址->相应PDE的地址
一个PTE的地址可以找到相应的虚拟地址范围,就可以找到虚拟地址对应的PDE地址
PDE_Address=(VirtualAddress>>22)*4+0xC0300000
PDE_Address=((((PTE_Address-0xC0000000)/4)<<12)>>22)*4+0xC0300000
8 一个PDE的地址->相应PTE的地址范围
一个PDE的地址可以找到相应的虚拟地址范围的开始地址,就可以找到该虚拟地址对应的PTE地址,
一个PDE对应1024个PTE,4K大小。
PTE_AddressStart=(VirtualAddress>>12)*4+0xC0000000
PTE_AddressStart=((((PDE_Address-0xC0300000)/4)<<22)>>12)*4+0xC0000000
PTE_AddressEnd=PTE_AddressStart+0x00000FFF
无效页与 Page Fault
访问的虚拟地址所在页在物理内存中时,该虚拟地址所在页相应的 PDE,PTE 都有效,CPU 自动根据相应的PDE,PTE把虚拟地址转换成物理地址,完成访问。一个虚拟地址所在页不在物理内存中时,比如在硬盘上的交换文件中,该虚拟地址所在页相应的 PDE,PTE 都无效,访问该虚拟地址将引起 Page-Fault 异常(Exception)。从而使 CPU 转去执行异常处理程序,异常处理程序会做相应的处理。对于发现访问的虚拟地址所在的页在硬盘上的交换文件中,就从交换文件中读入该页到物理内存,重新使PTE有效,并指向正确的物理页。最后 CPU 重新执行引起异常的指令,这时该指令所访问的虚拟地址已经在物理内存中了,并且该虚拟地址的PTE也有效了,于是就可以顺利执行。
下面我们针对 x86 CPU 做更详细的说明。
当某条指令访问无效页时,比如指令 MOV EAX,InValidAddress ,执行这条指令时,CPU 会自动通过页目录和页表把虚拟地址 InValidAddress 转换成物理地址,在地址转换过程中,CPU 在从页表项得到物理页地址的同时,会进行页保护检查,比如看该页表项是否有效,是否是只读等等。当CPU发现指令中地址的页表项无效,就会引发异常(Exception)。异常也是由 CPU 实现的。这里引起的是一个 Page Fault 异常,它的中断号是 0xe (十进制14),需要注意的是 Page Fault 的中断号是 0xe 这是由 CPU 定义的( x86 CPU 的 从 0 - 31 这32个中断是由 CPU 定义的,CPU 将根据这个定义做相应工作)。在发生异常时,CPU 自动把一些寄存器压入堆栈,然后根据中断号,(通过IDTR寄存器找到中断描述符表)在中断描述符表中找到相应的中断描述符,根据中断描述符中的地址,转到异常处理程序。中断描述符是由Win2k设置,异常处理程序也是由Win2k决定。对于 Win2k Build 2195 来说,中断 0xe 的处理程序是 ntoskrnl!KiTrap0E 地址在 804648a4 。当转到KiTrap0E 时,CPU 已经在堆栈中压入了下面的内容
|-------------|
| EFLAGS |
|-------------|
| CS |
|-------------|
| EIP |
|-------------|
| Error Code |
|-------------|<---- [ ESP ]
page-fault 异常 (#PF) 的 Error Code 定义如下( CPU 定义 )
| 3 | 2 | 1 | 0 |
+---------------------------------------------------+
| Reserved |RSVD|U/S|R/W| P |
+---------------------------------------------------+
P 0 错误由无效页引起
1 错误由违反页保护引起
W/R 0 引起错误的内存访问是读
1 引起错误的内存访问是写
U/S 0 访问错误时处理器处在管理模式
1 访问错误时处理器处在用户模式
需要说明的是堆栈中压入的 EIP 就是引发异常的指令地址,将来将根据这个地址重新执行该指令。而寄存器 cr2 中是引发异常时访问的地址。
#PF异常处理程序 KiTrap0E(由Win2k提供)将会调用 ntoskrnl!MmAccessFault ,MmAccessFault 通过 CR2 中的访问地址,计算出相应的 PDE,PTE地址,通过分析PTE中的内容,可以知道是哪种情况引起的异常,并根据情况作出相应的处理。
当发生异常时,CPU会把一些寄存器压入堆栈,转到相应的异常处理程序(由Win2k提供),Win2k 在异常处理程序中又会把一些寄存器压入堆栈,最后会在堆栈中形成一个 KTRAP_FRAME 结构。这个 KTRAP_FRAME 是 ntoskrnl!MmAccessFault 的参数之一。
!strct KTRAP_FRAME
struct _KTRAP_FRAME (sizeof=140)
+00 uint32 DbgEbp
+04 uint32 DbgEip
+08 uint32 DbgArgMark
+0c uint32 DbgArgPointer
+10 uint32 TempSegCs
+14 uint32 TempEsp
+18 uint32 Dr0
+1c uint32 Dr1
+20 uint32 Dr2
+24 uint32 Dr3
+28 uint32 Dr6
+2c uint32 Dr7
+30 uint32 SegGs
+34 uint32 SegEs
+38 uint32 SegDs
+3c uint32 Edx
+40 uint32 Ecx
+44 uint32 Eax
+48 uint32 PreviousPreviousMode
+4c struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList
+50 uint32 SegFs
+54 uint32 Edi
+58 uint32 Esi
+5c uint32 Ebx
+60 uint32 Ebp
+64 uint32 ErrCode
+68 uint32 Eip
+6c uint32 SegCs
+70 uint32 EFlags
+74 uint32 HardwareEsp
+78 uint32 HardwareSegSs
+7c uint32 V86Es
+80 uint32 V86Ds
+84 uint32 V86Fs
+88 uint32 V86Gs
系统,CPU 与 页目录项,页表项的关系
一个进程的PDE(页目录项),PTE(页表项)是由系统维护的。地址转换由CPU自动完成。访问无效地址CPU 将产生异常,执行异常处理程序。异常处理程序是系统提供的。
对于有效的 PDE,PTE,他们的格式大部分由CPU定义,CPU 将按照这个格式,根据每一位的值,决定相应的处理方式。CPU 利用这个格式中自己定义的物理页物理地址的部分来进行地址转换,自己定义的一些标志位来实现页的保护。系统必须按照这个格式定义,来维护 PDE 和 PTE。
x86 CPU 的有效页表项,CPU 定义如下
struct _HARDWARE_PTE_X86 (sizeof=4)
bits0-0 Valid
bits1-1 Write
bits2-2 Owner
bits3-3 WriteThrough
bits4-4 CacheDisable
bits5-5 Accessed
bits6-6 Dirty
bits7-7 LargePage
bits8-8 Global
bits9-11 reserved
bits12-31 PageFrameNumber
其中要注意的是 bits9-11 reserved 这3位,CPU 没有定义,留给操作系统使用。
对于无效的 PDE,PTE,他们的格式大部分由系统定义。当 CPU 发现访问的 PDE,PTE 无效时,就会转去执行异常处理程序。异常处理程序是由系统提供的。系统将按照自己定义的格式,根据每一位的值,决定相应的处理方式。
x86 CPU 的无效页表项,CPU 定义如下
struct _HARDWARE_PTE_X86 (sizeof=4)
bits0-0 Valid
bits1-31 reserved
对于无效页来说,CPU只定义了bits0-0,用来判断是否有效。无效页的 bits0-0 值为0。其他位都留给操作系统使用。
欢迎交流,欢迎交朋友,