【lwip】04内存管理


前言

想说的:

内存的动态申请&释放最重要的参考是指针;

申请得到的内存返回的是可用空间的其实地址(指针);

释放时也是传入该地址(指针)让内部算法进行释放。

一般这些地址前面部分是内存分配器管理的空间,用于管理本小段内存。

李柱明博客:https://www.cnblogs.com/lizhuming/p/15487079.html

4. 内存管理

lwip 提供两种简单高效的动态内存管理策略:

  1. 动态内存堆管理(heap)
  2. 动态内存池管理(pool)

4.1 内存分配策略

一般内存分配策略分两种:

  1. 分配固定大小的内存块;
  2. 利用内存堆进行动态分配。

lwip 支持 C 标准库中的 malloc 和 free 进行内存分配。(不建议使用)

4.1.1 固定大小的内存块

系统在初始化的时候会把可用的内存划分为 N 块固定大小的内存。然后通过单链表的方式把这些内存块连接起来。

用户申请的时候直接在链表的头部取出,且只能申请到固定大小的内存块。

释放的时候把内存块放到链表头部即可。

优点:

  • 分配效率高。
  • 没有内存碎片。

缺点:

  • 可能会浪费内存。(如:单个内存块的单位很大,而实际申请使用很小)

4.1.2 可变大小分配

可变大小分配的算法有很多种,lwip 采用 First Fit(首次拟合)内存管理算法:

  • 申请内存时只要找到一个比所请求的内存大的空闲块,就从中切割出合适的块,并把剩余的部分返回到动态内存堆中;

  • 这种分配策略分配的内存块大小有限制,要求请求的分配大小不能小于 MIN_SIZE,否则请求会被分配到 MIN_SIZE 大小的内存空间;

  • 在申请到的内存前面几个字节是内存分配器管理用的私有数据,也就是本段内存控制块,不允许用户修改。

结构参考:

优点:

  • 在限定上、下线的条件下,用户可自由申请需要的大小空间。
  • 首次拟合,分配速度稍快。

缺点:

  • 容易造成内存碎片。

    • 内存碎片:内存空间是有的,都是不连续,都是很小块。用户在申请大空间时就会申请失败。

4.2 动态内存池(pool)

lwip 使用到动态内存池的原因是很多协议首部或者控制块是固定大小的。

4.2.1 介绍

系统将所有可用区域以固定大小的字节单位进行划分,然后用单链表将所有空闲内存块连接起来。

同一链表中,所有节点大小都是相同的。这种分配只是前面讲的((20210803155807-x09b60h))的一个升级。

申请大小必须是指定固定大小字节的值(如 4、8、16 等等)。

lwip 源文件中 memp.cmemp.h 就是动态内存池分配策略。

4.2.2 内存池的预处理

在 lwip 内存初始化时,会初始化相应的内存池:

内核按照宏配置以固定的单位划分内存,然后用链表管理所有空闲块,组成一个内存池。

使用:

  • 外边提供 LWIP_MEMPOOL 宏定义,然后在包含 memp_std.h 文件,编译器就会处理。

参考例子:

  • // memp_std.h 文件
    
    #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 */
    
  • // 使用
    
    typedef enum
    {
    #define LWIP_MEMPOOL(name,num,size,desc)  MEMP_##name,
    #include "lwip/priv/memp_std.h"
        MEMP_MAX
    } memp_t;
    
  • // 预编译结果。 (假设宏都开通了)
    
    typedef enum
    {
        MEMP_RAW_PCB,
        MEMP_UDP_PCB,
        MEMP_MAX
    } memp_t;
    

memp_t 类型在整个内存池的管理中是最重要的存在。

通过内存池申请内存的时候,唯一的参数就是 memp_t 类型的,通过该类型告知分配函数去哪个 pool 申请内存块。

4.2.3 内存池的初始化

lwip 初始化时,会调用 memp_init() 函数对内存池进行初始化。

源码参考:((20210803171034-68omo3p))

说明:

  • MEMP_SIZE:内存分配器管理的空间。
  • desc->size:单个内存块大小。

4.2.4 内存分配

API:memp_malloc(memp_t type);,参数为内存类型。参考:((20210803185249-o03r2pp))

申请时直接从对应链表中拿出第一个空闲块。

主要代码为:memp = *desc->tab; // 核心

4.2.5 内存释放

API:memp_free(memp_t type, void *mem);。参考:((20210803190146-rtjgyhd))

使用完毕的块插回到对应链表。

4.3 动态内存堆

动态内存堆管理(heap)分为:

  • C 标准库自带的内存管理策略。
  • lwip 自身实现的内存堆管理策略。

C 和 lwip 实现的内存堆管理在 lwip 中只能通过宏 MEM_LIBC_MALLOC 来进行二选一。

lwip 的内存池和内存堆设计非常灵活:

  1. 通过宏 MEM_USE_POOLS 可以使能内存堆基于内存池实现
  2. 通过宏 MEMP_MEM_MALLOC 可以实现内存池基于内存堆实现

4.3.1 内存堆组织结构

源码&注释参考:((20210803195754-ppimyyp))

4.3.2 内存堆初始化

lwip 初始化时,会调用 mem_init() 函数对内存池进行初始化。

源码及注释参考:((20210803203835-ay0l1ou))

其简要内容:

  • 初始化该堆内存。
  • 初始化时,内存是空的,所以只有一个空闲的内存块。
  • 该空闲的内存块上一个为空,下一个为结尾。
  • 为了分支空闲块链表往下找,所以来个结尾内存块,一直标记为已使用,且上一个、下一个都指向本身。
  • 由上得出以下内存结构:

4.3.3 内存堆分配

API:mem_malloc(mem_size_t size_in);:源码参考((20210803210417-ezc7i9y))

简要内容:(其过程可以在脑海里想象一下)其实就是找到一个够大的空闲块。

  • 遍历空闲块链表,找到一个够大的空闲块。

  • 若该块足够大,被申请后剩余的空间能够组成一个新的空闲块节点(大于 mem 结构体大小 + 最小空闲空间大小),那就组成一个新的空闲块,初始化并插入空闲块链表。

    • 注意,组成新的节点要判断该节点的下一个节点是不是整个内存堆的结尾节点,若是,则不用管下一个节点的前一个变量。若不是,则要赋值下一个节点的前一个是新的节点。(就是双向链表中插入算法的步骤之一,这个步骤遇到内存堆尾节点是不用管的)
  • 若申请后剩余的空间不够组成新的空闲块节点,则不用创建新的空闲块节点。

  • 申请成功后返回的是用户实际可用的地址。该地址前面就是控制块,用户切记不要试图修改控制块内容。

4.3.4 内存堆释放

API:mem_free(void *rmem);:源码参考:

根据用户释放的内存块地址,通过偏移 mem 结构体大小得到正确的内存块起始地址,并且根据 mem 中保存的内存块信息进行释放、合并等操作,并将 used 字段清零,表示该内存块未被使用。

简要内容:

  • 过滤非法指针。
  • 找到需要是否的内存块控制块。
  • 若该内存块已被使用则释放。
  • 释放后检查该内存块在空闲链表中是否有效。
  • 若下一个内存块为空闲,则合并内存块。

4.4 使用 C 库的 malloc 和 free 来管理内存

使用宏 MEM_LIBC_MALLOC 来决定使用 C 库的还是使用 lwip 自己实现的。

使用 C 库的配置:

 #if MEM_LIBC_MALLOC
 void
 mem_init(void)
 {
 }
 void *
 mem_trim(void *mem, mem_size_t size)
 {
     LWIP_UNUSED_ARG(size);
     return mem;
 }

 #ifndef mem_clib_free
 #define mem_clib_free free
 #endif
 #ifndef mem_clib_malloc
 #define mem_clib_malloc malloc
 #endif
 #ifndef mem_clib_calloc
 #define mem_clib_calloc calloc
 #endif

 #define MEM_LIBC_STATSHELPER_SIZE 0

 #endif

使用 c 库中的系统内存块作为内存堆,其接口也是 c 库封装的,所以我们不必做什么处理,只要把 c 库的动态内存管理接口交给 lwip 封装即可。

4.5 lwip 中的配置

4.5.1 几个重要的宏

  • MEM_LIBC_MALLOC:该宏定义是否使用 C 标准库自带的内存分配策略。默认为 0,不使用。

    • 为 0:使用 lwip 自己实现的动态内存策略:动态内存池和动态内存堆。

      • MEMP_MEM_MALLOC:该宏定义表示是否使用 LwIP 内存堆分配策略实现内存池分配。默认为 0。
      • MEM_USE_POOLS:该宏定义表示是否使用 LwIP 内存池分配策略实现内存堆的分配。默认为 0。
      • 上面两个宏只能只能开其一。

4.5.2 内存池的方式实现内存堆分配配置

宏配置

#define MEM_USE_POOLS 1
#define MEMP_USE_CUSTOM_POOLS 1
#define MEMP_MEM_MALLOC 0

lwippools.h

LWIP_MALLOC_MEMPOOL_START

LWIP_MALLOC_MEMPOOL(20, 256)
LWIP_MALLOC_MEMPOOL(10, 512)
LWIP_MALLOC_MEMPOOL(5, 1512)

LWIP_MALLOC_MEMPOOL_END

注意:

  • 内存池的大小要依次增大,在编译阶段,编译器就会将这些内存个数及大小添加到系统的内存池之中。

    • 这样在用户申请内存的时候,最匹配的内存池中的内存块已经用完,可选择更大的内存池进行匹配,按小到达是为了能够高效匹配到内存块。

附件-代码

memp_init();

/**
 * Initializes lwIP built-in pools.
 * Related functions: memp_malloc, memp_free
 *
 * Carves out memp_memory into linked lists for each pool-type.
 */
void
memp_init(void)
{
  u16_t i;

  /* for every pool: */
  for (i = 0; i < LWIP_ARRAYSIZE(memp_pools); i++) {
    memp_init_pool(memp_pools[i]);

    /* 静态部分 */
#if LWIP_STATS && MEMP_STATS
    lwip_stats.memp[i] = memp_pools[i]->stats;
#endif
  }

#if MEMP_OVERFLOW_CHECK >= 2
  /* check everything a first time to see if it worked */
  memp_overflow_check_all();
#endif /* MEMP_OVERFLOW_CHECK >= 2 */
}

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

/**
 * 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);
  /* 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
      + MEMP_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_malloc();

/**
 * Get an element from a specific pool.
 *
 * @param type the pool to get an element from
 *
 * @return a pointer to the allocated memory or a NULL pointer on error
 */
void *
#if !MEMP_OVERFLOW_CHECK
memp_malloc(memp_t type)
#else
memp_malloc_fn(memp_t type, const char* file, const int line)
#endif
{
  void *memp;
  LWIP_ERROR("memp_malloc: type < MEMP_MAX", (type < MEMP_MAX), return NULL;);

#if MEMP_OVERFLOW_CHECK >= 2
  memp_overflow_check_all();
#endif /* MEMP_OVERFLOW_CHECK >= 2 */

#if !MEMP_OVERFLOW_CHECK
  memp = do_memp_malloc_pool(memp_pools[type]);
#else
  memp = do_memp_malloc_pool_fn(memp_pools[type], file, line);
#endif

  return memp;
}

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_overflow(memp, desc);
    memp_overflow_check_element_underflow(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 {
    LWIP_DEBUGF(MEMP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, (0x10007901, "memp_malloc: out of memory in pool\n"));
#if MEMP_STATS
    desc->stats->err++;
#endif
  }

  SYS_ARCH_UNPROTECT(old_level);
  return NULL;
}

memp_free();

/**
 * Put an element back into its pool.
 *
 * @param type the pool where to put mem
 * @param mem the memp element to free
 */
void
memp_free(memp_t type, void *mem)
{
#ifdef LWIP_HOOK_MEMP_AVAILABLE
  struct memp *old_first;
#endif

  LWIP_ERROR("memp_free: type < MEMP_MAX", (type < MEMP_MAX), return;);

  if (mem == NULL) {
    return;
  }

#if MEMP_OVERFLOW_CHECK >= 2
  memp_overflow_check_all();
#endif /* MEMP_OVERFLOW_CHECK >= 2 */

#ifdef LWIP_HOOK_MEMP_AVAILABLE
  old_first = *memp_pools[type]->tab;
#endif

  do_memp_free_pool(memp_pools[type], mem);

#ifdef LWIP_HOOK_MEMP_AVAILABLE
  if (old_first == NULL) {
    LWIP_HOOK_MEMP_AVAILABLE(type);
  }
#endif
}

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_overflow(memp, desc);
  memp_overflow_check_element_underflow(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 */
}

内存堆组织结构源码

// 位于文件 mem.c

/**
 * The heap is made up as a list of structs of this type.
 * This does not have to be aligned since for getting its size,
 * we only use the macro SIZEOF_STRUCT_MEM, which automatically aligns.
 */
struct mem {
  /** index (-> ram[next]) of the next struct */
  mem_size_t next; // 指向下一个内存块。并非指针,而是与堆头的偏移。
  /** index (-> ram[prev]) of the previous struct */
  mem_size_t prev; // 指向上一个内存块。并非指针,而是与堆头的偏移。
  /** 1: this area is used; 0: this area is unused */
  u8_t used; // 标记内存是否已经被使用。
};

/** All allocated blocks will be MIN_SIZE bytes big, at least!
 * MIN_SIZE can be overridden to suit your needs. Smaller values save space,
 * larger values could prevent too small blocks to fragment the RAM too much. */
#ifndef MIN_SIZE
#define MIN_SIZE             12 // 申请的内存最小为12字节
#endif /* MIN_SIZE */
/* some alignment macros: we define them here for better source code layout */
#define MIN_SIZE_ALIGNED     LWIP_MEM_ALIGN_SIZE(MIN_SIZE)
#define SIZEOF_STRUCT_MEM    LWIP_MEM_ALIGN_SIZE(sizeof(struct mem))
#define MEM_SIZE_ALIGNED     LWIP_MEM_ALIGN_SIZE(MEM_SIZE)

/** If you want to relocate the heap to external memory, simply define
 * LWIP_RAM_HEAP_POINTER as a void-pointer to that location.
 * If so, make sure the memory at that location is big enough (see below on
 * how that space is calculated). */
#ifndef LWIP_RAM_HEAP_POINTER
/** the heap. we need one struct mem at the end and some room for alignment */
LWIP_DECLARE_MEMORY_ALIGNED(ram_heap, MEM_SIZE_ALIGNED + (2U*SIZEOF_STRUCT_MEM)); // 内核的内存堆空间
#define LWIP_RAM_HEAP_POINTER ram_heap // 重命名 内核的内存堆空间
#endif /* LWIP_RAM_HEAP_POINTER */

/** pointer to the heap (ram_heap): for alignment, ram is now a pointer instead of an array */
static u8_t *ram; // 指向内存堆对齐后的起始地址
/** the last entry, always unused! */
static struct mem *ram_end; // 指向内存堆中最后一个内存块。一直未使用。
/** pointer to the lowest free block, this is used for faster search */
static struct mem *lfree; // 空闲内存块链表指针。

/** concurrent access protection */
#if !NO_SYS
static sys_mutex_t mem_mutex; // 互斥量
#endif

mem_init();

/**
 * Zero the heap and initialize start, end and lowest-free
 */
void
mem_init(void)
{
  struct mem *mem;

  LWIP_ASSERT("Sanity check alignment",
    (SIZEOF_STRUCT_MEM & (MEM_ALIGNMENT-1)) == 0);

  /* align the heap */
  ram = (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER); // 内存堆对齐后的起始地址被记录在ram中
  /* initialize the start of the heap */
  mem = (struct mem *)(void *)ram; // 在内存堆起始位置放置一个mem类型的结构体,因为初始化后的内存堆就是一个大的空闲内存块,每个空闲内存块的前面都需要放置一个mem结构体
  mem->next = MEM_SIZE_ALIGNED; // 下一个内存块的偏移量为MEM_SIZE_ALIGNED,这相对于直接到内存堆的结束地址了
  mem->prev = 0; // 上一个内存块为空
  mem->used = 0; // 未被使用
  /* initialize the end of the heap */
  ram_end = (struct mem *)(void *)&ram[MEM_SIZE_ALIGNED]; // 内存堆末尾的位置放置一个mem类型的结构体,并初始化表示内存堆结束的内存块。
  ram_end->used = 1; // 一直被使用。就是不给用户使用(因为后面没有空闲内存)。
  ram_end->next = MEM_SIZE_ALIGNED; // 指回本身
  ram_end->prev = MEM_SIZE_ALIGNED; // 指回本身

  /* initialize the lowest-free pointer to the start of the heap */
  lfree = (struct mem *)(void *)ram; // 空闲内存块链表指针,因为只有一块,所以先指向第一块

  MEM_STATS_AVAIL(avail, MEM_SIZE_ALIGNED);

  if (sys_mutex_new(&mem_mutex) != ERR_OK) { // 创建一个内存堆分配时候使用的互斥量,如果是无操作系统的情况。(在OS下才有效)
    LWIP_ASSERT("failed to create mem_mutex", 0);
  }
}

mem_malloc();

/**
 * Allocate a block of memory with a minimum of 'size' bytes.
 *
 * @param size is the minimum size of the requested block in bytes.
 * @return pointer to allocated memory or NULL if no free memory was found.
 *
 * Note that the returned value will always be aligned (as defined by MEM_ALIGNMENT).
 */
void *
mem_malloc(mem_size_t size)
{
  mem_size_t ptr, ptr2;
  struct mem *mem, *mem2;
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
  u8_t local_mem_free_count = 0;
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
  LWIP_MEM_ALLOC_DECL_PROTECT();

  if (size == 0) {
    return NULL;
  }

  /* Expand the size of the allocated memory region so that we can
     adjust for alignment. */
  size = LWIP_MEM_ALIGN_SIZE(size); // 字节大小进行字节对齐。

  if (size < MIN_SIZE_ALIGNED) { // 对比申请内存大小最小值
    /* every data block must be at least MIN_SIZE_ALIGNED long */
    size = MIN_SIZE_ALIGNED;
  }

  if (size > MEM_SIZE_ALIGNED) { // 申请的大小大于整个堆空间,就申请失败
    return NULL;
  }

  /* protect the heap from concurrent access */
  sys_mutex_lock(&mem_mutex); // 上锁
  LWIP_MEM_ALLOC_PROTECT();
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
  /* run as long as a mem_free disturbed mem_malloc or mem_trim */
  do {
    local_mem_free_count = 0;
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */

    /* Scan through the heap searching for a free block that is big enough,
     * beginning with the lowest free block.
     */
    for (ptr = (mem_size_t)((u8_t *)lfree - ram); ptr < MEM_SIZE_ALIGNED - size;
         ptr = ((struct mem *)(void *)&ram[ptr])->next) { // 遍历空闲内存块链表,直到找到第一个适合用户需求的内存块大小。
      mem = (struct mem *)(void *)&ram[ptr]; // 得到这个内存块起始地址。
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
      mem_free_count = 0;
      LWIP_MEM_ALLOC_UNPROTECT();
      /* allow mem_free or mem_trim to run */
      LWIP_MEM_ALLOC_PROTECT();
      if (mem_free_count != 0) {
        /* If mem_free or mem_trim have run, we have to restart since they
           could have altered our current struct mem. */
        local_mem_free_count = 1;
        break;
      }
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */

      if ((!mem->used) &&
          (mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size) { // 如果该内存块是未使用的,并且它的大小不小于用户需要的大小加上mem结构体的大小,就满足用户的需求。
        /* mem is not used and at least perfect fit is possible:
         * mem->next - (ptr + SIZEOF_STRUCT_MEM) gives us the 'user data size' of mem */

        if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >= (size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED)) { // 是否需要把本段剩下的组成一个新的内存块
          /* (in addition to the above, we test if another struct mem (SIZEOF_STRUCT_MEM) containing
           * at least MIN_SIZE_ALIGNED of data also fits in the 'user data space' of 'mem')
           * -> split large block, create empty remainder,
           * remainder must be large enough to contain MIN_SIZE_ALIGNED data: if
           * mem->next - (ptr + (2*SIZEOF_STRUCT_MEM)) == size,
           * struct mem would fit in but no data between mem2 and mem2->next
           * @todo we could leave out MIN_SIZE_ALIGNED. We would create an empty
           *       region that couldn't hold data, but when mem->next gets freed,
           *       the 2 regions would be combined, resulting in more free memory
           */
          ptr2 = ptr + SIZEOF_STRUCT_MEM + size; // 剩下空闲空间组成块
          /* create mem2 struct */
          mem2 = (struct mem *)(void *)&ram[ptr2]; // 赋值初始化&把新的节点插入链表
          mem2->used = 0;
          mem2->next = mem->next;
          mem2->prev = ptr;
          /* and insert it between mem and mem->next */
          mem->next = ptr2;
          mem->used = 1; // 标记申请到的已使用

          if (mem2->next != MEM_SIZE_ALIGNED) { // 如果不是结尾节点,就把其下一节点的节点内容补充完善。(其实就是插入节点操作中的一部分)
            ((struct mem *)(void *)&ram[mem2->next])->prev = ptr2;
          }
          MEM_STATS_INC_USED(used, (size + SIZEOF_STRUCT_MEM));
        } else { // 如果剩下空间不够组成一个新的节点,那就不组新的。直接使用。
          /* (a mem2 struct does no fit into the user data space of mem and mem->next will always
           * be used at this point: if not we have 2 unused structs in a row, plug_holes should have
           * take care of this).
           * -> near fit or exact fit: do not split, no mem2 creation
           * also can't move mem->next directly behind mem, since mem->next
           * will always be used at this point!
           */
          mem->used = 1;
          MEM_STATS_INC_USED(used, mem->next - (mem_size_t)((u8_t *)mem - ram));
        }
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
mem_malloc_adjust_lfree:
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
        if (mem == lfree) { // 如果成功申请到,那就更新空闲块链表指针
          struct mem *cur = lfree; // 找到第一个低地址的空闲内存块。
          /* Find next free block after mem and update lowest free pointer */
          while (cur->used && cur != ram_end) {
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
            mem_free_count = 0;
            LWIP_MEM_ALLOC_UNPROTECT();
            /* prevent high interrupt latency... */
            LWIP_MEM_ALLOC_PROTECT();
            if (mem_free_count != 0) {
              /* If mem_free or mem_trim have run, we have to restart since they
                 could have altered our current struct mem or lfree. */
              goto mem_malloc_adjust_lfree;
            }
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
            cur = (struct mem *)(void *)&ram[cur->next];
          }
          lfree = cur; // 将lfree指向该空闲内存块。
          LWIP_ASSERT("mem_malloc: !lfree->used", ((lfree == ram_end) || (!lfree->used)));
        }
        LWIP_MEM_ALLOC_UNPROTECT();
        sys_mutex_unlock(&mem_mutex); // 解锁
        LWIP_ASSERT("mem_malloc: allocated memory not above ram_end.",
         (mem_ptr_t)mem + SIZEOF_STRUCT_MEM + size <= (mem_ptr_t)ram_end);
        LWIP_ASSERT("mem_malloc: allocated memory properly aligned.",
         ((mem_ptr_t)mem + SIZEOF_STRUCT_MEM) % MEM_ALIGNMENT == 0);
        LWIP_ASSERT("mem_malloc: sanity check alignment",
          (((mem_ptr_t)mem) & (MEM_ALIGNMENT-1)) == 0);

        return (u8_t *)mem + SIZEOF_STRUCT_MEM; // 返回内存块可用的起始地址
      }
    }
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
    /* if we got interrupted by a mem_free, try again */
  } while (local_mem_free_count != 0);
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */
  LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SERIOUS, (0x100078ff, "mem_malloc: could not allocate %hd bytes\n", (s16_t)size));
  MEM_STATS_INC(err);
  LWIP_MEM_ALLOC_UNPROTECT();
  sys_mutex_unlock(&mem_mutex); // 解锁
  return NULL; // 申请失败
}

mem_free();

/**
 * Put a struct mem back on the heap
 *
 * @param rmem is the data portion of a struct mem as returned by a previous
 *             call to mem_malloc()
 */
void
mem_free(void *rmem)
{
    struct mem *mem;
    LWIP_MEM_FREE_DECL_PROTECT();

    if (rmem == NULL) // 空就返回
    {
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_TRACE |
                    LWIP_DBG_LEVEL_SERIOUS,
                    ("mem_free(p == NULL) was called.\n"));
        return;
    }
    if ((((mem_ptr_t)rmem) & (MEM_ALIGNMENT - 1)) != 0)
    {
        LWIP_MEM_ILLEGAL_FREE("mem_free: sanity check alignment");
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE,
                    ("mem_free: sanity check alignment\n"));
        /* protect mem stats from concurrent access */
        MEM_STATS_INC_LOCKED(illegal);
        return;
    }

    mem = (struct mem *)(void *)((u8_t *)rmem -
        (SIZEOF_STRUCT_MEM + MEM_SANITY_OFFSET));   // 找到控制块

    if ((u8_t *)mem < ram ||
            (u8_t *)rmem + MIN_SIZE_ALIGNED > (u8_t *)ram_end) // 对释放的地址进行偏移,得到真正内存块的起始地址
    {
        LWIP_MEM_ILLEGAL_FREE("mem_free: illegal memory");
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE,
                    ("mem_free: illegal memory\n"));
        /* protect mem stats from concurrent access */
        MEM_STATS_INC_LOCKED(illegal);
        return;
    }

    /* protect the heap from concurrent access */
    LWIP_MEM_FREE_PROTECT();

    /* mem has to be in a used state */
    if (!mem->used) // 要释放的内存块是否被使用,未被使用就直接返回
    {
        LWIP_MEM_ILLEGAL_FREE("mem_free: illegal \
    memory: double free");
        LWIP_MEM_FREE_UNPROTECT();
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE,
                    ("mem_free: illegal memory: double free?\n"));
        /* protect mem stats from concurrent access */
        MEM_STATS_INC_LOCKED(illegal);
        return;
    }

    if (!mem_link_valid(mem)) // 判断一下内存块在链表中的连接是否正常,如果不正常也直接返回
    {
        LWIP_MEM_ILLEGAL_FREE("mem_free: illegal memory:\
    non-linked: double free");
        LWIP_MEM_FREE_UNPROTECT();
        LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE,
                ("mem_free: illegal memory: non-linked: double free?\n"));
        /* protect mem stats from concurrent access */
        MEM_STATS_INC_LOCKED(illegal);
        return;
    }

    /* mem is now unused. */
    mem->used = 0; // 标记为未使用

    if (mem < lfree)
    {
        /* the newly freed struct is now the lowest */
        lfree = mem; // 更新lfree指针
    }

    MEM_STATS_DEC_USED(used, mem->next -
                    (mem_size_t)(((u8_t *)mem - ram)));

    /* finally, see if prev or next are free also */
    plug_holes(mem);  // 判断下一个内存块是否为空,为空则合并
    MEM_SANITY();

    LWIP_MEM_FREE_UNPROTECT();
}

 static void
 plug_holes(struct mem *mem)
 {
     struct mem *nmem;
     struct mem *pmem;

     LWIP_ASSERT("plug_holes: mem >= ram", (u8_t *)mem >= ram);
     LWIP_ASSERT("plug_holes: mem < ram_end",
                 (u8_t *)mem < (u8_t *)ram_end);
     LWIP_ASSERT("plug_holes: mem->used == 0", mem->used == 0);

     /* plug hole forward */
     LWIP_ASSERT("plug_holes: mem->next <= MEM_SIZE_ALIGNED",
                 mem->next <= MEM_SIZE_ALIGNED);

     nmem = ptr_to_mem(mem->next);
     if (mem != nmem && nmem->used == 0 &&
             (u8_t *)nmem != (u8_t *)ram_end)
     {
     /* if mem->next is unused and not end of ram, combine mem and mem->next */
         if (lfree == nmem)
         {
             lfree = mem;
         }
         mem->next = nmem->next;
         if (nmem->next != MEM_SIZE_ALIGNED)
         {
             ptr_to_mem(nmem->next)->prev = mem_to_ptr(mem);
         }
     }

     /* plug hole backward */
     pmem = ptr_to_mem(mem->prev);
     if (pmem != mem && pmem->used == 0)
     {
         /* if mem->prev is unused, combine mem and mem->prev */
         if (lfree == mem)
         {
             lfree = pmem;
         }
         pmem->next = mem->next;
         if (mem->next != MEM_SIZE_ALIGNED)
         {
             ptr_to_mem(mem->next)->prev = mem_to_ptr(pmem);
         }
     }
 }
原文地址:https://www.cnblogs.com/lizhuming/p/15487079.html