nt内核里的堆管理(1):关键结构

严格的说,用户态和内核态都有堆管理相关的内容,两者用的是同一份代码,稍微有些不同的地方就用宏隔开。在windows上写c程序会有不止一个的“堆管理器”介入,比如malloc, free用c runtime的堆管理器;用户态的HeapAlloc,HeadFree等函数用ntdll.dll里的堆管理器;内核态的RtlHeapAlloc,RtlHeapFree等用ntoskrnl.exe里的堆管理器。感觉蛮混乱的,相比之下linux内核里没有所谓的“堆管理器”,堆相关操作全在glibc(linux上的c runtime)里实现,内核中要动态申请(小块)内存,用的是kmalloc函数,而且申请到的是物理内存。gussing.cnblogs.com

堆管理的实质是:系统一次性批发很大一块内存留着,然后慢慢零售给应用程序。应用程序释放的内存也不直接还给系统的虚存管理系统,而是先给堆管理器,等攒够一块大的再一次性归还。有4个比较重要的队列负责实现此种批发零售:Segment队列,LookAside队列组,FreeList队列组,以及VirtualAllocatedBlock队列。gussing.cnblogs.com

Segment队列是一个静态数组,大小为64,充当批发商的角色,其他队列保留的内存其实都在Segment数组里,它们只是再引用而已。gussing.cnblogs.com

FreeList队列组是一个内存块的缓冲区域,拥有128个队列。应用程序释放的内存会先保留在FreeList里,之后程序再申请内存会先找FreeList里保留的。比如某程序释放了6单位大小的内存,它将被插入到FreeList[6]队列里。下次程序又申请6单位大小的内存,就可以直接取FreeList[6]队列里的内存块使用的。当然,实际情况比这个要稍微复杂些,因为你申请6单位大小内存的时候FreeList[6]可能是空的,这时候你就需要顺着FreeList[7],FreeList[8]这样一路找下去,直到找到合适的块(假设为9)为止。然后堆管理器会把这个块切割成两份,一份6单位大小返回给程序;另一份3单位大小的额外内存。原本这3单位全是可以直接使用的,现在第一单位被征用作为块头用于管理,然后插入到FreeList[3]中去。FreeList队列组中,第0号队列是比较特殊的,因为堆管理器里保存的内存块都带1单位的管理域,它的大小最小也就是1,不可能是0,于是0号队列就被空出来了。再考虑到FreeList中只有128个数组,大于128的内存块就没地方去了,正好FreeList[0]空着,就放到那里去吧。所以FreeList[0]中保留了大于128,小于某阈值的内存块。gussing.cnblogs.com

LookAside队列组长的和FreeList队列组很像,也是128个队列,但区别在于LookAside队列不会迁就应用程序,程序在这个队列里申请内存,有合适的就给你,没合适的就拉倒,你想让他把一块大的分割出来给你?对不起,办不到。这么做可以缓解内存碎片的问题:倘若一个程序申请释放小块内存很多很多次,那么FreeList里存着的就基本上全是小块内存了,这时候你要申请一块大的,即使内存其实还足够多,堆管理器也没法拿到合适的。而LookAside队列这样不给你分割的,即使你申请释放小内存很多次,对大内存也一点影响没有。gussing.cnblogs.com

应用程序每次操作的内存大小一般都是很小的,几十字节,几百k都是最常见的,但你也阻止不了人家申请巨大内存啊,比如有个人就是要申请20M内存,你还能不给他?这么大的内存块,FreeList和LookAside是没地方给他保存了(FreeList[0]中保留的也必须小于某阈值啊,不可能什么样的大内存都给你放),堆管理器就索性绕过所有的队列容器啥的,直接给它申请虚存,,申请到的内存就放在VirtualAllocatedBlock队列中。gussing.cnblogs.com

堆管理器的关键数据结构就是这些,围绕这些结构进行的具体操作,我们以后再谈。gussing.cnblogs.com

你可能感兴趣的:(管理)