一、不定长内存池设计

在这里插入图片描述

1. 整体设计

  • 先创建一个池(mp_create_pool)。
  • 分配内存时,根据 请求大小,走不同的逻辑:
    • 小块内存(≤pool->max):从池子里的节点(block)中划分。
    • 大块内存(>pool->max):直接 malloc,单独管理。

2. 小块内存的分配逻辑

  • 内存池内部维护一个节点链表(node list)。
  • 每个节点是一个大块内存,里面可以分配多个小块。
  • 分配步骤:
    1. 遍历节点链表,在当前节点的剩余空间中找到一块足够大的内存。。
    2. 如果有,就直接从 last 指针处分配一块。
    3. 如果没有,调用 mp_alloc_block 申请一个新节点加入链表。
    4. 返回分配到的小块地址。

特点:小块内存一般不单独释放,只能等整个池 resetdestroy

3. 大块内存的分配逻辑

  • 大于阈值(如 4096)的分配,直接调用 malloc
  • 用一个 mp_large_s 链表记录这些大块。
  • 可以单独释放(mp_free)。

4. 释放 & 重置

  • 小块内存:只能整体 reset(相当于把 last 指针回退,所有小块一次性“回收”)。
  • 大块内存:可以单独释放。
  • 销毁池:释放所有节点和大块,整个池回收。

二、关键函数

  • 小块size <= pool->max):从池里事先分配好的“节点(node)”里按需 bump(推进)分配,分配很快但不能单独释放,只能重置整个池。
  • 大块size > pool->max):直接用 malloc 分配,并把这些大块记录在 pool->large 链表里,可以单独释放。

2.1 数据结构

struct mp_large_s {
struct mp_large_s *next;
// 下一个大块
void *alloc;
// malloc 得到的大块地址
};
struct mp_node_s {
unsigned char *last;
// 当前分配到哪里了
unsigned char *end;
// 节点内存结束位置
struct mp_node_s *next;
// 下一个节点
size_t failed;
// 分配失败次数
};
struct mp_pool_s {
size_t max;
// 小块的最大分配阈值
struct mp_node_s *current;
// 当前使用的节点
struct mp_large_s *large;
// 大块链表
struct mp_node_s head[0];
// 第一个节点(变长结构体)
};
  • mp_node_s 表示“池里的一段大内存”(一个 node 包含很多小块可以分配)。
    • last 指示“我们已经分配到哪里了”,每次分配之后 last 往前推。
    • end 是边界,不可越过。
  • mp_large_s:记录单独分配的大块。
  • mp_pool_s:整个内存池,管理小块节点 + 大块链表。
    • head[0] 是 C 的变长数组技巧,意味着第一个 mp_node_s放到 pool 内存块里,节省了一次 malloc。

2.2 创建内存池

struct mp_pool_s *mp_create_pool(size_t size) {
posix_memalign(&p, MP_ALIGNMENT, size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
p->current = p->head;
p->large = NULL;
p->head->last = (unsigned char *)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
p->head->end = p->head->last + size;
return p;
}
  • posix_memalign 分配一大块内存(对齐)。
  • 前面放 mp_pool_smp_node_s(管理信息)。
  • 剩余部分作为第一个节点的分配区域。
  • 设置 lastend

示意(地址从低到高):

| mp_pool_s | mp_node_s (head) | node payload (size bytes) |

p->head->last 指向 node payload 的起始位置(第一个可分配字节);p->head->end 指向 node payload 的结束位置(起始 + size)。

2.3 对齐(alignment):mp_align / mp_align_ptr

  • CPU 对齐会影响性能,有些平台甚至要求对齐。这里 MP_ALIGNMENT = 32。
  • mp_align(n, alignment):把整数 n 向上对齐到 alignment 的倍数。
  • mp_align_ptr(p, alignment):把指针 p 向上对齐到 alignment 的地址。
    在分配时总把要返回的地址对齐,避免未对齐访问。

2.4 分配(mp_alloc)流程

(1)小块分配(size <= pool->max

p = pool->current;
do {
m = mp_align_ptr(p->last, MP_ALIGNMENT);
if ((size_t)(p->end - m) >= size) {
p->last = m + size;
return m;
}
p = p->next;
} while (p);
return mp_alloc_block(pool, size);
  1. 为什么从 pool->current 开始?
    current 指向一个“最有可能有空余”的 node,这样通常能减少从头遍历链表的开销(优化)。
  2. 对齐:
    m = mp_align_ptr(p->last, MP_ALIGNMENT) 把当前 last(下一可分配地址)对齐。对齐会牺牲极少字节作为填充,但保证返回地址满足对齐要求。
  3. 检查剩余空间:
    (p->end - m) 计算该 node 从 mend 还有多少字节。如果≥ size,说明该 node 能满足这次请求。
  4. 分配并推进 last
    p->last = m + size —— 这把 last 推到分配区之后,下次分配会从新的位置继续。return m 把这次分配的起始地址返回给调用者。
  5. 遍历下一 node:
    如果当前 node 空间不够,就试 p = p->next
  6. 所有 node 都不够:
    跑完链表没有合适 node 时,调用 mp_alloc_block(pool, size) 申请新的 node。
  7. mp_alloc_block
    • 申请一整块新的 node(和 head node 一样的大小)
    • 把这新 node 接到 node 链表末尾,然后在这个新 node 里立刻分配所需 size 并返回其起始地址。

(2)大块分配(size > pool->max)——mp_alloc_large

static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {
void *p = malloc(size);
if (p == NULL) return NULL;
// 查找是否有可复用的 large 节点
size_t n = 0;
struct mp_large_s *large;
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
if (n++ >
3) break;
}
// 如果没有可复用的,分配一个新的 large 节点
large = mp_alloc(pool, sizeof(struct mp_large_s));
if (large == NULL) {
free(p);
return NULL;
}
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
  1. malloc(size) 得到原始内存 p

  2. 遍历 pool->large 链(记录大块的结构),尝试找到 alloc == NULL 的节点来重用该记录(即之前释放的大块的记录)。

    • 最多查找 3 个节点(if (n++ > 3) break;),这是为了避免遍历过长的链表。
  3. 如果没有可复用的 mp_large_s 节点,便用 mp_alloc(pool, sizeof(mp_large_s)) 在 pool 中分配一个 mp_large_s 结构(这本身调用 mp_alloc,有可能走小块路径或失败)。

    • 若分配 mp_large_s 失败(pool 内已无可用空间),则 free(p) 并返回 NULL
  4. 否则,把 p 挂到新 mp_large_s 结构上并插入链头 pool->large = large,返回 p 给调用者。

要点

  • mp_alloc_large 保证大块可以单独释放,因为 mp_free 会查找并 free 它们。
  • 同时实现了 大块记录复用,避免 mp_large_s 结构无限增长。

2.5 释放与回收策略

mp_free

  • 只遍历 pool->large 链表,查找 l->alloc == pfree(l->alloc),然后设为 NULL(保留 mp_large_s 节点以便复用)。

  • 不处理小块 —— 因为小块没有单独的元数据(每个小块没有 header 指示大小或归属 node),单独释放会复杂且容易引入碎片或许多额外元数据。

mp_reset_pool

  • pool->large 的所有 allocfree() 并把 pool->large = NULL

  • 对每个 node 把 h->last = (unsigned char *)h + sizeof(struct mp_node_s); —— 把小块区域视为“全新未分配”,即一次性回收所有小块。

mp_destory_pool

  • 释放 pool->large 的实际内存;

  • 释放 head->next 开始的所有动态创建的 node(free(h)),注意首个 node 是内联在 pool 内存里的(因此最后 free(pool) 会释放它);

  • 最后 free(pool) 本身(即创建 pool 时 posix_memalign 分配的内存)。


三、完整代码

#include <stdlib.h>
  #include <stdio.h>
    #include <string.h>
      #include <unistd.h>
        #include <fcntl.h>
          // ========================== 常量与对齐宏定义 =============================
          // 内存对齐粒度:32 字节对齐
          #define MP_ALIGNMENT 32
          // 默认页大小:4KB
          #define MP_PAGE_SIZE 4096
          // 从内存池中能分配的最大块(小于一页)
          #define MP_MAX_ALLOC_FROM_POOL (MP_PAGE_SIZE-1)
          // 将 n 向上对齐到 alignment 的整数倍
          #define mp_align(n, alignment) (((n)+(alignment-1)) &
          ~(alignment-1))
          // 将指针 p 向上对齐到 alignment 的整数倍地址
          #define mp_align_ptr(p, alignment) (void *)((((size_t)p)+(alignment-1)) &
          ~(alignment-1))
          // ========================== 结构体定义 =============================
          // 管理“大块内存”的链表节点
          // (当分配的内存超过阈值时,会直接 malloc,然后挂在这里)
          struct mp_large_s {
          struct mp_large_s *next;
          // 指向下一个大块
          void *alloc;
          // 指向大块分配的内存地址
          };
          // 内存池中的“小块分配节点”
          // 一个 node 表示一段连续的内存区域
          struct mp_node_s {
          unsigned char *last;
          // 当前已分配到的位置
          unsigned char *end;
          // 该内存块的结尾
          struct mp_node_s *next;
          // 指向下一个 node
          size_t failed;
          // 分配失败次数(用于优化 current 指针)
          };
          // 内存池的主体结构
          struct mp_pool_s {
          size_t max;
          // 小块分配的最大值(超过则走大块分配)
          struct mp_node_s *current;
          // 当前活跃的 node
          struct mp_large_s *large;
          // 大块链表
          struct mp_node_s head[0];
          // 内存池头节点(柔性数组)
          };
          // ========================== 函数声明 =============================
          struct mp_pool_s *mp_create_pool(size_t size);
          void mp_destory_pool(struct mp_pool_s *pool);
          void *mp_alloc(struct mp_pool_s *pool, size_t size);
          void *mp_nalloc(struct mp_pool_s *pool, size_t size);
          void *mp_calloc(struct mp_pool_s *pool, size_t size);
          void mp_free(struct mp_pool_s *pool, void *p);
          // ========================== 内存池创建与销毁 =============================
          // 创建内存池
          struct mp_pool_s *mp_create_pool(size_t size) {
          struct mp_pool_s *p;
          // 分配一块对齐的内存,包含 pool 本身、node 头和 size 大小的空间
          int ret = posix_memalign((void **)&p, MP_ALIGNMENT, size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
          if (ret) {
          return NULL;
          }
          // 设置最大可分配的小块
          p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
          p->current = p->head;
          // 当前节点指向头节点
          p->large = NULL;
          // 大块链表初始化为空
          // 初始化头节点的可用内存范围
          p->head->last = (unsigned char *)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
          p->head->end = p->head->last + size;
          p->head->failed = 0;
          return p;
          }
          // 销毁内存池,释放所有资源
          void mp_destory_pool(struct mp_pool_s *pool) {
          struct mp_node_s *h, *n;
          struct mp_large_s *l;
          // 释放大块内存
          for (l = pool->large; l; l = l->next) {
          if (l->alloc) {
          free(l->alloc);
          }
          }
          // 释放所有 node
          h = pool->head->next;
          while (h) {
          n = h->next;
          free(h);
          h = n;
          }
          // 释放内存池本身
          free(pool);
          }
          // 重置内存池:释放所有大块内存,清空小块分配状态
          void mp_reset_pool(struct mp_pool_s *pool) {
          struct mp_node_s *h;
          struct mp_large_s *l;
          // 释放大块
          for (l = pool->large; l; l = l->next) {
          if (l->alloc) {
          free(l->alloc);
          }
          }
          pool->large = NULL;
          // 重置所有 node 的 last 指针,相当于“清空已分配的小块”
          for (h = pool->head; h; h = h->next) {
          h->last = (unsigned char *)h + sizeof(struct mp_node_s);
          }
          }
          // ========================== 内存分配实现 =============================
          // 内部函数:申请一个新的小块 node
          static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {
          unsigned char *m;
          struct mp_node_s *h = pool->head;
          size_t psize = (size_t)(h->end - (unsigned char *)h);
          // 分配对齐的内存块
          int ret = posix_memalign((void **)&m, MP_ALIGNMENT, psize);
          if (ret) return NULL;
          // 初始化新 node
          struct mp_node_s *p, *new_node, *current;
          new_node = (struct mp_node_s*)m;
          new_node->end = m + psize;
          new_node->next = NULL;
          new_node->failed = 0;
          // 设置 last 指针(预留 node 头结构体)
          m += sizeof(struct mp_node_s);
          m = mp_align_ptr(m, MP_ALIGNMENT);
          new_node->last = m + size;
          // 将新 node 挂到链表末尾,同时更新 current
          current = pool->current;
          for (p = current; p->next; p = p->next) {
          if (p->failed++ >
          4) {
          current = p->next;
          }
          }
          p->next = new_node;
          pool->current = current ? current : new_node;
          return m;
          }
          // 内部函数:申请大块内存
          static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {
          void *p = malloc(size);
          if (p == NULL) return NULL;
          // 查找是否有可复用的 large 节点
          size_t n = 0;
          struct mp_large_s *large;
          for (large = pool->large; large; large = large->next) {
          if (large->alloc == NULL) {
          large->alloc = p;
          return p;
          }
          if (n++ >
          3) break;
          }
          // 如果没有可复用的,分配一个新的 large 节点
          large = mp_alloc(pool, sizeof(struct mp_large_s));
          if (large == NULL) {
          free(p);
          return NULL;
          }
          large->alloc = p;
          large->next = pool->large;
          pool->large = large;
          return p;
          }
          // 分配对齐的小块/大块内存
          void *mp_alloc(struct mp_pool_s *pool, size_t size) {
          unsigned char *m;
          struct mp_node_s *p;
          // 小块分配
          if (size <= pool->max) {
            p = pool->current;
            do {
            m = mp_align_ptr(p->last, MP_ALIGNMENT);
            if ((size_t)(p->end - m) >= size) {
            p->last = m + size;
            return m;
            }
            p = p->next;
            } while (p);
            return mp_alloc_block(pool, size);
            }
            // 大块分配
            return mp_alloc_large(pool, size);
            }
            // 分配(不做对齐处理)
            void *mp_nalloc(struct mp_pool_s *pool, size_t size) {
            unsigned char *m;
            struct mp_node_s *p;
            if (size <= pool->max) {
              p = pool->current;
              do {
              m = p->last;
              if ((size_t)(p->end - m) >= size) {
              p->last = m + size;
              return m;
              }
              p = p->next;
              } while (p);
              return mp_alloc_block(pool, size);
              }
              return mp_alloc_large(pool, size);
              }
              // 分配并清零(calloc)
              void *mp_calloc(struct mp_pool_s *pool, size_t size) {
              void *p = mp_alloc(pool, size);
              if (p) {
              memset(p, 0, size);
              }
              return p;
              }
              // 释放(仅释放大块内存,小块只能在 reset 时统一回收)
              void mp_free(struct mp_pool_s *pool, void *p) {
              struct mp_large_s *l;
              for (l = pool->large; l; l = l->next) {
              if (p == l->alloc) {
              free(l->alloc);
              l->alloc = NULL;
              return;
              }
              }
              }
              // ========================== 测试用例 =============================
              int main(int argc, char *argv[]) {
              int size = 1 <<
              12;
              // 4096
              // 创建内存池
              struct mp_pool_s *p = mp_create_pool(size);
              // 测试小块分配
              int i = 0;
              for (i = 0; i <
              10; i++) {
              void *mp = mp_alloc(p, 512);
              // mp_free(mp); // 小块不能单独 free
              }
              // 测试对齐宏
              printf("mp_align(24, 32): %d, mp_align(17, 32): %d\n", mp_align(24, 32), mp_align(17, 32));
              // 测试 calloc
              int j = 0;
              for (i = 0; i <
              5; i++) {
              char *pp = mp_calloc(p, 32);
              for (j = 0; j <
              32; j++) {
              if (pp[j]) {
              printf("calloc wrong\n");
              }
              printf("calloc success\n");
              }
              }
              // 测试大块分配 + 释放
              for (i = 0; i <
              5; i++) {
              void *l = mp_alloc(p, 8192);
              mp_free(p, l);
              }
              // 重置内存池
              mp_reset_pool(p);
              // 再次分配
              for (i = 0; i <
              58; i++) {
              mp_alloc(p, 256);
              }
              // 销毁内存池
              mp_destory_pool(p);
              return 0;
              }
posted on 2025-09-18 11:22  ycfenxi  阅读(5)  评论(0)    收藏  举报