【lwip】04-記憶體管理


前言

想說的:

記憶體的動態申請&釋放最重要的參考是指針;

申請得到的記憶體返回的是可用空間的其實地址(指針);

釋放時也是傳入該地址(指針)讓內部演算法進行釋放。

一般這些地址前面部分是記憶體分配器管理的空間,用於管理本小段記憶體。

李柱明部落格://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.c 和 memp.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);
         }
     }
 }