一.常见的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)三个节点所构成,下面讲述一下原理。
如上图所示,首先内存池结点有两个主要成员,一个是空闲内存管理节点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。