有了前面的介绍,堆管理的大体框架已经清楚了,但为了使内容完整,我们还是需要看看释放内存时具体都干了些什么。gussing.cnblogs.com
函数RtlFreeHeap接受一个PVOID的参数作为内存地址,却不需要指定内存的大小。这是一个很有趣的地方,malloc,HeapAlloc等申请内存的函数都会指定所需内存的大小,但相应的free函数和HeapFree函数都不需要指定大小。我在面试应聘者的时候很喜欢问这个问题:请问为什么不需要指定就可以释放正确大小的内存?很遗憾照着我的统计数据,9成以上的人回答不出来,这9成里面又有9成压根就没去想过这些问题,也不知道是不乐意,不屑与,还是不舍得去关心。个人认为作为IT从业人员,不管是铁了心要一直当码工也好还是有更高的最求也罢,即使是出于职业道德的考虑偶尔也是要去了解下代码背后的秘密的。偏题了,让我们继续说正题吧。gussing.cnblogs.com
如上面所说,RtlFreeHeap函数接受PVOID类型的函数地址却不需要内存大小,那它如何确定本次需要释放多大内存呢?答案就是:每次申请到的内存都有带一个管理头,里面有指定本内存块的大小gussing.cnblogs.com
返回给用户的内存块,是管理头之后的部分,也就是说,每次内存分配的时候实际分配的大小比所要求的要大。考虑到c runtime也有自己的堆管理器,这部分额外内存可能会不小。free函数和RtlFreeHeap函数就是从这些额外的管理区域里读出内存块大小的。gussing.cnblogs.com
RtlFreeHeap函数接收到用户内存的地址后,做的第一件事情就是将地址往前移8字节,指到内存块的管理头BusyBlock。然后检查内存的完整性,比如有没有溢出之类,具体是检查BusyBlock地址的末3未,因为堆申请是以8字节为单位的,所以末3位一定都是0,否则就是被破坏了,需要理解抛异常通知进程。实际的溢出检测比这个复杂多,暂且不谈。经过一些个检测后,堆管理器认为本次操作安全,可以继续下去,于是又根据BusyBlock->Size进行的分情况处理又开始了,需要注意的是BusyBlock->Size是以8为单位的内存区域的数量,就是上一次所说的index:
表明此堆块是直接从虚存里申请出来的,需要直接调用ZwFreeVirtualMemory函数释放,并从VirtualAllocBlock队列里移除相应的项。否则:
先检测LookAsideList是否为空,不为空则插入到LookAsideList[BusyBLock->Size]中去。若为空,则搜索FreeList[BusyBlock->Size]队列,看此队列中时候有其他block,若有则取出,与BusyBlock合并,合并完后重新根据BusyBlock->Size大小分类进行。之所以要与旧block合并,是为了最大限度的减少内存碎片。
普通的FreeList已经没法容纳这么大的堆块了,所以需要插入到FreeList[0]中去。
将BusyBlock分块,分别插入到相应的FreeList里去
堆里已经积累了太多内存,需要调用RtlpDeCommitFreeBlock,释放虚存。RtlpDeCommitFreeBlock函数会把BusyBlock里占满一整个page的内存释放掉,余下的还是放回到堆管理器里去。如下图:
区域2,3,4能占满一整个page所以释放,1和5放回到堆管理器里。这么做的原因是虚存的申请和释放都是以page为单位的,这是最小粒度。
到这里为止,堆的申请和释放都完了。要完整了解堆管理器至少还需要看RtlCreateHeap和RtlDeleteHeap两个函数,有兴趣的同学可以自行围观ReactOS相关代码。