内存分配与释放.docx
《内存分配与释放.docx》由会员分享,可在线阅读,更多相关《内存分配与释放.docx(16页珍藏版)》请在冰豆网上搜索。
内存分配与释放
内存分配与释放
1.Windows内存分配与释放提供了缓存机制,由空闲链表方式提供对非分页内存管理,由位图方式提供分页内存管理
同时提供相应的快查表,将最近释放的页面放入快查链表中,再次申请的时候可以快速的从快查表中取得数据.
2.在基本的分页内存管理与非分页内存管理上层,windows暴露了相关的内存分配释放接口(ExAllocatePoolWithTag
1.ExFreePool)并提供相应的执行体内存管理,其采用快查表形式将空闲链表划分到ListHeads数组中,并给每个CUP提供相应
的缓存链表。
执行体内存管理采下层提供的内存管理函数,分配基本的内存页,并裁剪到快查表中,如果ExAllocatePoolWithTag分配的内存
大于阈值则直接采用下层提供的内存分配接口。
3.
内存地址转换:
1.看下面几个函数
#defineMiGetPdeAddress(va)((PMMPTE)(((((ULONG)(va))>>22)<<2)+PDE_BASE))这里PDE_BASE为0xc0300000
#defineMiGetPteAddress(va)((PMMPTE)(((((ULONG)(va))>>12)<<2)+PTE_BASE))这里PTE_BASE为0xc0000000
32位windows中PAE没有打开时页目录的地址为0xc0300000,CR3寄存器中也保存着这个0xc0300000
举个例子
kd>!
ptec1234567
VAc1234567
PDEatC0300C10PTEatC03048D0
contains17CE5963contains0156C4E8
pfn17ce5-G-DA--KWEVnotvalid
Proto:
E155B1D0
虚拟地址0xc1234567寻址过程:
先计算出页目录索引为0x304,每个地址是4字节,所以他的页目录项的虚拟地址是0xc0300000+0x304*4=0xC0300C10
其中保存的内容0x17CE5963,17ce5是他的页帧索引(页表的物理页面,这个物理页面对应的虚拟地址是0xC0304000到0xC0304FFF)。
由公式我们得到PTE的虚拟地址是0xC03048D0即PTE基地址0xc0000000+每页表的大小(0x1000)*页目录索引(0x304)+页表索引(0x234)*每个地址大小4字节)。
2.Windows在初始化内存管理时采用了自映射机制
首先,看下面的一个宏:
#defineMiGetVirtualAddressMappedByPte(PTE)((PVOID)((ULONG)(PTE)<<10))
此宏的含义是,给定一个PTE的虚拟地址,返回该PTE所指页面的虚拟地址。
举例而言,假设PTE的地址(虚拟地址)为0xc0390c84,即1100,0000,00|11,1001,0000,|1100,1000,0100,这里“|”符号将它分为页目录索引、页表索引、页内偏移三部分。
在Windows中,页目录(CR3寄存器)的地址是0xc0300000,所以,处理器在访问该PTE的时候,首先从页目录页面中,找到1100,0000,00项,即第0x300项。
这一项由系统特别设置好,它指向页目录自身(后面进一步解释为什么这么设置)。
接下来查找0xc0390c84的页表索引,即11,1001,0000,处理器继续在页目录页面中查找,找到11,1001,0000,即第0x390项,这一项指向一个页表页面。
最后,处理器再根据页内偏移1100,1000,0100,即0xc84,定位到第0x321项。
此PTE的寻址过程如下图所示。
按照MiGetVirtualAddressMappedByPte,将0xc0390c84,左移10位,变成0xe4321000,即1110,0100,00|11,0010,0001,|0000,0000,0000,这样得到页目录索引1110,0100,00,即0x390;页表索引为11,0010,0001,即0x321;页内偏移为0。
可以看到,这里复用了PTE寻址过程中的两次查表步骤。
此PTE所指页面的寻址过程如下图所示。
MiGetVirtualAddressMappedByPte宏之所以能够工作的关键之处在于,页目录页面(虚拟地址为0xc0300000)的0x300项指向其自身。
即下图的结构。
由此可以明白,页目录地址0xc0300000和这里的0x300项绝不是偶然的,而是精心选择的(但并非唯一)。
文件缓存分析
一个典型文件创建缓存过程:
1.当文件对象第一次被缓存读写时,文件系统调用CcInitializeCacheMap初始化缓存
2.如果FileObject->SectionObjectPointer->SharedCacheMap==NULL,从NonPagedPool分配SharedCacheMap,初始化其中一部分域。
3.调用MmCreateSection创建SharedCacheMap->Section,MmCreateSection中:
●首先检查是否FileObject->SectionObjectPointer->DataSectionObject已经存在
●没有存在从NonPagedPool分配一个NewControlArea大小为ControlAreaSize=sizeof(CONTROL_AREA)+sizeof(MSUBSECTION),设置到FileObject->SectionObjectPointer->DataSectionObject
●创建Segment,这里以MiCreateDataFileMap为例子:
*检查CcInitializeCacheMap传入的文件占用大小是不是大于系统定义的最大Section大小,计算文件需要的pte数量
*从PagedPool创建NewSegment,类型是MAPPED_FILE_SEGMENT,然后把源文件打散成许多块(Subsection)
*在NonPagedPool中分配Subsection链接在NewControlArea后面
*初始化NewControlArea剩下的域,得到第一个Subsection,设置其中域,找到所有的Subsection初始化他们
●创建SECTION对象,并且初始化
4.创建Section完成后,如果是streamobject是不可能被映射的,这时候关闭modwrite标志
5.调用CcCreateVacbArray创建VACB索引数组
6.………….
当缓存创建完成后,如果收到CcCopyRead
1.首先先用GetActiveVacb找到是否落在活动视图内,如果没有落在活动视图内调用CcGetVirtualAddress取得地址:
●用GetVacb取得FileOffset处的VACB,如果为NULL就调用CcGetVacbMiss从CcVacbFreeList中获得一个没有用的VACB
●调用MmMapViewInSystemCache映射视图到系统缓存:
*得到需要多少页面NumberOfPages,计算PteOffset与LastPteOffset
*LOCK_PFN锁定PFN,提升DPC,得到第一个空闲的系统缓存地址保存到PointerPte,更新第一个空闲地址MmFirstFreeSystemCache
*如果ControlArea->FilePointer不为空,调用MiAddViewsForSection分配原型pte(MappedSubsection->SubsectionBase=ProtoPtes)
*得到VACB对应缓存buffer的PointerPte的虚拟地址,设置相应的值(设置成原形PTE),返回
●设置这个VACB相应值并返回
2.得到了VACB对应CacheBuffer,按照页大小分几次操作.
3.校验内存是否是有效的地址,调用MmCheckCachedPageState:
●得到地址SystemCacheAddress的PTE(MiGetPteAddress()),根据PTE得到对应原形PTE(MiPteToProto())
●如果无效的调用MmAccessFault,MmAccessFault校验PDE,PTE,这里会得到PTE对应原形PTE然后调用MiDispatchFault去解决这个地址错误
*MiDispatchFault内部调用MiResolveProtoPteFault解决原形pte错误(注意PFN项的颜色,这里调用MiRemoveAnyPage得到一个物理页面).
*这里详细看下MiResolveProtoPteFault取得物理页面过程:
①MiGetInPageSupportBlock先分配一个读取块ReadBlockLocal,得到ReadBlockLocal->Page[0]地址.
②计算出EndPage=Page+ClusterSize,
③取得pte的颜色,根据颜色调用MiRemoveAnyPage取得物理页帧给ReadBlock->Page中每个成员赋值
④调用MiInitializeReadInProgressPfn初始化指定的pfn项成为转移,正在读入状态,设置原形pte为转移状态,指向物理页面
*调用完成MiResolveProtoPteFault后,原形pte设置成转移状态,pte指向物理页面,物理页面被设置成正在转移读入状态,这时候我们需要读入内容(STATUS_ISSUE_PAGING_IO)
*调用IoPageRead发出pagingio读入内容
在xp下解析一个文件映射结构
1.直接创建一个文件映射,FileObject的DataSectionObject被赋值
kd>dt0x81c2fd50_SECTION_OBJECT_POINTERS
ntdll!
_SECTION_OBJECT_POINTERS
+0x000DataSectionObject:
0x81cb3fa0Void
+0x004SharedCacheMap:
(null)
+0x008ImageSectionObject:
(null)
kd>dt_CONTROL_AREA0x81cb3fa0
nt!
_CONTROL_AREA
+0x000Segment:
0xe19092e0_SEGMENT
+0x004DereferenceList:
_LIST_ENTRY[0x0-0x0]
+0x00cNumberOfSectionReferences:
1
+0x010NumberOfPfnReferences:
0x10
+0x014NumberOfMappedViews:
1
+0x018NumberOfSubsections:
1
+0x01aFlushInProgressCount:
0
+0x01cNumberOfUserReferences:
2
+0x020u:
__unnamed
+0x024FilePointer:
0x820bdef0_FILE_OBJECT
+0x028WaitingForDeletion:
(null)
+0x02cModifiedWriteCount:
0
+0x02eNumberOfSystemCacheViews:
0
kd>dt0xe19092e0_SEGMENT
nt!
_SEGMENT
+0x000ControlArea:
0x81cb3fa0_CONTROL_AREA
+0x004TotalNumberOfPtes:
0x10
+0x008NonExtendedPtes:
0x10
+0x00cWritableUserReferences:
2
+0x010SizeOfSegment:
0x10000
+0x018SegmentPteTemplate:
_MMPTE
+0x020NumberOfCommittedPages:
0
+0x024ExtendInfo:
(null)
+0x028SystemImageBase:
(null)
+0x02cBasedAddress:
(null)
+0x030u1:
__unnamed
+0x034u2:
__unnamed
+0x038PrototypePte:
0x00020208_MMPTE
+0x040ThePtes:
[1]_MMPTE
2.从NumberOfSubsections=1看出被打散成一块,注意
kd>dt_SUBSECTION0x81cb3fa0+2e+2
nt!
_SUBSECTION
+0x000ControlArea:
0x81cb3fa0_CONTROL_AREA
+0x004u:
__unnamed
+0x008StartingSector:
0
+0x00cNumberOfFullSectors:
0x10
+0x010SubsectionBase:
0xe2072aa8_MMPTE
+0x014UnusedPtes:
0
+0x018PtesInSubsection:
0x10
+0x01cNextSubsection:
(null)
关于缓存的结构:
VACB被设计成了索引形式:
除了VACB外,还有一个用于表示锁定的内存(pin)的形式:
值得注意的是系统用512k也就是vacb视图的2倍大小来管理BCB视图。
每512k就在共享缓存视图的BcbList中加入一个LIST_ENTRY结构作为链表的头
kd>dt_SHARED_CACHE_MAP0x82404008
nt!
_SHARED_CACHE_MAP
+0x000NodeTypeCode:
0n767
+0x002NodeByteSize:
0n304
+0x004OpenCount:
1
+0x008FileSize:
_LARGE_INTEGER0x1490000
+0x010BcbList:
_LIST_ENTRY[0x8269d2a0-0x8269d158]
+0x018SectionSize:
_LARGE_INTEGER0x1500000
+0x020ValidDataLength:
_LARGE_INTEGER0x7fffffff`ffffffff
+0x028ValidDataGoal:
_LARGE_INTEGER0x7fffffff`ffffffff
+0x030InitialVacbs:
[4](null)
+0x040Vacbs:
0x8269d008->0x827813f0_VACB
+0x044FileObject:
0x82799220_FILE_OBJECT
+0x048ActiveVacb:
(null)
+0x04cNeedToZero:
(null)
+0x050ActivePage:
0
+0x054NeedToZeroPage:
0
+0x058ActiveVacbSpinLock:
0
+0x05cVacbActiveCount:
2
+0x060DirtyPages:
9
+0x064SharedCacheMapLinks:
_LIST_ENTRY[0x8206f204-0x8089d290]
+0x06cFlags:
0x605
+0x070Status:
0n0
+0x074Mbcb:
(null)
+0x078Section:
0xe1958410Void
+0x07cCreateEvent:
(null)
+0x080WaitOnActiveCount:
(null)
+0x084PagesToWrite:
0
+0x088BeyondLastFlush:
0n20500480
+0x090Callbacks:
0xf719e64c_CACHE_MANAGER_CALLBACKS
+0x094LazyWriteContext:
0x82641cf0Void
+0x098PrivateList:
_LIST_ENTRY[0x8240412c-0x8240412c]
+0x0a0LogHandle:
0xe17d4140Void
+0x0a4FlushToLsnRoutine:
0xf71b5870voidNtfs!
LfsFlushToLsn+0
+0x0a8DirtyPageThreshold:
0
+0x0acLazyWritePassCount:
0x141
+0x0b0UninitializeEvent:
(null)
+0x0b4NeedToZeroVacb:
(null)
+0x0b8BcbSpinLock:
0
+0x0bcReserved:
(null)
+0x0c0Event:
_KEVENT
+0x0d0VacbPushLock:
_EX_PUSH_LOCK
+0x0d8PrivateCacheMap:
_PRIVATE_CACHE_MAP
kd>dc0x8269d158这块内存分配的大小是文件大小/512k*8
8269d158824040188269d1608269d1588269d168.@@.`.i.X.i.h.i.
8269d1688269d1608269d1708269d168827098b0`.i.p.i.h.i...p.
8269d178827098b08269d1808269d1788269d188..p...i.x.i...i.
8269d1888269d1808269d1908269d18881ce0cd0..i...i...i.....
8269d19881ce0cd08269d1a08269d1988269d1a8......i...i...i.
8269d1a88269d1a08269d1b08269d1a88269d1b8..i...i...i...i.
8269d1b88269d1b08269d1c08269d1b88269d1c8..i...i...i...i.
8269d1c88269d1c08269d1d08269d1c88269d1d8..i...i...i...i.
上面红色的为插入的一个BCB项:
注意bcb是按照FileOffset降序插入的。
kd>dtnt!
_BCB81ce0cc0
+0x000Dummy:
_MBCB
+0x000NodeTypeCode:
0n765
+0x002Dirty:
0x1''
+0x003Reserved:
0''
+0x004ByteLength:
0x1000
+0x008FileOffset:
_LARGE_INTEGER0x3d3000
+0x010BcbLinks:
_LIST_ENTRY[0x8269d190-0x8269d198]
+0x018BeyondLastByte:
_LARGE_INTEGER0x3d4000
+0x020OldestLsn:
_LARGE_INTEGER0x2c1c6a65
+0x028NewestLsn:
_LARGE_INTEGER0x2c1c6e13
+0x030Vacb:
(null)
+0x034PinCount:
0
+0x038Resource:
_ERESOURCE
+0x070SharedCacheMap:
0x82404008_SHARED_CACHE_MAP
+0x074BaseAddress:
(null)
Pin的作用是锁定内存保证修改后不会被修改页面写出器与映射页面写出器写出。