内碎片:需要3字节,系统分配4字节,剩下的1字节就是内碎片
外碎片:由于不断申请内存和归还内存使得内存中剩下很多小片段的内存,无法被利用
作用:
(1)提高代码复用率,功能模块化。
(2)减少内存碎片问题。
(3)提高内存分配的效率。
(4)有内存不足时的应对措施。
(5)隐藏实际中对存储空间的分配及释放细节,确保所有被分配的存储空间都最终获得释放。
(5)考虑多线程状态。
考虑到小型区块可能导致的内存碎片问题,设置了两级空间配置器。分别为:一级空间配置器、二级空间配置器。当区块大于128字节,调用一级空间配置器;小于等于128字节,为了降低额外开销,用底层较复杂的二级空间配置器。
一级空间配置器
分为三个函数
Allocate用于分配空间,如果申请失败,用oom_alloc重新尝试申请。
Deallocate用于释放空间。
Reallocate用于根据需要自己调整已经存在的空间大小,如果申请调整失败,用oom_realloc尝试申请。
SGI二级空间配置器的原理是:当区块小于128字节,则以内存池(memory pool)管理,回收时管理一个用户归还的空间,类似于哈希桶。每次配置一块内存,并维护对应的自由链表(free_list)。为了方便管理,SGI二级配置器会对齐到8个字节。(例:需要30字节的空间,自动调整到32字节)。维护16个free_lists,各自管理大小分别为
8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128字节。
内存池开始位置:start_free
内存池结束位置:end_free
所需空间的大小:n
内存池大小:heap_size=end_free-start_free.
一次二级空间配置器的示例
以申请12字节的空间为例讲解一下二级空间配置器处理空间申请的过程-------12字节会被上调到8的整数倍,也就是16.
首先去对应自由链表里边找(通过字节数16对齐到计算出对应位置为1号自由链表),看自由链表对应位置有没有空间,如果有,就采用头删的方式将自由链表上挂接的第一个空间结点分配给用户使用,然后将剩下的空间继续挂接起来空间申请就结束了,简单又高效。但是如果0号自由链表中没有空间,就需要去内存池里边申请了,如果就向内存池申请一块 16个字节的空间,下一次申请16字节空间的时候自由链表中依然是没有空间,又要到内存池里边申请,每次都这样,很麻烦,所以,在向内存池里申请空间的时候,不是一次只申请一块16个字节的空间,而是多申请几块16字节的空间(比如20块),方便下一次 16字节空间的申请。
到内存池里边申请空间,又有三种情况,第一种情况:内存池里边空间还很足,16字节大小的空间还可以分配出20块,那么,将其中1块分配给用户使用,然后将剩下的19块挂接到自由链表中,下一次申请16字节的空间就可以直接在自由链表中取了。第二种情况:内存池里边空间不是很足,不能一次性提供 20 个块 16个字节的空间,但是可以提供5块 16字节的空间(不一定是5 ,只要比20小,大于等于1就行),那么,就将能提供的这5块空间的其中一块返回给用户使用,剩下的4块挂接到自由链表中;第三种情况:内存池里的空间已经很紧张了,假如只剩下8个字节,连一块16字节的空间都提供不了了,那么这个时候就需要朝系统去申请空间来填充内存池了,由于管理内存池的就是一个_start 和 _end 指针,只能标记上一块连续的空间,所以,之前内存池中还剩的8个字节的空间就需要将它挂接到对应的自由链表(也就是0号自由链表)中,然后向系统申请一端空间放入到内存池中,并用_start 和 _end 指针将这段空间标记管理起来。这就回到了第一种情况。
向系统申请一大块空间就一定能申请成功么?不一定,如果系统里边的空间也已经不足了就会申请失败,这个时候,就需要向自由链表后边管理的比16字节大的空间里边去切割了,因为不确定那个自由链表里有空间,所以,需要遍历管理自由链表的数组,在遍历的时候有一个小细节,目前我们申请的16字节的空间对应的是1号自由链表,按理说我们应该从2号自由链表开始查找,但是由于需要考虑多线程,(有可能在我们向内存池或系统申请空间的时候,另一个线程已经归还空间并挂接到了1号自由链表中),所以,依然从1号自由链表开始遍历。当遍历到某个自由链表(假如是3号自由链表中管理的有空间),那么,就从3号自由链表中取一个空间结点下来(同样是以头删的方式)放入到内存池中,然后再使用在内存中申请空间的方式申请空间就OK了
如果一直将后边全部的自由链表都遍历完了,也没有找到一块空间。也就是说所有自由链表、内存池、系统里都不能提供空间,可以说是山穷水尽了。空间都去哪了?已经分配给用户了,那么,只能调用一级空间配置器,因为一直空间配置器在无法分配空间的时候会调用用户设置的内存释放函数,将自己已经不用的空间归还给系统,这样就有可能还能申请到空间,如果用户没有设置内存释放函数的话,那么申请空间的结果就只能是失败(抛异常)了。
参考链接:
【1】https://blog.csdn.net/ZWE7616175/article/details/80559884
【2】https://blog.csdn.net/guaiguaihenguai/article/details/80598998