03-系统篇-内存碎片

一.常见的malloc内存分配原理

1内存分配原理
linux中应用层动态分配内存一般是用的malloc函数,而malloc在glibc中实现时,是用sbrk()来分内存.
在前面的章节中,我们了解到了堆的概念,堆在内存中,是一断连续的内存,并且上往上增长的,而sbrk()的作用是根据指定的size大小,对堆末尾指针进行往上移动或往下移动,从而实现内存的分配与释放。
size>0   分配内存,size<0   释放内存

2常见malloc和free内存管理逻辑
操作系统的堆内存是连续的,而我们释放内存时,只能对堆末尾的内存进行释放,由于先分配的内存并不一定会先释放,因而,通过sbrk()下移来释放内存在实际中存在问题。
在malloc分配内存时,分配长度=header+用户需要的大小size,header是一个链表节点,包含此段内存是否空闲,此段内存大小,下一个内存的指针。
struct header_t{
     size_t size;
     unsigned int is_free;
     struct header_t *next;
}
若我们调用了free函数,先确认要释放的内存是否在堆的末尾,若在堆末尾,直接操作系统释放,若不在末尾就将is_free;标为空闲,下次使用。
而下一次malloc时,先找一下已存在有链表中,是否有空闲的并且大小合适的内存分配,若有,直接使用,若存在的空闲内存不够大,再调用sbrk()进行分配。

二.内存池原理

实际系统中,由于malloc和free分配内存大小或者分配与释放的时机并不确定,加上内存本身而言就是线性结构,因而肯定会存在正在使用的内存之间有一些内存空闲,但使用不了,这种小的空闲的内存,叫做内存碎片,随着系统分配和释放的频繁,内存碎片会越来越多。
为了减少内存碎片的产生,本节描述了常见解决内碎片的方法,也就是内存池。下面介绍常见的内存池(Apache)的结构。
(1)内存单元结点
struct apr_memnode_t {
  apr_memnode_t*next;           /**< next:指向下一个结点的指针; */
    apr_memnode_t**ref;           /**< 本结点自身 */
    apr_uint32_t  index;              /**< 结点的大小 */
    apr_uint32_t  free_index;       /**< 内存块中未被占用的空间 */
    char         *first_avail;          /**< 可用空间开始位置的指 */
    char         *endp;                 /**< 可用空间结尾位置的指针 */
};

(2)空闲内存管理结点
struct apr_allocator_t {
    apr_uint32_t        max_index;   //free指针数组的最大内存块链表下标
    apr_uint32_t        max_free_index;  //内存分配器所能容纳的最大内存空间数值
    apr_uint32_t        current_free_index;  //内存分配器中还能接收的空间大小
    apr_pool_t          *owner;    //属于哪个内存池
    apr_memnode_t  *free[MAX_INDEX];  //指向一组链表的头结点, MAX_INDEX为20
};

(3)内存池结点
struct apr_pool_t {
    ……
    apr_allocator_t      *allocator;  //空闲内存管理
    apr_memnode_t   *active;//正在使用的内存链表
    ……
};


内存池由上述(1)、(2)、(3)三个节点所构成,下面讲述一下原理。
 

03-系统篇-内存碎片_第1张图片

如上图所示,首先内存池结点有两个主要成员,一个是空闲内存管理节点allocator。另外一个是active链表。
allocator可以理解成此内存池剩余的空间,而active表示此内存池正在运行的空间。

1.active:由上图可以知道active是由apr_memnode_t结构体组成的链表,当用户要分配内存时,比如分配内存的大小是size,内存池实际会分配total_size = sizeof(apr_memnode_t)+size,total_size为结构体加上用户需要使用的内存大小。若其中在分配内存时结构体中的index可以取值为0-20,MAX_INDEX为20.   分配内存时,最小分配内存为8K(对应该index=1),超过8K后会以4K为单位累计分配内存,malloc_size = (index-1)*4K+8K  (1 <= index <= 20),因而分配内存的取值最小8K,最大84K,粒度为4K。

若要分配的内存大于84K时,对应的index取值为0。    total_size在哪一个区间,就会对应的分配多少内存从而锁定出对应的index。

2.allocator:由上图可以知道allocator对应apr_allocator_t管理器,其中free是一个数组链表,free的下标的索引就是对应的index,apr_allocator_t管理器是管理空闲内存的,也就是暂时没有使用的内存,根据index可以将空闲的内存放在对应free[index]的所在链表中,从而实现了空闲内存的管理。

3.用户要分配内存,首先可以确认分配内存的index, 然后在allocator管理器查看有没空闲内存,若存在空闲内存,则将空闲的apr_memnode_t 内存节点放在active链表,若没有空闲内存,则从系统中malloc内存。

同样用户释放内存,可以将apr_memnode_t 内存节点从active链表改到allocator管理器上并将内存结点置为空闲。

4.那什么时候将内存返还给系统呢?

(1).整个内存池注销时,所有的内存会一次性全部返还free给系统。

(2).另外需要分配的内存大于apr_allocator_t ->max_free_index时,管理器会在此块内存使用完后,直接还给系统,而不放allocator管理器。 

另外基础篇06-基础篇-缓存与内存泄露讨论的环形缓存也是一种减少内存碎片的办法,内存碎片的解决,实际上就是减少频繁的malloc和free。

你可能感兴趣的:(嵌入式系统篇,linux)