C语言内存池实现

本文提供一种高效内存池的实现方法。该内存池预先调用系统函数malloc申请一大片内存,使用时从该内存池中申请内存,使用完成后释放回内存池,内存块由内存池维护。

一、实现思路

1. 准备内存池

a) 预先申请大小为 2 n 2^{n} 2n 的内存。
b) 将该内存分割为若干 2 i ( i = j , j + 1 , . . . 且 i < n ) 2^{i} (i =j,j+1,...且i<n) 2i(i=j,j+1,...i<n) 大小的内存块。
c) 准备一个空闲链表数组 free_list [ m ] (m >= n),用来存放分割后的内存块,这些内存块都是空闲的。
d) 将最小的一个内存块标记为正在使用,并从链表 free_list [ j ] 中移除(后续说明原因)

  • 2 j 2^{j} 2j 即为最小的内存块大小。
  • 内存块存放规则:如果内存块的大小为 2 i 2^{i} 2i,那么将他添加到 free_list [ i ] 这个链表中。

初始状态如图所示,每个空闲链表都有一个内存块。在这里插入图片描述

2、申请内存

申请内存的原理就是从空闲链表中取出合适大小的内存块返回使用,这个过程可能存在内存块分裂的情况,步骤如下。

a) 根据申请的内存大小size,计算出能够提供该大小的最小内存块大小为 2 x 2^{x} 2x,直接从链表 free_list [ x ] 中取出一个内存块,标记为正在使用后返回。
在这里插入图片描述

b) 如果链表 free_list [ x ] 中没有内存块,那么先从链表 free_list [ x + 1] 中取出一个内存块,大小为 2 x + 1 2^{x+1} 2x+1,将其分裂成相等大小的两个伙伴内存块,大小为 2 x 2^{x} 2x,添加到 free_list [ x ] 中。
同理,如果链表 free_list [ x + 1] 也没有,那么就从 free_list [ x + 2] 中分裂,这个过程递归调用,直到没有内存块,就返回失败。
在这里插入图片描述

3、释放内存

释放内存的原理就是将内存块添加回到空闲链表中,同样,这个过程可能会存在内存块合并的情况,步骤如下。

a) 获取内存块的大小为 2 x 2^{x} 2x,那么直接将他添加到 free_list [ x ] 中。
b) 获取该内存块的伙伴内存块,伙伴内存块与该内存块相邻且大小需要相等(没有继续分裂)。
c)如果伙伴内存块存在,且是空闲的,一定也在链表 free_list [ x ] 中,将这两个内存块从 free_list [ x ] 中移除,合并成一个大的内存块,大小为 2 x + 1 2^{x + 1} 2x+1,添加到 free_list [ x + 1] 中。
同理,继续获取这个合并内存块的伙伴内存块,按照相同的方式,继续合并成更大的内存块,直到无法合并为止。
最小内存块的伙伴在初始时,已经设置为了正在使用,所以不会被合并。

二、代码实现

引用:
C语言通用双向链表

1. 数据结构

typedef struct pool_blk_struct pool_blk_t;
typedef LIST_BASE_NODE_T(pool_blk_t) pool_blk_lst_t;
typedef struct pool_struct pool_t;

/* 内存块 */
struct pool_blk_struct
{
    uint64          size;           /* 内存块大小(含头部在内) */
    bool            in_use;         /* 是否正在使用 */

    LIST_NODE_T(pool_blk_t) link;
};

/* 内存池 */
struct pool_struct
{
    uint64          buf_size;       /* 内存池初始大小 */
    byte*           buf;            /* 内存池缓冲区(初始) */
    pool_blk_lst_t  free_list[64];  /* 空闲内存块链表 */
    uint32          max_n;          /* 内存块大小的最大2的次幂 */

    uint64          used_size;      /* 已使用大小 */
};

#define POOL_MIN_SIZE           (16 * KILO_BYTES)                               /* 内存池最小大小 */
#define POOL_BLK_HEAD_SIZE      calc_align2(sizeof(pool_blk_t), UINT64_SIZE)    /* 内存块头部大小 */
#define POOL_BLK_MIN_SIZE       (POOL_BLK_HEAD_SIZE * 2)                        /* 内存块最小大小 */

2. 创建内存池

/* 功能: 创建内存池 */
pool_t* pool_create(
    IN uint64       size
)
{
    pool_t*         pool;
    pool_blk_t*     blk;
    uint64          used = 0;
    uint32          n;

    /* 内存池大小为2的次方(向下取整) */
    size = (size <= POOL_MIN_SIZE) ? POOL_MIN_SIZE : calc_2_n(calc_log_2(size));
    assert(((size - 1) & size) == 0);

    /* 申请内存, 初始化 */
    pool = malloc(sizeof(pool_t) + size);
    memset(pool, 0, sizeof(pool_t));
    pool->buf_size = size;
    pool->buf = (byte*)(pool + 1);

    /* 初始化空闲内存块 */
    pool->max_n = calc_log_2(size) - 1;
    while (size - used > POOL_BLK_MIN_SIZE)
    {
        /* 计算剩余空间是2的多少次方 */
        n = calc_log_2(size - used);
        /* 取一半做为内存块, 添加到空闲链表 */
        blk = (pool_blk_t*)(pool->buf + used);
        memset(blk, 0, POOL_BLK_HEAD_SIZE);
        blk->size = calc_2_n(n - 1);
        LIST_ADD_FIRST(link, pool->free_list[n - 1], blk);
        /* 增加已使用大小 */
        used += blk->size;
    }

    /* 将最后一个内存块设置为正在使用, 避免被合并 */
    blk = (pool_blk_t*)(pool->buf + used);
    memset(blk, 0, POOL_BLK_HEAD_SIZE);
    blk->size = size - used;
    blk->in_use = TRUE;
    pool->used_size = size - used;
    
    return pool;
}

3. 申请内存

/* 功能: 申请内存 */
void* pool_new(
    IN pool_t*      pool,
    IN uint64       size
)
{
    uint32          n;
    uint64          act_size;
    pool_blk_t*     blk;

    /* 实际要申请大小的含头部 */
    act_size = max(size + POOL_BLK_HEAD_SIZE, POOL_BLK_MIN_SIZE);

    /* 计算对应的2的次幂(申请大小要向上取整, 保证空间申请足够) */
    n = calc_log_2(act_size - 1) + 1;

    /* 从对应链表中取出空闲的内存块 */
    blk = LIST_GET_FIRST(pool->free_list[n]);
    if (blk == NULL)
    {
        /* 如果对应链表中没有内存块, 需要分裂更大的内存块填充到该链表中 */
        if (!pool_fill_free_list(pool, n))
        {
            return NULL;
        }
        blk = LIST_GET_FIRST(pool->free_list[n]);
    }

    /* 从链表中移除, 标记为正在使用 */
    assert(blk);
    LIST_REMOVE(link, pool->free_list[n], blk);
    blk->in_use = TRUE;

    /* 增加内存池已使用大小 */
    pool->used_size += blk->size;

    return (byte*)blk + POOL_BLK_HEAD_SIZE;
}

内存块分裂(递归):

/* 功能: 分裂更大的内存块, 填充到指定链表中(递归) */
/* 返回值: 分裂是否成功 */
bool pool_fill_free_list(
    IN pool_t*      pool,
    IN uint32       n       /* 2的次幂(链表数组下标) */
)
{
    pool_blk_t*     blk;

    /* 如果需要的内存块已经是最大的大小, 或者更大的, 返回失败 */
    RET_IF(n >= pool->max_n, FALSE);

    /* 取出一个更大的空闲内存块 */
    blk = LIST_GET_FIRST(pool->free_list[n + 1]);
    if (blk == NULL)
    {
        /* 如果没有, 继续分裂更大的 */
        RET_IF_FALSE(pool_fill_free_list(pool, n + 1), FALSE);
        blk = LIST_GET_FIRST(pool->free_list[n + 1]);
    }
    LIST_REMOVE(link, pool->free_list[n + 1], blk);

    /* 分裂成两个小的内存块, 填充到空闲链表中 */
    assert(blk->in_use == FALSE && blk->size == calc_2_n(n + 1));
    blk->size = calc_2_n(n);
    LIST_ADD_LAST(link, pool->free_list[n], blk);

    blk = (pool_blk_t*)((byte*)blk + blk->size);
    blk->in_use = FALSE;
    blk->size = calc_2_n(n);
    LIST_ADD_LAST(link, pool->free_list[n], blk);

    return TRUE;
}

4. 释放内存

/* 功能: 释放内存 */
void pool_delete(
    IN pool_t*      pool,
    IN void*        data
)
{
    pool_blk_t*     blk;
    pool_blk_t*     buddy;
    uint32          n;

    /* 找到原本的内存块, 和他的伙伴 */
    blk = (pool_blk_t*)((byte*)data - POOL_BLK_HEAD_SIZE);
    assert(blk->in_use == TRUE);
    buddy = pool_get_buddy(pool, blk);

    /* 计算对应的2的次幂 */
    n = calc_log_2(blk->size);

    /* 如果伙伴是空闲的, 并且大小一致, 那么他们可以合并成一个大的内存块 */
    if (buddy->in_use == FALSE && buddy->size == blk->size)
    {
        LIST_REMOVE(link, pool->free_list[n], buddy);
        pool_merge_to_free_list(pool, blk, buddy);
    }
    /* 否则, 直接添加到空闲链表 */
    else
    {
        LIST_ADD_FIRST(link, pool->free_list[n], blk);
        blk->in_use = FALSE;
    }

    /* 减少内存池使用大小 */
    pool->used_size -= calc_2_n(n);
}

获取内存块的伙伴:

/* 功能: 获取一个内存块的伙伴 */
INLINE
pool_blk_t* pool_get_buddy(
    IN pool_t*      pool,
    IN pool_blk_t*  blk
)
{
    pool_blk_t*     buddy;
    
    /* 如果伙伴在高地址, 那么blk相对buf的偏移, 除以blk的大小, 能被2整除 */
    if ((((byte*)blk - pool->buf) % (2 * blk->size)) == 0)
    {
        buddy = (pool_blk_t*)((byte*)blk + blk->size);
    }
    /* 伙伴在低地址 */
    else
    {
        buddy = (pool_blk_t*)((byte*)blk - blk->size);
    }
    return buddy;
}

内存块合并(递归):

/* 功能: 合并两个内存块到空闲链表中(空闲, 但不在free list中)(递归) */
void pool_merge_to_free_list(
    IN pool_t*      pool,
    IN pool_blk_t*  blk1,
    IN pool_blk_t*  blk2
)
{
    pool_blk_t*     blk;
    pool_blk_t*     buddy;
    uint32          n;

    /* 获取合并后的内存块, 和他的伙伴 */
    blk = min(blk1, blk2);
    blk->size = blk->size * 2;
    buddy = pool_get_buddy(pool, blk);

    /* 计算2的次幂 */
    n = calc_log_2(blk->size);

    /* 如果伙伴是空闲的, 并且大小一致, 那么他们可以合并成一个大的内存块 */
    if (buddy->in_use == FALSE && buddy->size == blk->size)
    {
        LIST_REMOVE(link, pool->free_list[n], buddy);
        pool_merge_to_free_list(pool, blk, buddy);
    }
    /* 否则, 直接添加到空闲链表 */
    else
    {
        LIST_ADD_FIRST(link, pool->free_list[n], blk);
        blk->in_use = FALSE;
    }
}

5. 释放内存池

/* 功能: 释放内存池 */
void pool_free(
    IN pool_t*      pool
)
{
    free(pool);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值