📄 jiurl玩玩win2k内存篇 内存共享(一) protopte.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0071)http://jiurl.cosoft.org.cn/jiurl/document/JiurlPlayWin2k/MmProtoPTE.htm -->
<HTML><HEAD><TITLE>JIURL玩玩Win2k内存篇 内存共享(一) ProtoPTE</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内存篇 内存共享(一)
ProtoPTE</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=10000
width="96%">
<TBODY>
<TR>
<TD height=1041 vAlign=top width="131%">
<P><FONT face=宋体><B>内存共享</B> </FONT>
<P><FONT face=宋体> Windows 2000 中进程之间共享内存的几个主要应用是, 一个
dll(动态链接库)可能被多个进程使用,应该被共享。一个程序也应该可以被多个运行的实例共享。通过文件映射(Memory Mapped
File)实现的进程之间通过内存通信,传输数据。</FONT>
<P><FONT face=宋体>
每个进程有自己的4G地址空间,地址空间通过进程自己的页目录和页表,以页为单位映射物理内存。把两个不同进程的一页地址空间映射到同一个物理页上,两个进程的一页地址空间就实现了共享,两个进程对该页地址空间的读写,都是对同一个物理页的读写。两个进程自己的某个
PTE(页表项,对应一页地址空间)中的物理页帧号相同,就使两个进程的某页映射到了同一个物理页。比如进程1的0x10000-0x10FFF这一页映射物理内存的第100页,进程2的0x20000-0x20FFF这一页也映射物理内存的第100页,那么进程1的0x10000-0x10FFF
的就是 进程2的0x20000-0x20FFF ,就是物理内存的第100页。 </FONT>
<P><FONT face=宋体> 我们下面观察一下Win2k中dll的共享。ntdll.dll
通常会被所有的进程使用。 我们打开一个记事本程序,一个计算器程序,使用 SoftICE 来看看这两个进程负责映射 ntdll.dll 的
页表项(PTE)是否相同。相同的话,就说明共享了物理内存,不相同就说明没有共享物理内存。<BR><BR>运行计算器和记事本,使用SoftICE观察。<BR><BR>首先来看计算器<BR><BR>//
使用 addr 命令转换到 calc进程 也就是计算器进程的地址空间<BR>:addr calc<BR>// 使用 map32 -u
命令,看看用户地址空间中映射的模组<BR>:map32 -u<BR>Owner Obj Name Obj# Address Size
Type<BR>calc .text 0001 001B:01001000 000124EE CODE RO<BR>calc .data 0002
0023:01014000 000010C0 IDATA RW<BR>calc .rsrc 0003 0023:01016000 00002B98
IDATA RO<BR>NVDESK32 .text 0001 001B:10001000 0000DB8F CODE
RO<BR>...<BR>gdi32 .reloc 0004 0023:77F7A000 0000151C IDATA RO<BR>ntdll
.text 0001 001B:77F81000 00042492 CODE RO<BR>ntdll ECODE 0002
001B:77FC4000 00004371 CODE RO<BR>ntdll PAGE 0003 001B:77FC9000 00003983
CODE RO<BR>ntdll .data 0004 0023:77FCD000 00002350 IDATA RW<BR>ntdll .rsrc
0005 0023:77FD0000 00026D08 IDATA RO<BR>ntdll .reloc 0006 0023:77FF7000
00001DA8 IDATA RO<BR>...<BR>MSVCRT .reloc 0005 0023:78043000 00002600
IDATA RO<BR>// 看到了 ntdll 的各种节 映射到地址空间中的地址<BR>// 代码放在 .text 节中, .text
节的开始地址为 77F81000 <BR>// 我们看 .text 节中的 77F82000-77F82FFF 这一页<BR>//
这一页对应的 PTE 的虚拟地址为 (77F82000>>12)*4+0xC0000000= 0xc01dfe08<BR><BR>//
显示这部分页表项<BR>:dd c01dfe04 l 100<BR>0010:C01DFE04 00000000 04E63005 04E4B005
04E4C005 .....0..........<BR>0010:C01DFE14 04E4D025 04E2E005 04E2F005
04E30005 %...............<BR>0010:C01DFE24 04E11005 04E12005 04E1B025
04E1C025 ..... ..%...%...<BR>0010:C01DFE34 04E1D005 04DFE005 04DFF025
04E80025 ........%...%...<BR>0010:C01DFE44 04E61005 04E13005 04E14025
04E15005 .....0..%@...P..<BR>0010:C01DFE54 04E16005 04DF7025 04DF8025
00000000 .`..%p..%.......<BR>0010:C01DFE64 04E1A005 00000000 00000000
00000000 ................<BR>0010:C01DFE74 00000000 00000000 04E43025
04E44005 ........%0...@..<BR>0010:C01DFE84 00000000 04E46025 00000000
00000000 ....%`..........<BR>...<BR>// 0xc01dfe08 处的值为 04E63005
,其中高20bit是物理页的页号,低12bit是标志<BR>// 所以 ntdll .text 节中的 77F82000-77F82FFF 这一页
映射到了物理页 04E63 <BR>//(物理地址 04E63000-04E63FFF )<BR><BR>再来看记事本<BR><BR>//
转换到 notepad 的地址空间<BR>:addr notepad<BR>:map32 -u<BR>Owner Obj Name Obj#
Address Size Type<BR>NOTEPAD .text 0001 001B:01001000 000065CA CODE
RO<BR>NOTEPAD .data 0002 0023:01008000 00001944 IDATA RW<BR>NOTEPAD .rsrc
0003 0023:0100A000 00005238 IDATA RO<BR>NVDESK32 .text 0001 001B:10001000
0000DB8F CODE RO<BR>...<BR>gdi32 .reloc 0004 0023:77F7A000 0000151C IDATA
RO<BR>ntdll .text 0001 001B:77F81000 00042492 CODE RO<BR>ntdll ECODE 0002
001B:77FC4000 00004371 CODE RO<BR>ntdll PAGE 0003 001B:77FC9000 00003983
CODE RO<BR>ntdll .data 0004 0023:77FCD000 00002350 IDATA RW<BR>ntdll .rsrc
0005 0023:77FD0000 00026D08 IDATA RO<BR>ntdll .reloc 0006 0023:77FF7000
00001DA8 IDATA RO<BR>...<BR>MSVCRT .reloc 0005 0023:78043000 00002600
IDATA RO<BR>// 看到了 ntdll 的各种节 .text 节的开始地址为 77F81000 <BR>// 同样看 .text
节中的 77F82000-77F82FFF 这一页<BR>// 这一页对应的 PTE 的虚拟地址为
(77F82000>>12)*4+0xC0000000= 0xc01dfe08<BR><BR>// 显示这部分页表项<BR>:dd
c01dfe04 l 100<BR>0010:C01DFE04 00000000 04E63005 04E4B025 04E4C005
.....0..%.......<BR>0010:C01DFE14 04E4D005 04E2E005 04E2F005 04E30005
................<BR>0010:C01DFE24 04E11005 04E12025 04E1B005 04E1C005
....% ..........<BR>0010:C01DFE34 04E1D005 04DFE005 04DFF025 04E80025
........%...%...<BR>0010:C01DFE44 04E61025 04E13005 04E14005 04E15025
%....0...@..%P..<BR>0010:C01DFE54 04E16005 04DF7005 04DF8025 00000000
.`...p..%.......<BR>0010:C01DFE64 04E1A025 00F254C4 00000000 00000000
%....T..........<BR>0010:C01DFE74 00000000 00000000 04E43005 04E44005
.........0...@..<BR>0010:C01DFE84 00F254D2 00F254D4 00000000 00000000
.T...T..........<BR>...<BR>// 0xc01dfe08 处的值为 04E63005
,其中高20bit是物理页的页号,低12bit是标志<BR>// 所以 ntdll .text 节中的 77F82000-77F82FFF 这一页
映射到了物理页 04E63 <BR>//(物理地址 04E63000-04E63FFF )<BR><BR>可以看到,两个进程的地址空间
77F82000-77F82FFF 映射了相同的物理页。<BR><BR><B>Prototype PTE(原型PTE)</B></FONT>
<P><FONT face=宋体> 和共享的实现有着密切关系的一个数据结构是 页帧号数据库项 ( PFN
DataBase Entry ) ,对处于Active(Valid) 状态的物理页,该物理页的 PFN DataBase Entry 的 +08
处,4个字节,是 share
count,共享计数。<BR><BR>下面我们分析一下进程间共享物理页的几种情况。<BR><BR>进程1第一次将一些可以被共享的内容读入一个物理页。于是进程1相应的PTE有效,并指向这个物理页。被共享的物理页对应的
PFN DataBase Entry 状态为 Active(Valid) ,share count
值为1。<BR><BR>进程2需要共享这个物理页,于是进程2相应的PTE有效,并指向这个物理页。由于这个物理页现在被进程1和进程2共享,所以对应的
PFN DataBase Entry 的 share count
的值加1,变为2。<BR><BR>进程3需要共享这个物理页,于是进程3相应的PTE有效,并指向这个物理页。这个物理页对应的 PFN DataBase
Entry 的 share count 的值加1,变为3。<BR><BR>进程2修整 Working Set (工作集),决定把该页从自己的
Working Set 中移出,于是相应的PTE无效。这个物理页对应的 PFN DataBase Entry 的 share count
的值减1,变为2。<BR><BR>进程1结束,这个物理页对应的 PFN DataBase Entry 的 share count
的值又减1,变为1。<BR><BR>进程2又需要访问这个共享页。于是PTE重新有效,并指向这个物理页。物理页的 share count
加1,又变为2。<BR><BR>进程2,进程3,修整 Working Set (工作集),都把这页从 Working Set
中移出,于是两个进程相应的PTE都无效。物理页的 share count 从 2 变成了 0。当变成0时,该物理页的状态从
Active(Valid) 变成了 Standby,链入了 Standby
链,不过该物理页中的内容不会被改变。<BR><BR>进程2又需要访问这个共享页。由于该物理页在 Standby
链上,内容没有被改变。于是直接取回该物理页,PTE重新有效并指向这个物理页。物理页状态从 Standby 变为 Active(Valid) ,
share count 变为1。<BR><BR>进程2又修整 Working Set (工作集),决定把该页从自己的 Working Set
中移出,于是相应的PTE无效。这个物理页的 share count 的值减1,变为0。当变成0时,该物理页的状态从 Active(Valid)
变成了 Standby,链入了 Standby 链,不过该物理页中的内容不会被改变。<BR><BR>系统需要内存,从 Standby
链上取走了该物理页。<BR><BR>进程2又需要访问这个共享页。没有物理页有原来的数据了,于是分配一个新的物理页,从文件中将数据读入。于是进程2相应的PTE有效,并指向这个新的物理页。这个物理页对应的
PFN DataBase Entry 状态为Active(Valid) ,share count
值为1。<BR><BR> 需要强调的一点是,只有被共享的物理页的 share count 减为 0
时,才能被移入 Standby 链,然后被用来做其他事。如果 share count
不为0,就说明还有进程在使用这个物理页,如果这时把这个物理页移入 Standby
链,可能会有非常严重的后果。比如放着被共享的dll的程序代码的一个物理页,被多个进程共享,而其中一个结束,share count
减1但不为0,这时如果把这个物理页移入 Standby 链,系统又把这个物理页给清零,放入 Zeroed
链中待用。而这时其他几个进程的相应的PTE仍然是有效的,并且指向这个本来该是程序代码的物理页。当这几个进程执行这里的代码的时候,将会彻底出错。如果
share count 为0 ,所有进程相应的 PTE 都已经无效了。</FONT>
<P><FONT face=宋体> 共享机制还有一个问题。一个被共享的物理页 share count 为0
,并且已经被用来做其他事,所有进程相应的 PTE 都已经无效了。当一个进程访问相应 PTE
对应的地址空间,系统会分配一个新的物理页,从文件中将数据读入新的物理页,相应的PTE有效,并指向这个新的物理页,这样这个进程就可以访问这个共享页。但是其他的进程无法知道这个新的物理页,他们相应的PTE仍然无效。他们如果也重新访问这个共享页,他们将如何得知新的物理页来填入PTE?Win2k
使用 Prototype PTE 来解决这个问题。</FONT>
<P><FONT face=宋体>
对于一个载入内存的应用程序,载入内存的动态链接库,或者一个文件映射,他们都有可能被共享。比如一个可能被共享的东西,映射到地址空间,需要100页,那么每页都有一个相应的
Prototype PTE
(4个字节)。一个进程把这个100页的共享的东西映射到地址空间,需要用100个PTE。这100个PTE中,有效的PTE指向共享的物理页,无效的PTE将指向相应的
Prototype PTE ,Prototype PTE
指向物理页。这样一来,当遇到前面的问题,一个进程重新访问一个共享页,而这个共享页原来映射的物理页已经做其他事,系统分配新的物理页,把进程相应的PTE指向新的物理页,并把PTE变为有效之后,也把
Prototype PTE 指向新的物理页。其他的进程重新访问这个共享页时,他们的PTE是无效的,并且指向 Prototype PTE ,而
Prototype PTE
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -