Chinaunix首页 | 论坛 | 博客
  • 博客访问: 315568
  • 博文数量: 174
  • 博客积分: 3061
  • 博客等级: 中校
  • 技术积分: 1740
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-04 22:43
文章分类

全部博文(174)

文章存档

2011年(54)

2010年(14)

2009年(30)

2008年(26)

2007年(27)

2006年(23)

我的朋友

分类: WINDOWS

2009-11-04 11:50:33

 

先从理论上讲下。。。

内存管理器

1. 将一个进程的虚拟地址翻译转化为物理地址,这样当进程中的代码引用到虚拟地址时候就可以正确访问物理地址。(虚拟地址空间上被提交的物理地址集合成为工作集)

2. 当物理内存被过度提交时候,即物理内存已经不够时候,将会进行swap处理。

3. 内存映射文件(内存区对象,section object)

4. copy-on-write

5. AWE , 地址窗口扩展

 

内存管理器由如下几个部件组成

1. 工作集管理器

   当空闲内存的数量降低到某个特定阀值下时候;或者每1s钟掉一次,执行工作集修剪,页面变老等。

2. 进程/栈交换器

   执行进程栈和内核线程栈的换入换出操作。

3. 已修改页面写出器

   将修改列表上的dirty page写回到合适的页面文件。

4. 映射页面写出器

    将映射文件中的dirty page写到磁盘。

 

 

下面讲一些我的总结。。

 一个进程的地址空间中的页面,分为free,reserved,commit。

这个可以通过观察process explorer / vmmap ,前者可以看到commit mem/phy mem.

后者可以看到总共保留的空间,提交的空间,workingset,这样能够比较好的直观查看.

 

 

    另外commit的页面分为share,private,或者mapped(该内存区可能被其他进程映射了,或者没有).
   
    内存管理器中用以实现共享内存的是内存区对象也成为file mapping object。
 
    共享内存的原理就是,多个进程可以共享同一块物理内存,而它们在各自进程内的地址依然是私有的。
据个例子来说,多个进程同时加载一个dll,那么实际上此dll的代码页面将被自动共享,即加载到物理内存后,被各个不同的进程映射访问。
 
    内存区对象并不==共享内存,当内存区对象连接到一个磁盘文件时候成为映射文件;连接到内存时候便可提供共享内存。
 
    从实现来说,CreateFileMapping 如果传入INVALID_HANDLE_VALUE 则表示将创建一个连接到内存的内存区对象,而其他进程就可以通过OpenFileMapping 来打开此内存区对象,同时将其map到本身的地址空间中。
 
    至于这个mapviewoffilemapping的过程,大致可以推断:
   1) 保留一个连续的地址空间
   2) 将进程的页表修改或者添加,使得刚保留的虚拟地址解释指向此特定的物理地址

下面做一个例子,查看内存映射文件

下面是一个例子

表示是基于内存的。

另外ieframe.dll 分为有image和data 映射。

Name Mapping Description Version Base Size Image Base WS Shareable
 Data   0x22E7000 0x79000 0x0  K
 Data   0x2360000 0x2000 0x0  K
ieframe.dll Image Internet Explorer 8.00.6001.18876 0x3ECA0000 0xA93000 0x3ECA0000 72 K
ieframe.dll Data Internet Explorer 8.00.6001.18876 0x7010000 0xE000 0x0 12 K

另外image并不仅仅映射内存而已,也往往作为handle被打开
比如下例。。
 
File C:\WINDOWS\system32\stdole2.tlb 0x00120089 0x893C2598 R-- 0x898
File C:\WINDOWS\system32\Macromed\Flash\Flash10c.ocx 0x00120089 0x88E3D258 R-- 0x91C
File C:\WINDOWS\system32\ieframe.dll 0x00120089 0x88E99690 R-- 0xE70
File C:\WINDOWS\Microsoft.NET\Framework 0x00100001 0x893532E8 RW- 0xE6C
 
上面这些信息其实可以通过procexplorer的dll、handle来观察。
 
 
系统页目录,页表。
 
1. windows存在一份描述系统空间的页表PTEs,这份是所有进程共享的
2. windows中每个进程都有一个页目录表,这份页目录表PDE,PDE中部分PTE是本进程私有的,而部分则指向系统的页表。
3. windows中也目录始终被map到0xC0300000,PAE开启的话则map到0xC0600000
 
 
虚拟地址描述符VAD
 
一颗自平衡二叉树。
 
即一段最大的地址范围,作为一个节点,描述有地址范围,访问权限,是否集成。
然后较这段地址小的作为它的左节点;否则右节点;构成了二叉树。
 
kd> !vad 827db860
VAD     level      start      end    commit
8279fa78 (10)         40       7f        26 Private      READWRITE
82aa3570 ( 9)         80       82         0 Mapped       READONLY
829ff7e8 (10)        270      275         0 Mapped       READONLY
82a06bc8 ( 9)        280      2c0         0 Mapped       READONLY
82996ac8 ( 6)        400      408         2 Mapped  Exe  EXECUTE_WRITECOPY
....
Total VADs:   261  average level:    9  maximum depth: 16
 
内存区对象 section object
内存区对象可以被映射到页面文件(mem),或者磁盘中的另外一个文件.
 
一个内存区对象有如下属性:
 
最大尺寸
页面保护
页面文件/映射文件
基内存/非基内存   (基内存指sharemem在所有进程内虚拟地址一致,否则不一定一致 )
 
观察映射文件的情况。
!memusage
 
!ca controladdr
 
Control Valid Standby Dirty Shared Locked PageTables  name
828fa7b8   244   5388     0     0     0     0  mapped_file( mscorwks.dll )
82a15e38     0    100     0     0     0     0  mapped_file( helpsvc.exe )
82acf430   304    176     0    60     0     0  mapped_file( msvcr80.dll )
827a1f60     0     32     0     0     0     0  mapped_file( LINK.EXE-18E29C64.pf )
82bc5ca8  6508   1184     0     0     0     0  mapped_file( $Mft )
829cdd98   388    216     0   140     0     0  mapped_file( wininet.dll )
8280da58     0     96     0     0     0     0  mapped_file( A8FABA189DB7D25FBA7CAC806625FD30 )
 
....................
 
 
 
内存"优化"
1. 通过大量申请内存,造成可用内存瞬间大量减少
2. 其他进程的工作集被裁剪,进入swapfile
3. 申请进程瞬间释放内存
4. 短时间内出现了大量的可用内存
 
不过这个代码是压缩其他进程的工作集以及系统可用内存为代价的。
将可能大大降低其它进程的性能。
 
 
任务管理器的 commit 表示已经提交的虚拟内存总量,而物理内存的可用,大致可以推断出pagefile占了多少。
 
最后提一个问题,请分析内存泄露和物理内存的使用以及虚拟内存的使用量的关系?
 
A>> 将直接造成虚拟内存使用量上升,但是物理内存不一定。
 
 
交换分区大小=0可行吗?
A>> 不可行,尽管windows允许你设置不设置交换分区。但是一般来说在windows运行过程中,windows将有可能提示你,“您系统的虚拟内存过低,windows将自动帮您扩展".
 
根据我的理解,windows在收到一个内存commit之后,会根据某种策略或者算法,来确定是否需要对已有的进程的工作集workingset进行裁剪,所谓裁剪就是指回收那些进程的workingset将其交换入交换文件,然后释放物理内存,将这部分+可用的物理内存来满足本次内存commit。
 
因此即时你设置了交换分区=0,windows依然在内部以某种我现在还不知道的方式在使用交换分区。
 
 
x86内存地址空间
 
特别说明一点,ntdll.dll 对于每个进程而言地址都市一致的。
 
 
今天观察vmmap和win 7的任务管理器的内存列表,发现了几个不解的地方。
 
先说一下可以理解的:
1. total workingset 表示本进程占用的物理内存,包括私有的和share。
2. peek workingset
3. shared/sharable workingset.
 
不解的地方:
1. vmmap 的size、commit到底表示啥含义,看了help还是有点不太理解。
2. win7的任务管理器中有一个 “已提交内存” 帮助中显示这是内存 - 提交大小
 为某进程使用而保留的虚拟内存的数量。  从vmmap中检查后发现,这个貌似既不是reserved,也不是所有已经提交的内存,因为这个值往往还比较小。
 
 
然后再说一下自己的理解,其实我想一个进程的内存是用,我们最关注的不外乎:
1. 它使用了多少物理内存,这其中又分为它私有的多少,共享的多少。
2. 它究竟总共使用了多少虚拟内存,这个我的认知认为应该是它到底提交了多少内存。
 
第一点其实已经可以知道;
第二点,从表面看起来应该是vmmap的commit字段。
 而vmmap的size-commited 貌似也等于reserved的大小(注意这一点得到了证实,确实是).
 那么这儿的疑问是commit-total ws 的大小表示啥? 从我反复查看vmmap,我得到的一个可能的结论是
两者的差值(这个其实看detail view,看某个行,然后这个行commit和total ws不一样就可以),是已经提交的,但是没有分配给物理存储器的地址空间。 但是这个貌似又不太像书上说的。
 
我仔细查看vmmap后,再次推论, 比如某一行,commit显示是1000k,total ws显示 100k,而这个区域的属性是read,那么剩下的900k到哪儿去了呢?
 
这儿有两个推断:
1. 900k是disk
2. 900k实际上还是reserved ,不过vmmap可能是整体保留了一块区域,然后只是提交了其中的一部分。
 
不过这个可以继续做测试验证。。。
 
验证的时候,准备做最多两个例子:
1. 关闭windows的swap支持,然后看commited 和total WS 是否一致,如果一致那么就证实了commited - total ws 就是 disk file. 否则继续。。
2. 写一个程序,预分配一段地址空间,然后提交其中的一段,通过vmmap 来观察地址的属性。
 
 
今天检查后,发现1这种理解不正确,即os hide了disk swap和物理内存的差异,你看到的ws就是包含两者。 因此将os修改为不使用swap实际上不会对结果造成影响,commited和total ws之间的差异还是没有解。
 
继续进行第二个例子,发现这个操作完全正常,即如果我分配一个region,然后提交,然后回收,这些操作都能够正常的在vmmap看到对应的结果,但是依然无法解释这个差异。
 
 
最后我自己写了一个简单的程序:
 

PVOID pMem = ::VirtualAlloc(NULL , 1024 * 100 , MEM_RESERVE , PAGE_NOACCESS);

  while(true)
  {
   int i = ::getchar();
   if(i == L'c')
   {
    ::VirtualAlloc((LPBYTE)pMem + 1024 * 50 , 10 * 1024 , MEM_COMMIT, PAGE_READWRITE);
    *((LPBYTE)pMem +1024 * 50) = 1;

    //*((LPBYTE)pMem +1024 * 70) = 1;
   }
   else if(i == L'f')
   {
    ::VirtualFree((LPBYTE)pMem + 1024 * 50 , 10*1024 , MEM_DECOMMIT);
   }
   else if(i == L'r')
   {
    ::VirtualFree(pMem  , 0 , MEM_RELEASE);
   }
  }

 

当 ::VirtualAlloc((LPBYTE)pMem + 1024 * 50 , 10 * 1024 , MEM_COMMIT, PAGE_READWRITE);
 执行后,我发现COMMITED 确实变为 12k,4k一个page.

但是total ws = 0, 然后我使用

*((LPBYTE)pMem +1024 * 50) = 1;

再观察,发现total ws = 4kb

哦,这样结果出来了,原来是虚拟内存的缺页机制在发生作用。

1. 每个虚拟内存page,有一个属性 reserve , free , commited 。

2. 每个虚拟内存页,有一个present标记,表示是否在物理内存中;

 

因此如果我知识commit一块内存,并不代表一定有这么多物理内存需要马上提交。

如果我始终不使用这块内存,则不会实际分配。

关于这个推论,可以继续通过lkd,来查看页表的变化来确认是否真是如此。。。

开始实验:
1. 准备win7下的local kerneldebug,可以看我另外一片文章,在win7下为了方便观察,需要关闭pae,开启local debug.
   1) host 机为win7
   2) target机为 xpmode
   3) 双方采用虚拟串口连接
 
2. 启动test程序 如同上面的source code。 在每次对vm操作的时候,观察页表的变化.
 
 当virtualalloca reserve完后,我们来看看现象。
 
  
 
 
 
通过第一个步骤,结合vmmap的help,大致就可以看到我之前的推断是ok的,即size 表示所有的分配大小,不管是reserve还是commit。
 
 
这个时候到了用windbg来查看这个地址的时候了。
 
我们看到了pmem=0xd90000=00000000 11011001 00000000 00000000
那么也就是说它的高10位地址=0x3,表示它在页目录表中的offset=3;
中间10bit=01 1001 0000 = 0x190,这也是页表中的offset;
最后是低10位也是内页便宜 = 0
 
那么我们在kernel模式中看看这个test.exe的页目录地址是啥?
 
 
这儿就看到了dirbase=29d6f000 , 注意这个是物理地址.
 
那么我们就知道了待访问的目标0xd90000,所在的页表地址是
29d6f000 + 3 * 4
 
 
也就是图中的0x2a414067
 
这个地址的高20位表示页的实际地址,滴12bit=0x67表示页的属性。
 
这个属性这儿就不具体分析了。
 
然后就是看实际的页地址了 2a414000+ 0x190 * 4
 
这儿我们看到地址处的页地址=0,即还不存在。
 
那么我们继续运行代码
 
,即commit一块内存.
 
然后再来看windbg的表现
 
 
我们注意到,页的属性页内容从0--->0x80;
 
这儿付PDE的格式:
║ PAGE TABLE ADDRESS 31..12 │ AVAIL │G | PS│0 │A│PCD| PWT│U/S│R/W│P║
P - PRESENT
PS - 0 for 4KB;1 for 4MB
A - accessed or not
R/W - READ/WRITE
U/S - USER/SUPERVISOR
D - DIRTY
PTE格式(4KB)
║ PAGE FRAME ADDRESS 31..12 │ AVAIL │G |PAT│D│A│PCD|PWT│U/S│R/W│P║
P - PRESENT
R/W - READ/WRITE
U/S - USER/SUPERVISOR
D - DIRTY
AVAIL - AVAILABLE FOR SYSTEMS PROGRAMMER USE
 
那么这儿的0x80 是上可以看到PAT=1 , 其他=0
P=0 表示这个页不存在,至于PAT的含义我目前也没有搞清楚,不过这个不影响我们继续分析。
 
搞到这儿大致可以看到,windows在我们reserve一段地址的时候实际上从本例来看是保留了3个4KB page,
而这个也符合在vmmpa的图像
 
 
 
这儿我们注意到windows将内存的访问属性修改为了NOACCESS。
 
下面我们真正写入一个byte
 
 
 
从vmmap的结果看,windows在我们访问某一个byte的时候,真正提交了4kb的物理内存。并且如同我们的code那样,将访问属性修改为RW。
 
这时候我们看看windbg的report
上图表现了:
1. PTE的属性发生了 变化从0x80 -->0x2b367067, 这儿低12bit表示属性,显然P=1,表示已经在内存中了。
2. 高20bit 是页的真正地址,+ 页内offset=0,我们看到了我们写入的那个值=1了,呵呵。
 
这样从workingset,到vmmap的不理解。。。。
阅读(4222) | 评论(0) | 转发(1) |
0

上一篇:保护模式的理解!

下一篇:中断/异常

给主人留下些什么吧!~~