LwIP系列教程:深入解析内存管理(内存池)

一、目的

在《LwIP系列–内存管理(堆内存)详解》中我们详细介绍了LwIP中内存堆的实现原理,本篇我们介绍LwIP中内存池的实现细节。

在LwIP源码中为了满足特定内存分配的需要以及优化内存占用制定了各种尺寸大小的内存池(每种内存池管理的内存块大小固定)。

内存池分配方案的特点:

  • 每个内存块大小固定(由于根据大小从不同内存池中获取内存块,故内存堆的内存碎片得到优化)

  • 不存在内存碎片

  • 分配时间相对固定

  • 因为LwIP需要根据TCP/IP协议各层需要不同大小的数据包,故针对不同层和协议制定了不同大小的内存池。

    内存堆示意图

    上图中定义了A/B/C各种尺寸的内存池,内存池A管理着A-1/A-2/A3等内存块,每个内存池可以有不同数量相同大小的内存块,不同内存池中的内存块的大小一般不相同。

    二、介绍

    在正式介绍内存池实现细节之前我们先理解跟内存池相关的宏定义和结构体,LwIP在内存池的实现上定义了各种宏定义

    宏定义说明

    LWIP_DEBUG
    MEMP_OVERFLOW_CHECK
    LWIP_STATS_DISPLAY
    MEMP_STATS
    MEMP_MEM_MALLOC

    LWIP_DEBUG定义是否调试LwIP内核

    MEMP_OVERFLOW_CHECK定义是否进行内存池溢出检查

    LWIP_STATS_DISPLAY定义是否进行统计数据显示

    MEMP_STATS定义是否打开内存池统计

    MEMP_MEM_MALLOC定义内存池的分配和释放是否使用mem_malloc/mem_free(也就是不使用内存池的分配算法而使用动态内存堆方式进行分配)

    MEMP_SIZE宏定义

    #if MEMP_OVERFLOW_CHECK
    
    
    /* MEMP_SIZE: save space for struct memp and for sanity check */
    #define MEMP_SIZE          (LWIP_MEM_ALIGN_SIZE(sizeof(struct memp)) + MEM_SANITY_REGION_BEFORE_ALIGNED)
    #define MEMP_ALIGN_SIZE(x) (LWIP_MEM_ALIGN_SIZE(x) + MEM_SANITY_REGION_AFTER_ALIGNED)
    
    #else /* MEMP_OVERFLOW_CHECK */
    
    /* No sanity checks
     * We don't need to preserve the struct memp while not allocated, so we
     * can save a little space and set MEMP_SIZE to 0.
     */
    #define MEMP_SIZE           0
    #define MEMP_ALIGN_SIZE(x) (LWIP_MEM_ALIGN_SIZE(x))
    
    #endif /* MEMP_OVERFLOW_CHECK */

    如果定义了MEMP_OVERFLOW_CHECK宏,则内存池中的每个内存块头部都会有内存块管理结构用于内存块的完整性检查

    内存池描述

    /** Memory pool descriptor */
    struct memp_desc {
    #if defined(LWIP_DEBUG) || MEMP_OVERFLOW_CHECK || LWIP_STATS_DISPLAY
      /** Textual description */
      const char *desc;
    #endif /* LWIP_DEBUG || MEMP_OVERFLOW_CHECK || LWIP_STATS_DISPLAY */
    #if MEMP_STATS
      /** Statistics */
      struct stats_mem *stats;
    #endif
    
      /** Element size */
      u16_t size;
    
    #if !MEMP_MEM_MALLOC
      /** Number of elements */
      u16_t num;
    
      /** Base address */
      u8_t *base;
    
      /** First free element of each pool. Elements form a linked list. */
      struct memp **tab;
    #endif /* MEMP_MEM_MALLOC */
    };

    各字段含义说明:

    desc:内存池的字符串描述(在调试、内存池溢出检查、数据统计时定义该字段)

    stats:内存池统计信息,包括可用内存,最大内存、已经分配的内存信息(定义MEMP_STATS时定义该字段)

    size:记录内存池中每个内存块的大小

    num:记录该内存池中有几个内存块

    base:内存池的首地址

    tab:通过此字段将内存块构成链表进行管理(指向空闲的第一个内存块)

    其中size/num/base/tab定义了内存池的几个关键字段(在MEMP_MEM_MALLOC=0时定义这些字段)

    /** Memory stats */
    struct stats_mem {
    #if defined(LWIP_DEBUG) || LWIP_STATS_DISPLAY
      const char *name;
    #endif /* defined(LWIP_DEBUG) || LWIP_STATS_DISPLAY */
      STAT_COUNTER err;
      mem_size_t avail;
      mem_size_t used;
      mem_size_t max;
      STAT_COUNTER illegal;
    };

    结构体stats_mem定义了内存的使用信息

    各字段如下:

    name:此结构体变量的描述性信息(用于指示属于某个内存结构)

    err: 内存分配出错的次数

    avail:可用内存

    used:已使用内存

    max:最大内存

    #if !MEMP_MEM_MALLOC || MEMP_OVERFLOW_CHECK
    struct memp {
      struct memp *next;
    #if MEMP_OVERFLOW_CHECK
      const char *file;
      int line;
    #endif /* MEMP_OVERFLOW_CHECK */
    };
    #endif /* !MEMP_MEM_MALLOC || MEMP_OVERFLOW_CHECK */

    各个字段的含义:

    next:下一个内存块的地址

    file:调用内存池分配函数的文件

    line:调用内存池分配函数的行数

    内存池类型

    在LwIP中定义了各种类型的内存池,故使用一个枚举类型来标记不同的类型的内存池

    /** Create the list of all memory pools managed by memp. MEMP_MAX represents a NULL pool at the end */
    typedef enum {
    #define LWIP_MEMPOOL(name,num,size,desc)  MEMP_##name,
    #include "lwip/priv/memp_std.h"
      MEMP_MAX
    } memp_t;

    注意宏定义LWIP_MEMPOOL的定义

    ##用于拼接字符串

    上面的代码片段定义了memp_t枚举类型,MEMP_MAX定义了memp_t枚举类型的最大值。

    举例说明

    LWIP_MEMPOOL(MYPOOL, 4, 16, "my pool type")
    //相当于定义枚举值MEMP_MYPOOL

    在memp_std.h头文件中的定义了各种内存池

    /*
     * A list of internal pools used by LWIP.
     *
     * LWIP_MEMPOOL(pool_name, number_elements, element_size, pool_description)
     *     creates a pool name MEMP_pool_name. description is used in stats.c
     */
    #if LWIP_RAW
    LWIP_MEMPOOL(RAW_PCB,        MEMP_NUM_RAW_PCB,         sizeof(struct raw_pcb),        "RAW_PCB")
    #endif /* LWIP_RAW */
    
    #if LWIP_UDP
    LWIP_MEMPOOL(UDP_PCB,        MEMP_NUM_UDP_PCB,         sizeof(struct udp_pcb),        "UDP_PCB")
    #endif /* LWIP_UDP */
    
    #if LWIP_TCP
    LWIP_MEMPOOL(TCP_PCB,        MEMP_NUM_TCP_PCB,         sizeof(struct tcp_pcb),        "TCP_PCB")
    LWIP_MEMPOOL(TCP_PCB_LISTEN, MEMP_NUM_TCP_PCB_LISTEN,  sizeof(struct tcp_pcb_listen), "TCP_PCB_LISTEN")
    LWIP_MEMPOOL(TCP_SEG,        MEMP_NUM_TCP_SEG,         sizeof(struct tcp_seg),        "TCP_SEG")
    #endif /* LWIP_TCP */
    
    #if LWIP_ALTCP && LWIP_TCP
    LWIP_MEMPOOL(ALTCP_PCB,      MEMP_NUM_ALTCP_PCB,       sizeof(struct altcp_pcb),      "ALTCP_PCB")
    #endif /* LWIP_ALTCP && LWIP_TCP */

    上面的代码中定义了MEMP_RAW_PCB/MEMP_UDP_PCB/MEMP_TCP_PCB/MEMP_TCP_PCB_LISTEN/MEMP_TCP_SEG/MEMP_ALTCP_PCB这些枚举值,即存在这些类型的内存池

    定义内存池

    #define LWIP_MEMPOOL_DECLARE(name,num,size,desc) \
      LWIP_DECLARE_MEMORY_ALIGNED(memp_memory_ ## name ## _base, ((num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size)))); \
        \
      LWIP_MEMPOOL_DECLARE_STATS_INSTANCE(memp_stats_ ## name) \
        \
      static struct memp *memp_tab_ ## name; \
        \
      const struct memp_desc memp_ ## name = { \
        DECLARE_LWIP_MEMPOOL_DESC(desc) \
        LWIP_MEMPOOL_DECLARE_STATS_REFERENCE(memp_stats_ ## name) \
        LWIP_MEM_ALIGN_SIZE(size), \
        (num), \
        memp_memory_ ## name ## _base, \
        &memp_tab_ ## name \
      };

    各个字段的含义:

    name:内存池的名字

    num:内存池中内存块的个数

    size:内存池中每个内存块的大小

    desc:内存池的描述字符串

    举例说明:

    LWIP_MEMPOOL_DECLARE(mypool, 4, 16, "my pool")

    上面的代码片段宏展开后的代码如下:

    u8_t memp_memory_mypool_base[4 * (MEMP_SIZE + 16)];
    static struct stats_mem memp_stats_mypool;
    static struct memp *memp_tab_mypool;
    const struct memp_desc memp_mypool = {
        "my pool",
        &memp_stats_mypool,
        16,
        4,
        memp_memory_mypool_base,
        &memp_tab_mypool
    };

    其中MEMP_SIZE用于完整性检查,一般定义MEMP_SIZE为0即可;

    在上面的代码中我们通过LWIP_MEMPOOL_DECLARE宏定义就定义了一个新的内存池mypool以及相关的内存以及描述信息。

    memp_memory_mypool_base数组就是内存池需要的内存空间

    内存池集合

    const struct memp_desc *const memp_pools[MEMP_MAX] = {
    #define LWIP_MEMPOOL(name,num,size,desc) &memp_ ## name,
    #include "lwip/priv/memp_std.h"
    };

    每个内存池都有一个内存池描述,这些内存池描述组成了内存池集合,即LwIP管理的所有内存池

    内存池初始化

    #define LWIP_MEMPOOL_INIT(name)    memp_init_pool(&memp_ ## name)
    
    /**
     * Initialize custom memory pool.
     * Related functions: memp_malloc_pool, memp_free_pool
     *
     * @param desc pool to initialize
     */
    void
    memp_init_pool(const struct memp_desc *desc)
    {
    #if MEMP_MEM_MALLOC    //①
      LWIP_UNUSED_ARG(desc);
    #else
      int i;
      struct memp *memp;
    
      *desc->tab = NULL;
      memp = (struct memp *)LWIP_MEM_ALIGN(desc->base);    //②
    #if MEMP_MEM_INIT                                //③
      /* force memset on pool memory */
      memset(memp, 0, (size_t)desc->num * (MEMP_SIZE + desc->size 
    #if MEMP_OVERFLOW_CHECK
                                           + MEM_SANITY_REGION_AFTER_ALIGNED
    #endif
                                          ));
    #endif
      /* create a linked list of memp elements */
      for (i = 0; i < desc->num; ++i) {  //④
        memp->next = *desc->tab;
        *desc->tab = memp;
    #if MEMP_OVERFLOW_CHECK
        memp_overflow_init_element(memp, desc);
    #endif /* MEMP_OVERFLOW_CHECK */
        /* cast through void* to get rid of alignment warnings */
        memp = (struct memp *)(void *)((u8_t *)memp + MEMP_SIZE + desc->size
    #if MEMP_OVERFLOW_CHECK
                                       + MEM_SANITY_REGION_AFTER_ALIGNED
    #endif
                                      );
      }
    #if MEMP_STATS                
      desc->stats->avail = desc->num;
    #endif /* MEMP_STATS */
    #endif /* !MEMP_MEM_MALLOC */
    
    #if MEMP_STATS && (defined(LWIP_DEBUG) || LWIP_STATS_DISPLAY)
      desc->stats->name  = desc->desc;
    #endif /* MEMP_STATS && (defined(LWIP_DEBUG) || LWIP_STATS_DISPLAY) */
    }

    ①MEMP_MEM_MALLOC宏定义代表使用内存堆的分配方式作为内存池实现,此处我们讲解的是内存池的实现原理,故未定义此宏

    ②对栈上变量memp进行赋值

    ③对memp开始的地址进行清零操作

    ④对内存块管理结构进行赋值(将各个内存块通过next指针链接起来)

    初始化后的内存布局如下:

    第一个内存块的next指针为NULL,第二个内存块的next指向第一个内存块,依次类推;最终内存块的描述结构desc->tab指向内存池最后一个内存块

    从内存池中分配内存

    #define LWIP_MEMPOOL_ALLOC(name)   memp_malloc_pool(&memp_ ## name)

    举例如果从刚才定义的mypool内存池中分配内存就可以这样操作

    void *ptr = LWIP_MEMPOOL_ALLOC(mypool)
    //也可以
    void *ptr = memp_malloc_pool(&memp_mypool);
    void *
    #if !MEMP_OVERFLOW_CHECK
    memp_malloc_pool(const struct memp_desc *desc)
    #else
    memp_malloc_pool_fn(const struct memp_desc *desc, const char *file, const int line)
    #endif
    {
      LWIP_ASSERT("invalid pool desc", desc != NULL);
      if (desc == NULL) {
        return NULL;
      }
    
    #if !MEMP_OVERFLOW_CHECK
      return do_memp_malloc_pool(desc);
    #else
      return do_memp_malloc_pool_fn(desc, file, line);
    #endif
    }
    static void *
    #if !MEMP_OVERFLOW_CHECK
    do_memp_malloc_pool(const struct memp_desc *desc)
    #else
    do_memp_malloc_pool_fn(const struct memp_desc *desc, const char *file, const int line)
    #endif
    {
      struct memp *memp;
      SYS_ARCH_DECL_PROTECT(old_level);    //①
    
    #if MEMP_MEM_MALLOC
      memp = (struct memp *)mem_malloc(MEMP_SIZE + MEMP_ALIGN_SIZE(desc->size));
      SYS_ARCH_PROTECT(old_level);
    #else /* MEMP_MEM_MALLOC */
      SYS_ARCH_PROTECT(old_level);
    
      memp = *desc->tab;                //②
    #endif /* MEMP_MEM_MALLOC */
    
      if (memp != NULL) {
    #if !MEMP_MEM_MALLOC
    #if MEMP_OVERFLOW_CHECK == 1
        memp_overflow_check_element(memp, desc);
    #endif /* MEMP_OVERFLOW_CHECK */
    
        *desc->tab = memp->next;            //③
    #if MEMP_OVERFLOW_CHECK
        memp->next = NULL;
    #endif /* MEMP_OVERFLOW_CHECK */
    #endif /* !MEMP_MEM_MALLOC */
    #if MEMP_OVERFLOW_CHECK
        memp->file = file;
        memp->line = line;
    #if MEMP_MEM_MALLOC
        memp_overflow_init_element(memp, desc);
    #endif /* MEMP_MEM_MALLOC */
    #endif /* MEMP_OVERFLOW_CHECK */
        LWIP_ASSERT("memp_malloc: memp properly aligned",
                    ((mem_ptr_t)memp % MEM_ALIGNMENT) == 0);
    #if MEMP_STATS
        desc->stats->used++;
        if (desc->stats->used > desc->stats->max) {
          desc->stats->max = desc->stats->used;
        }
    #endif
        SYS_ARCH_UNPROTECT(old_level);
        /* cast through u8_t* to get rid of alignment warnings */
        return ((u8_t *)memp + MEMP_SIZE);  //④
      } else {
    #if MEMP_STATS
        desc->stats->err++;
    #endif
        SYS_ARCH_UNPROTECT(old_level);
        LWIP_DEBUGF(MEMP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("memp_malloc: out of memory in pool %s\n", desc->desc));
      }
    
      return NULL;
    }

    ①分配内存时先进行保护

    ②获取内存池第一个空闲块的地址

    ③将内存池空闲块的指针指向当前块的下一个,并将当前块的next设置为NULL

    ④将获取的空闲内存块的地址返回(此处是memp + MEMP_SIZE)

    上图从内存池中分配一个内存块后的内存布局

    上图是从内存池中再次分配一个内存块后的内存布局

    释放内存

    #define LWIP_MEMPOOL_FREE(name, x) memp_free_pool(&memp_ ## name, (x))
    /**
     * Put a custom pool element back into its pool.
     *
     * @param desc the pool where to put mem
     * @param mem the memp element to free
     */
    void
    memp_free_pool(const struct memp_desc *desc, void *mem)
    {
      LWIP_ASSERT("invalid pool desc", desc != NULL);
      if ((desc == NULL) || (mem == NULL)) {
        return;
      }
    
      do_memp_free_pool(desc, mem);
    }
    static void
    do_memp_free_pool(const struct memp_desc *desc, void *mem)
    {
      struct memp *memp;
      SYS_ARCH_DECL_PROTECT(old_level);  //①
    
      LWIP_ASSERT("memp_free: mem properly aligned",
                  ((mem_ptr_t)mem % MEM_ALIGNMENT) == 0);
    
      /* cast through void* to get rid of alignment warnings */
      memp = (struct memp *)(void *)((u8_t *)mem - MEMP_SIZE);  //②
    
      SYS_ARCH_PROTECT(old_level);    //③
    
    #if MEMP_OVERFLOW_CHECK == 1
      memp_overflow_check_element(memp, desc);
    #endif /* MEMP_OVERFLOW_CHECK */
    
    #if MEMP_STATS
      desc->stats->used--;
    #endif
    
    #if MEMP_MEM_MALLOC
      LWIP_UNUSED_ARG(desc);
      SYS_ARCH_UNPROTECT(old_level);
      mem_free(memp);
    #else /* MEMP_MEM_MALLOC */
      memp->next = *desc->tab;   //④
      *desc->tab = memp;  //⑤
    
    #if MEMP_SANITY_CHECK
      LWIP_ASSERT("memp sanity", memp_sanity(desc));
    #endif /* MEMP_SANITY_CHECK */
    
      SYS_ARCH_UNPROTECT(old_level);
    #endif /* !MEMP_MEM_MALLOC */
    }

    ①声明保护

    ②获取内存块的首地址

    ③对释放过程进行保护

    ④将要释放的内存块的next指向内存池中的空闲块的地址

    ⑤将内存池的空闲块指针指向当前块

    上图中内存块D释放后的内存布局

    至此,我们基本讲解完了LwIP的内存池实现(原理上还是很简单的)

    物联沃分享整理
    物联沃-IOTWORD物联网 » LwIP系列教程:深入解析内存管理(内存池)

    发表评论