内存池的实现(二)

  《内存池的实现(一)》中,介绍了使用内存池的原因,设计内存池应该考虑的问题,最后给出一个简单的内存池实现例子。使用上一篇文章中介绍的内存池实现方案,要在一定的限定条件下,下面我们来看更通用的内存池实现——Apache服务器的内存池实现。

   Apache服务器的开发人员将代码中可移植的部分整理出来,编辑成Apache可移植运行库(Apacheportable Run-timelibraries),简称APR,该库可从这里下载,其中包含这里要介绍的内存池的实现代码。下面将Apache服务器内存池简称为APR内存池。

1.APR内存池结构

1.1 内存分配结点

  在了解整个内存池架构前,我们先来了解APR内存池中最基本的单元——内存分配结点。内存分配结点被用来描述每次分配的内存块,对应的结构名为apr_memnode_t,定义在文件apr_allocator.h中,其定义如下:

1 /* basic memory nodestructure*/
2 struct apr_memnode_t {
3   apr_memnode_t*next;           /**< next memnode */
4     apr_memnode_t**ref;           /**< reference to self */
5     apr_uint32_t  index;              /**< size */
6     apr_uint32_t  free_index;       /**< how much free */
7     char         *first_avail;          /**< pointer to first free memory */
8     char         *endp;                 /**< pointer to end of free memory */
9 };

  即使结构中的每个字段,源文件中都给出了注释,但对于每个字段的用途,还是很难让人理解。这里先给出每个字段的简略解析:

    A.next:指向下一个结点的指针;

    B.ref:指向上一结点的next结点,上一结点的next指向本结点,因此**ref就是本结点自身;

    C.index:既指示了该结点的大小,同时指示了该结点所在链表的索引下标值;

    D.free_index:所描述的内存块中未被占用的空间(这里和上面的index和它们字面意思有出入,理解的时候要留意);

    E.first_avail:指向可用空间开始位置的指针;

    F.endp:指向可用空间结尾位置的指针。

  该结点示意图如下:

结点示意图

1.2 内存分配器

  在ARP内存池中,使用内存分配器对内存分配结点进行管理,它在apr_pools.c中定义如下:

1 struct apr_allocator_t {
2     apr_uint32_t        max_index;
3     apr_uint32_t        max_free_index;
4     apr_uint32_t        current_free_index;
5     apr_pool_t          *owner;
6     apr_memnode_t  *free[MAX_INDEX];
7 };

  max_index:free指针数组的下标,free[max_index]指向已有的最大内存块链表;

    A.max_free_index:内存分配器所能容纳的最大内存空间数值;

    B.current_free_index:内存分配器中还能接收的空间大小,与max_free_index结合使用,解决限制内存池空间大小的问题;

    C.owner:指示该分配器属于哪个内存池;

    D.free:指向一组链表的头结点,该链表中每个结点指向内存结点组成的链表,MAX_INDEX为20。

   内存分配器及其管理的内存结点图示如下:

内存分配器

  从上图我们可以清晰地看出,free数组的下标从1到MAX_INDEX-1,分别指向一条结点大小固定的链表,下标增加1,结点的大小增加4k,因此free[MAX_INDEX]所指向的链表的结点大小为84k,这也是内存池使用者所能申请的最大”规则结点“,超过该大小的结点将下标0指向的链表进行管理。要明白free数组下标和结点大小的关系,我们需要知道宏定义APR_ALIGN:

1 #defineAPR_ALIGN(size, boundary) \
2                    (((size)+ ((boundary) - 1)) &~((boundary) - 1))

  该宏所做的无非就是计算出最接近size的boundary的整数倍的整数。通常情况下size大小为整数即可,而boundary则必须保证为2的幂。比如APR_ALIGN(7,4)为8;APR_ALIGN(21,8)为24;APR_ALIGN(21,16)则为32。

   对于每次空间申请,APR先对齐空间大小:

1 size = APR_ALIGN(size +APR_MEMNODE_T_SIZE, 4096);

  结果是size的值变成4096(4k,2的12次方)的倍数,最后,通过左移与我们的下标对应起来:

1 index= (size >>BOUNDARY_INDEX) - 1; //BOUNDARY_INDEX=12

1.3 内存池结点

  APR的内存池结点,在apr_pools.c文件中定义如下:

1 struct apr_pool_t {
2     ……
3     apr_allocator_t      *allocator;
4     apr_memnode_t   *active;
5     ……
6  
7 };

  该结构中的字段比较多,我们主要关注以上列出的两个字段。

    A.allocator:指向相应的内存分配器;

    B.active:指向使用中内存链表的指针

  内存池结点及其管理的内存结点如下图所示:

内存池结点

  需要留意的是虽然该结构字面意义上为“内存池结构”,但是它负责管理使用中的内存。

 2.APR内存池的内存管理

  对APR内存池各个结构有了初步了解之后,我们来看APR中是如何利用这些结构进行内存管理的。

 2.1 内存申请

  内存申请的核心函数是allocator_alloc函数,参数为一个指向内存分配器的指针和所要申请空间的大小,内存分配就是对内存分配器进行操作(以下列出的字段参见“内存分配器”章节),其内存申请的策略如下:

    A.根据申请空间的大小size,生成索引index,如果索引数值在1~max_index范围内,那就在index~max_index范围内的链表中返回一块内存;

    B.如果索引数值index >max_index,则在free[0]链表中查找一块合适的内存;

    C.经上两步仍未找到空闲内存块,则通过malloc(size)返回一块新生成的内存。

 2.2 内存释放

  在内存申请中提到,假如内存分配器中没有合适的内存块,将会调用malloc获取一块,但是新分配的内存并不挂接到内存分配器链表中,而是在调用allocator_free进行内存释放的时候,内存才可能挂到内存分配器链表上。内存释放策略如下:

    A.如果结点的大小超过了完全释放的阙值max_free_index,那么我们就不能将其简单的归还到索引链表中,而必须将其完全归还给操作系统;

    B.如果index< MAX_INDEX,则意味着该结点属于“规则结点”的范围。因此可以将该结点返回到对应的“规则链表”中;

    C.如果结点超过了“规则结点”的范围,但是并没有超过阙值max_free_index,此时我们则可以将其置于“索引0”链表的首部中。

 2.3 内存池结点管理的内存

  刚开始,由内存分配器管理的链表并没有挂接任何内存,也就是说内存池是空的,当我们申请内存时,必然进行“内存申请”中的第三步操作,新分配的内存就由我们的内存池结点进行管理。

  先来看用于内存池结点管理的一个宏定义:

1 /* Node list managementhelper macros; list_insert() inserts 'node'
2  * before 'point'. */
3 #define list_insert(node,point) do {         \
4     node->ref= point->ref;                     \
5     *node->ref= node;                          \
6     node->next= point;                         \
7     point->ref= &node->next;                \
8 } while (0)

  在内存池结点初次建立时,链表状态如下图:

结点初次建立

  再次申请一个结点,运行list_insert(node,point)后,结果如下图:

插入一个结点后

  我们可以通过allocator_free调用或apr_pool_clear、apr_pool_destroy调用(它们内部调用allocator_free)进行内存池结点的释放,所释放的内存将按照“内存释放”中的策略归还内存池或操作系统。

 3.小结

  free数组下标的双重含义(即是数组下标,又指示内存块大小)、通过阙值max_free_index限制内存池大小,这些都较难理解,但也是是APR内存池实现中出彩的部分,另外,APR内存池还涉及到父/子/兄弟内存池、内存池生命周期的概念,同学们可以通过文章末尾给出的参考文档,进行更深入地学习。

 

原文链接:http://www.cnblogs.com/bangerlee/archive/2011/09/01/2161437.html

转载于:https://www.cnblogs.com/blueoverflow/p/4906949.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
内存池是一种常见的内存管理技术,它可以在程序启动时预先分配一定数量的内存空间,并将其划分为多个固定大小的块,然后在程序运行过程中动态地将这些块分配给需要使用内存的对象,从而减少内存碎片和内存分配的时间开销。下面是一个简单的内存池实现示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define BLOCK_SIZE 1024 #define BLOCK_NUM 10 typedef struct _memory_block { void *start; void *end; struct _memory_block *next; } memory_block; typedef struct _memory_pool { size_t block_size; memory_block *free_list; memory_block *used_list; } memory_pool; memory_pool *memory_pool_create(size_t block_size) { memory_pool *pool = (memory_pool *) malloc(sizeof(memory_pool)); pool->block_size = block_size; pool->free_list = NULL; pool->used_list = NULL; for (int i = 0; i < BLOCK_NUM; i++) { memory_block *block = (memory_block *) malloc(sizeof(memory_block)); block->start = malloc(block_size); block->end = (char *) block->start + block_size; block->next = pool->free_list; pool->free_list = block; } return pool; } void *memory_pool_alloc(memory_pool *pool, size_t size) { memory_block *block = pool->free_list; while (block) { if ((char *) block->end - (char *) block->start >= size) { void *ptr = block->start; block->start = (char *) block->start + size; if (block->start == block->end) { pool->free_list = block->next; block->next = pool->used_list; pool->used_list = block; } return ptr; } block = block->next; } return NULL; } void memory_pool_free(memory_pool *pool) { memory_block *block = pool->used_list; while (block) { memory_block *next = block->next; free(block->start); free(block); block = next; } block = pool->free_list; while (block) { memory_block *next = block->next; free(block->start); free(block); block = next; } free(pool); } int main() { memory_pool *pool = memory_pool_create(BLOCK_SIZE); char *str1 = (char *) memory_pool_alloc(pool, 10); char *str2 = (char *) memory_pool_alloc(pool, 20); char *str3 = (char *) memory_pool_alloc(pool, 30); strcpy(str1, "hello"); strcpy(str2, "world"); strcpy(str3, "memory pool"); printf("%s %s %s\n", str1, str2, str3); memory_pool_free(pool); return 0; } ``` 该示例中,首先定义了两个结构体:memory_block表示内存块,包括起始地址、结束地址和下一个内存块的指针;memory_pool表示内存池,包括块大小、空闲链表和已用链表。 然后,定义了三个函数:memory_pool_create用于创建内存池,先分配一定数量的内存块,并将其加入空闲链表;memory_pool_alloc用于从内存池中分配一块指定大小的内存空间,遍历空闲链表,找到第一个大小足够的内存块,并将其划分为新的内存空间;memory_pool_free用于释放内存池中的所有内存块,将已用链表和空闲链表中的内存块全部释放。 最后,在main函数中创建一个内存池,并使用memory_pool_alloc从内存池中分配三个字符串空间,将其赋值并打印出来,最后使用memory_pool_free释放内存池中的所有内存块。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值