📄 jiurl玩玩win2k内存篇 分页机制 (三).htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0070)http://jiurl.cosoft.org.cn/jiurl/document/JiurlPlayWin2k/MmPaging3.htm -->
<HTML><HEAD><TITLE>JIURL玩玩Win2k内存篇 分页机制 (三)</TITLE>
<META content="text/html; charset=gb2312" http-equiv=Content-Type>
<STYLE type=text/css>.title {
FONT-FAMILY: "黑体", Arial, sans-serif; FONT-SIZE: 21px; FONT-WEIGHT: bold; LINE-HEIGHT: 48px; TEXT-DECORATION: none
}
.author {
FONT-FAMILY: "宋体"; FONT-SIZE: 12px; LINE-HEIGHT: 16px
}
.content {
FONT-SIZE: 14px; LINE-HEIGHT: 20px
}
</STYLE>
<META content="MSHTML 5.00.2614.3500" name=GENERATOR></HEAD>
<BODY bgColor=#f7f7f7 topMargin=5>
<DIV align=center>
<CENTER>
<TABLE border=0 cellPadding=0 cellSpacing=0 height=29 width="96%">
<TBODY>
<TR>
<TD class=title height=41 width="100%">
<P align=center><FONT face=宋体>JIURL玩玩Win2k内存篇 分页机制
(三)</FONT></P></TD></TR></CENTER>
<TR>
<TD class=author height=9 width="100%">
<P align=center><FONT face=宋体>作者: <A
href="mailto:jiurl@mail.china.com">JIURL</A> </FONT></P></TD></TR>
<TR>
<TD class=author height=6 width="100%">
<P align=center><FONT
face=宋体>
主页: <A href="http://jiurl.yeah.net/">http://jiurl.yeah.net/</A>
</FONT></P></TD></TR>
<TR>
<TD class=author height=2 width="100%">
<P align=center><FONT face=宋体> 日期: 2003-7-30</FONT>
</P></TD></TR></TBODY></TABLE></DIV>
<DIV align=center>
<CENTER>
<TABLE border=0 cellPadding=0 cellSpacing=0 height=1 width="96%">
<TBODY>
<TR>
<TD height=1 width="100%">
<HR color=#396da5 SIZE=3>
</TD></TR></TBODY></TABLE></CENTER></DIV>
<DIV align=center>
<TABLE border=0 cellPadding=0 cellSpacing=0 class=content height=4300
width="96%">
<TBODY>
<TR>
<TD height=1066 vAlign=top width="131%"><FONT
face=宋体><B>8种转换</B><BR><BR>由于页表被映射到了0xc0000000
开始的4MB地址空间。<BR>所以我们也可以象CPU那样完成虚拟地址到物理地址的转换。<BR><BR>系统按照对应虚拟空间的先后顺序,把一个进程的页表映射在0xc0000000
开始的4MB地址空间中,把页目录映射在0xc0300000 开始的4KB地址空间中。于是我们可以做如下几种地址的相互转换。<BR><BR>1
虚拟地址->虚拟地址对应的PDE地址<BR><BR>PDE_Address=(VirtualAddress>>22)*4+0xC0300000 <BR><BR>2
虚拟地址->虚拟地址对应的PTE地址<BR><BR>PTE_Address=(VirtualAddress>>12)*4+0xC0000000 <BR><BR>3
虚拟地址->物理地址<BR><BR>如果 虚拟地址大于等于0x80000000 并且小于0xa0000000(在 Large Page
部分),<BR>直接用虚拟地址减去0x80000000就得到了物理地址。<BR>其他情况<BR>取得该虚拟地址的PDE,判断是否有效。<BR>有效的话,取得该虚拟地址的PTE,判断是否有效。<BR>有效的话,将PTE的低12位清0加上虚拟地址的低12位就得到了物理地址。<BR>由于页表和页目录在系统地址空间中,访问需要程序运行在ring0,所以要测试的话,需要写驱动程序。<BR><BR>unsigned
int PDE;<BR>unsigned int PTE;<BR><BR>if(VirtualAddress>=0x80000000
&&
VirtualAddress<0xa0000000)<BR>{<BR>PhysicalAddress=VirtualAddress-0x80000000;<BR>}<BR>else<BR>{<BR>PDE=*(unsigned
int*)((VirtualAddress>>22)*4+0xC0300000);<BR>if(PDE&0x00000001)<BR>{<BR>PTE=*(unsigned
int*)((VirtualAddress>>12)*4+0xC0000000);<BR>if(PTE&0x00000001)<BR>{<BR>PhysicalAddress=((PTE&0xFFFFF000)+(VirtualAddress&0x00000FFF));<BR>}<BR>}<BR>}<BR><BR>4
一个PDE的地址->相应的虚拟地址范围<BR><BR>VirtualAddressStart=((PDE_Address-0xC0300000)/4)<<22<BR>VirtualAddressEnd=VirtualAddressStart+0x003FFFFF<BR><BR>5
一个PTE的地址->相应的虚拟地址范围<BR><BR>VirtualAddressStart=((PTE_Address-0xC0000000)/4)<<12<BR>VirtualAddressEnd=VirtualAddressStart+0x00000FFF<BR><BR>6
物理地址->虚拟地址<BR><BR>一个物理地址常常会对应多个虚拟地址。转换方法就是遍历所有页表和页目录,如果有效就比较该项的高20bit是否等于我们提供的物理地址的高20bit,如果相等,就找到了一个。比如该项是第i个PDE的,第j个PTE,那么虚拟地址等于,i*4M+j*4K+物理地址的低12bit。遍历所有页表和页目录找到每一个。<BR><BR>7
一个PTE的地址->相应PDE的地址<BR><BR>一个PTE的地址可以找到相应的虚拟地址范围,就可以找到虚拟地址对应的PDE地址<BR>PDE_Address=(VirtualAddress>>22)*4+0xC0300000 <BR>PDE_Address=((((PTE_Address-0xC0000000)/4)<<12)>>22)*4+0xC0300000 <BR><BR>8
一个PDE的地址->相应PTE的地址范围<BR><BR>一个PDE的地址可以找到相应的虚拟地址范围的开始地址,就可以找到该虚拟地址对应的PTE地址,<BR>一个PDE对应1024个PTE,4K大小。<BR><BR>PTE_AddressStart=(VirtualAddress>>12)*4+0xC0000000<BR>PTE_AddressStart=((((PDE_Address-0xC0300000)/4)<<22)>>12)*4+0xC0000000 <BR>PTE_AddressEnd=PTE_AddressStart+0x00000FFF<BR><BR><B>无效页与
Page Fault</B><BR><BR>访问的虚拟地址所在页在物理内存中时,该虚拟地址所在页相应的 PDE,PTE 都有效,CPU
自动根据相应的PDE,PTE把虚拟地址转换成物理地址,完成访问。一个虚拟地址所在页不在物理内存中时,比如在硬盘上的交换文件中,该虚拟地址所在页相应的
PDE,PTE 都无效,访问该虚拟地址将引起 Page-Fault 异常(Exception)。从而使 CPU
转去执行异常处理程序,异常处理程序会做相应的处理。对于发现访问的虚拟地址所在的页在硬盘上的交换文件中,就从交换文件中读入该页到物理内存,重新使PTE有效,并指向正确的物理页。最后
CPU
重新执行引起异常的指令,这时该指令所访问的虚拟地址已经在物理内存中了,并且该虚拟地址的PTE也有效了,于是就可以顺利执行。<BR><BR>下面我们针对
x86 CPU 做更详细的说明。<BR><BR>当某条指令访问无效页时,比如指令 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
已经在堆栈中压入了下面的内容<BR><BR>|-------------| <BR>|
EFLAGS |<BR>|-------------|<BR>|
CS
|<BR>|-------------|<BR>| EIP
|<BR>|-------------|<BR>| Error Code |<BR>|-------------|<---- [
ESP ]<BR><BR><BR>page-fault 异常 (#PF) 的 Error Code 定义如下( CPU 定义
)<BR> |
3 | 2 | 1 | 0
|<BR>+---------------------------------------------------+ <BR>|
Reserved |RSVD|U/S|R/W|
P
|<BR>+---------------------------------------------------+ <BR><BR>P
0 错误由无效页引起<BR> 1 错误由违反页保护引起<BR>W/R 0
引起错误的内存访问是读<BR> 1 引起错误的内存访问是写<BR>U/S 0
访问错误时处理器处在管理模式<BR> 1 访问错误时处理器处在用户模式<BR><BR>需要说明的是堆栈中压入的
EIP 就是引发异常的指令地址,将来将根据这个地址重新执行该指令。而寄存器 cr2 中是引发异常时访问的地址。<BR><BR>#PF异常处理程序
KiTrap0E(由Win2k提供)将会调用 ntoskrnl!MmAccessFault ,MmAccessFault 通过 CR2
中的访问地址,计算出相应的
PDE,PTE地址,通过分析PTE中的内容,可以知道是哪种情况引起的异常,并根据情况作出相应的处理。<BR><BR>当发生异常时,CPU会把一些寄存器压入堆栈,转到相应的异常处理程序(由Win2k提供),Win2k
在异常处理程序中又会把一些寄存器压入堆栈,最后会在堆栈中形成一个 KTRAP_FRAME 结构。这个 KTRAP_FRAME 是
ntoskrnl!MmAccessFault 的参数之一。<BR><BR>!strct KTRAP_FRAME<BR>struct
_KTRAP_FRAME (sizeof=140)<BR>+00 uint32 DbgEbp<BR>+04 uint32 DbgEip<BR>+08
uint32 DbgArgMark<BR>+0c uint32 DbgArgPointer<BR>+10 uint32
TempSegCs<BR>+14 uint32 TempEsp<BR>+18 uint32 Dr0<BR>+1c uint32 Dr1<BR>+20
uint32 Dr2<BR>+24 uint32 Dr3<BR>+28 uint32 Dr6<BR>+2c uint32 Dr7<BR>+30
uint32 SegGs<BR>+34 uint32 SegEs<BR>+38 uint32 SegDs<BR>+3c uint32
Edx<BR>+40 uint32 Ecx<BR>+44 uint32 Eax<BR>+48 uint32
PreviousPreviousMode<BR>+4c struct _EXCEPTION_REGISTRATION_RECORD
*ExceptionList<BR>+50 uint32 SegFs<BR>+54 uint32 Edi<BR>+58 uint32
Esi<BR>+5c uint32 Ebx<BR>+60 uint32 Ebp<BR>+64 uint32 ErrCode<BR>+68
uint32 Eip<BR>+6c uint32 SegCs<BR>+70 uint32 EFlags<BR>+74 uint32
HardwareEsp<BR>+78 uint32 HardwareSegSs<BR>+7c uint32 V86Es<BR>+80 uint32
V86Ds<BR>+84 uint32 V86Fs<BR>+88 uint32 V86Gs<BR><BR><B>系统,CPU 与
页目录项,页表项的关系</B><BR><BR>一个进程的PDE(页目录项),PTE(页表项)是由系统维护的。地址转换由CPU自动完成。访问无效地址CPU
将产生异常,执行异常处理程序。异常处理程序是系统提供的。<BR><BR>对于有效的 PDE,PTE,他们的格式大部分由CPU定义,CPU
将按照这个格式,根据每一位的值,决定相应的处理方式。CPU
利用这个格式中自己定义的物理页物理地址的部分来进行地址转换,自己定义的一些标志位来实现页的保护。系统必须按照这个格式定义,来维护 PDE 和
PTE。<BR><BR>x86 CPU 的有效页表项,CPU 定义如下<BR><BR>struct _HARDWARE_PTE_X86
(sizeof=4)<BR>bits0-0 Valid<BR>bits1-1 Write<BR>bits2-2 Owner<BR>bits3-3
WriteThrough<BR>bits4-4 CacheDisable<BR>bits5-5 Accessed<BR>bits6-6
Dirty<BR>bits7-7 LargePage<BR>bits8-8 Global<BR>bits9-11
reserved<BR>bits12-31 PageFrameNumber<BR><BR>其中要注意的是 bits9-11 reserved
这3位,CPU 没有定义,留给操作系统使用。<BR><BR>对于无效的 PDE,PTE,他们的格式大部分由系统定义。当 CPU 发现访问的
PDE,PTE
无效时,就会转去执行异常处理程序。异常处理程序是由系统提供的。系统将按照自己定义的格式,根据每一位的值,决定相应的处理方式。<BR><BR>x86
CPU 的无效页表项,CPU 定义如下<BR><BR>struct _HARDWARE_PTE_X86 (sizeof=4)<BR>bits0-0
Valid<BR>bits1-31 reserved<BR><BR>对于无效页来说,CPU只定义了bits0-0,用来判断是否有效。无效页的
bits0-0 值为0。其他位都留给操作系统使用。</FONT>
<P>欢迎交流,欢迎交朋友,<BR>欢迎访问 <A
href="http://jiurl.yeah.net/">http://jiurl.yeah.net/</A> <A
href="http://jiurl.cosoft.org.cn/forum">http://jiurl.cosoft.org.cn/forum</A></P>
<P> </P>
<P> </P></TD></TR></TBODY></TABLE></DIV></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -