Fantacity

Stand Alone Complex

STL allocator(3)

接下来看一下第二级配置器最主要的两个部件:refill机制和内存池(memory pool)。

refill机制

refill机制的工作时机为,当free-list对应的大小位置没有可用的区块时,就调用refill函数来重新补充节点,新的空间将取自内存池(经由chunk_alloc()完成)。缺省取得20个新节点,但万一内存池空间不足,获得的节点数可能小于20,下面是refill的代码。
refill函数
refill()函数通过chunk_alloc()函数获得可用的内存块,把第一个返回给客户端,剩余的n-1个(n-1 >= 0 && n - 1 <= 19)依次链接放入对应的free-list位置,供下次使用。注意,这里的chunk_alloc()返回的地址至少有1个可用的块,如果一个可用的块都没有,那么还是不会返回,而是直接抛出异常,在下面会详细描述。

内存池(memory pool)

从内存池中取空间个free-list使用,是chunk_alloc()的工作:
chunck_alloc代码
我们一点点来代码,首先是if (bytes_left >= total_bytes)的情况下,那么把内存池的起始地址往后移动total_bytes的量,并返回先前的内存池起始地址,代表把这一块内存分配出去了。

接着是else if (bytes_left >= size)表示内存是有剩余一个以上,但是小于20个的区块,这个时候也把这些区块分配出去。但是要修改nobjs的值,因为nobjs是传引用,客户端需要知道到底分配了几个区块。

下面的情况就是bytes_left < size表示内存池剩余空间连一个也不够分配了。首先计算bytes_to_get他的值为需求量的两倍加上一个每随分配次数而增加的附加量。在这之前,如果if (bytes_left > 0)先把内存池剩余零头先分配出去,放入适合的free-list中。(可以看到,这种情况下,内存池中的剩余量rest必然满足0 < rest < size),所以可以放入到某个合适的free-list当中最为某个节点。

接下去通过malloc来分配所需的内存,如果malloc成功,则修正start_freeend_free的值,并递归调用chunck_alloc()来重新分配内存块。否则如果malloc失败的话,可见系统内存已经给不出这么多内存了,这个时候尝试在free-list当中寻找空余的的内存块,把他们重新回收回来,并分配给急需使用的free-list(回收之后递归调用chunck_alloc())。

如果连这样也失败的话,已经没有内存可以用了,这个时候调用第一级配置器,尝试触发oom机制来获取可用的内存,如果失败则抛出异常,程序结束;如果成功则又有可用的内存补充进来了,接下去仍然递归调用chunck_alloc()来修正nobjs。

小结

至此,我们把std::alloc都讲完了。再来梳理一下,std::alloc包括两级配置器,第一级配置器直接使用malloc和free来分配和释放内存,第二级配置器对于大于182bytes的内存申请直接调用第一级配置器,否则的话从free-list当中取出符合的节点分配出去。而free-list的节点则从内存池中补充,从而在一定程度上避免了大量的小内存申请造成的内存碎片化问题。

如果stl定义了__USE_MALLOC,则使用第一级配置器,否则使用第二级配置器。SGI默认使用第二级配置器。