背景与介绍

优势

解决频繁申请释放内存后产生大量内存碎片

方案

先提前申请特定大小的内存,当需要使用时再进行分配,不再使用时先不释放而是统一释放。

设计与实现

组成

  1. 小块内存block
  2. 大块内存large
  3. 内存池管理结构mempool

    API

  4. 创建内存池mp_init

  5. 销毁内存池mp_destory
  6. 重置内存池mp_reset
  7. 分配内存mp_palloc

    实现

    小块内存 mp_block_t

    1. typedef struct mp_block_s {
    2. struct mp_block_s *next;
    3. unsigned char *last; // 指向未分配内存起始地址
    4. unsigned char *end; // 指向最大分配地址
    5. size_t failed;
    6. }mp_block_t;
  8. 使用链表连接

  9. 该小块可分配内存大小:block->end - block->last

    大块内存 mp_large_t

    1. typedef struct mp_large_s {
    2. struct mp_large_s *next;
    3. void *alloc;
    4. } mp_large_t;

    注意:

  10. 链表连接,即所有大块内存使用链表遍历

  11. 在小块内存中存储大块内存指针
  12. 仅在内存池首个节点块关联大块内存链表
  13. 使用头插法,将新分配的大块内存添加到链表中

    内存池 mp_pool_t

    1. typedef struct mp_pool_s {
    2. mp_block_t *block;
    3. mp_large_t *large;
    4. size_t max; // 小块内存的最大分配大小
    5. // 其他,如日志等,可对比Nginx内存池实现
    6. mp_block_t head[0]; // 柔性数组
    7. }mp_pool_t;
  14. head[],柔性数组,来连接第一个小块内存

  15. max,

    创建内存池 mmpool_init

    1. mp_pool_t* mmpool_init(size_t size) {
    2. mp_pool_t *p;
    3. if (posix_memalign(&p, MP_ALIGNMENT, size + sizeof(mp_pool_t) + sizeof(mp_block_t)) != 0) {
    4. return NULL;
    5. }
    6. p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
    7. p->block = p->head; // 指向第一个小块内存,等价p->block = p->head->last = (unsigned char *)p + sizeof(mp_pool_t) + sizeof(mp_block_t)
    8. p->large = NULL;
    9. p->head->last = (unsigned char *)p + sizeof(mp_pool_t) + sizeof(mp_block_t);
    10. p->head->end = p->head->last + size;
    11. p->head->failed = 0;
    12. return p;
    13. }
  16. 不要设计成int mmpool_init(mp_pool_t *p, size_t size),需要在函数内部申请内存后再返回指针(堆空间)

  17. 使用posix_memalign而不是malloc

流程描述:

  1. 申请内存,大小为池结构+小块内存结构+实际分配:size + sizeof(mp_pool_t) + sizeof(mp_block_t)
  2. 判断size是否超出一页4k,两中取小
  3. 对所有结构进行初始化

疑问

  1. MP_MAX_ALLOC_FROM_POOL = 4k - 1,为什么要减1?
  2. 参数中的size推荐为整个池的大小还是仅为首块内存池的大小更好?

    销毁内存池 mmpool_destory

    1. // 销毁内存池
    2. void mmpool_destory(mp_pool_t *pool) {
    3. mp_block_s *head, *block;
    4. mp_large_t *large;
    5. // 1. 释放大块内存
    6. for (large = pool->large; large; large = large->next) {
    7. if (large->alloc) {
    8. free(large->alloc);
    9. }
    10. }
    11. // 2. 释放小块内存, 从第二块开始(第一个与pool连接在一起)
    12. head = pool->head->next;
    13. while (head) {
    14. block = head->next;
    15. free(head);
    16. head = block;
    17. }
    18. // 3. 释放池结构(含首个小块内存、小块内存结构)
    19. free(pool);
    20. }

    流程:

  3. 释放大块内存

  4. 释放小块内存, 从第二块开始(第一个与pool连接在一起)
  5. 释放池结构(含首个小块内存、小块内存结构)

    重置内存池

    1. void mmpool_reset(mp_pool_t *pool) {
    2. mp_block_s *block;
    3. mp_large_s *large;
    4. // 1. 释放所有大块内存
    5. for (large = pool->large; large; large = large->next) {
    6. if (large->alloc) {
    7. free(large->alloc);
    8. }
    9. }
    10. pool->large = NULL;
    11. // 2. 收回所有小块内存空间但不释放(即已申请的内存空间均可重新分配)
    12. for (block = pool->head; block; block = block->next) {
    13. block->last = (unsigned char *)block + sizeof(mp_block_t);
    14. }
    15. }

    流程:

  6. 释放所有大块内存

  7. 收回所有小块内存空间但不释放(即已申请的内存空间均可重新分配)

    分配内存

    流程

  8. 比较申请大小与内存池最大来决定在小块还是大块上申请内存

  9. size <= pool->max时,在小块内存节点上申请mp_alloc_block()
    1. 遍历小块内存节点链表,是否可在已有小块节点上分配内存size < block->end - block->last,可以则分配后直接返回
    2. 不可以则需要在内存池上分配新的小块内存节点,并进行分配
  10. size > pool->max时,在大块内存节点上申请mp_alloc_large()
    1. 遍历大块内存节点,看是否存在空的节点,有则进行分配并返回
    2. 没有则新建大块内存节点并分配内存后添加到内存池,最后返回

      应用案例

      Nginx内存池

RingBuffer

其他

内存对齐

内存对齐

内存分配