对于嵌入式开发来说,内存管理及使用是至关重要的,内存的使用多少、内存泄漏等时刻需要注意!合理的内存管理策略将从根本上决定内存分配和回收效率,最终决定系统的整体性能。LwIP 就提供了 动态内存堆管理(heap)和 动态内存池管理(pool)两种简单却又高效的动态内存管理机制。

  目前,网络上多数文章所使用的 LwIP 版本为 1.4.1。最新版本为 2.0.3。从 1.4.1 到 2.0.3(貌似从 2.0.0 开始),LwIP 的源码有了一定的变化,甚至于源码的文件结构也不一样,内部的一些实现源文件也被更新和替换了。

  • 2023.4.25 同步到 2.1.3 最新版

简介

  LwIP 为了能够灵活的使用内存,为使用者提供两种简单却又高效的动态内存管理机制:动态内存堆管理(heap)和动态内存池管理(pool) 。这两中内存管理策略的实现分别对应着源码文件 mem.c/hmemp.c/h

  LwIP 在自己的内存堆和内存池具体实现上也比较灵活。内存池可有由内存堆实现,通过宏值 MEMP_MEM_MALLOC 来选择;反之,内存堆也可以有内存池实现,通过宏值 MEM_USE_POOLS 来选择,且二者只能选择其一。

  其中,动态内存堆管理(heap)又可以分为两种: C 运行时库自带的内存分配策略、LwIP自己实现的内存堆分配策略。这两者的选择需要通过宏值 MEM_LIBC_MALLOC 来选择,且二者只能选择其一。

内存堆

  动态内存堆分配策略原理就是在一个事先定义好大小的内存块中进行管理,其内存分配的策略是采用最快合适(First Fit)方式,只要找到一个比所请求的内存大的空闲块,就从中切割出合适的块,并把剩余的部分返回到动态内存堆中。内存的释放时,重新将申请到的内存返回堆中。

关于内存堆的详细介绍,参见独立博文 LwIP 之五 详解内存堆(mem.c/h)动态内存管理策略。

  其优点就是内存浪费小,比较简单,适合用于小内存的管理,其缺点就是如果频繁的动态分配和释放,可能会造成严重的内存碎片,如果在碎片情况严重的话,可能会导致内存分配不成功。

内存池

  内存池(Memory Pool) 是一种内存分配方式,又被称为固定大小区块规划(fixed-size-blocks allocation)。内存池的特点是预先开辟多组固定大小的内存块(为了适应不同场景,可能定义多种类型的内存池)组织成链表,实现简单,分配和回收速度快,不会产生内存碎片,但是大小固定,并且需要预估算准确。

配置

  在 LwIP 中,内存的选择是通过以下这几个宏值来决定的,根据用户对宏值的定义值来判断使用那种内存管理策略。但是,无论宏值怎么配置,LwIP 中都会同时有 内存堆内存池 这两种内存管理策略(不能只用一种)。

内存堆

  根据内存堆的内存从哪里分配而来,可以将内存堆分为 C 库、内存池、全局数组 ram_heap 这三种不同的实现方式,具体使用那种方式由以下宏值来决定:

  • MEM_LIBC_MALLOC: 该宏值定义是否使用 C 运行时库自带的内存分配策略来分配内存堆的内存。该值默认情况下为 0,表示不使用 C 运行时库自带的内存分配策略。如果要使用 C 运行时库自带的分配策略,则需要把该值定义为 1。

  • MEM_USE_POOLS: 该宏值定义是否使用 LwIP 内存池分配策略实现内存堆的分配(即:要从内存堆中获取内存时,实际是从内存池中分配)。默认情况下为 0,表示不使用。要使用内存池的方式,则需要将该宏值定义为 1,除此之外还需要做一下处理:

    1. #define MEMP_USE_CUSTOM_POOLS 1
    2. 新建文件 lwippools.h,并且在该文件中定义如下内存池(想多定义几个时,必须在宏 LWIP_MALLOC_MEMPOOL_STARTLWIP_MALLOC_MEMPOOL_END 之间添加):
      LWIP_MALLOC_MEMPOOL_START
      LWIP_MALLOC_MEMPOOL(20, 256)
      LWIP_MALLOC_MEMPOOL(10, 512)
      LWIP_MALLOC_MEMPOOL(5, 1512)
      LWIP_MALLOC_MEMPOOL_END
      
    3. MEMP_MEM_MALLOC 只能选择其一
  • 全局数组 ram_heap:当我们即不选择 MEM_LIBC_MALLOC,也不选择 MEM_USE_POOLS 时,默认就是使用 全局数组 ram_heap 来分配内存堆的内存。这也是 LwIP 的默认方式。

    • MIN_SIZE:定义了内存堆的大小

内存池

  内存池的内存来源不像内存堆那么多,只有 内存堆独立数组 这两种方式,但是其实现方法却极其巧妙,甚至有些颠覆我们对于 C 语言的认知,对于新手非常难以理解。其主要通过以下宏值来选择:

  • MEMP_MEM_MALLOC: 该宏值定义是否使用 LwIP 内存堆分配策略实现内存池分配(即:要从内存池中获取内存时,实际是从内存堆中分配)。由于内存堆可以分配任意大小,因此,没有过多复杂调用,在实际对外即可直接调用 mem_malloc 分配需要的内存即可
  • 默认情况下为 0,表示不从内存堆中分配,每一种内存池都是独立一块内存实现

  为了使用多种应用场景,LwIP 定义了多种内存池,无论是 内存堆 方式,还是 独立数组 这种方式,内存池都是通过顶一个全局数组 memp_pools 来索引所有内存池。

内存对齐

  一般来说,每一种处理器都会有自己的内存对齐要求,这样做的目的很大程度上是为了处理器读取内存数据的效率,且与对应硬件上的设计也有很大的关系。LwIP 中,对于内存的操作函数都用到了内存对齐。在 arch.h 中就包含了内存相关的配置项:

 /* 定义全局数组作为内存堆的内存,LwIP 就是实现的如何管理这块内存的。这块内存时经过对其操作的 */
#ifndef LWIP_DECLARE_MEMORY_ALIGNED
#define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) u8_t variable_name[LWIP_MEM_ALIGN_BUFFER(size)]
#endif/** Calculate memory size for an aligned buffer - returns the next highest* multiple of MEM_ALIGNMENT (e.g. LWIP_MEM_ALIGN_SIZE(3) and* LWIP_MEM_ALIGN_SIZE(4) will both yield 4 for MEM_ALIGNMENT == 4).*//* 数据占用空间大小对齐计算 */
#ifndef LWIP_MEM_ALIGN_SIZE
#define LWIP_MEM_ALIGN_SIZE(size) (((size) + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT-1U))
#endif/** Calculate safe memory size for an aligned buffer when using an unaligned* type as storage. This includes a safety-margin on (MEM_ALIGNMENT - 1) at the* start (e.g. if buffer is u8_t[] and actual data will be u32_t*)*/
#ifndef LWIP_MEM_ALIGN_BUFFER
#define LWIP_MEM_ALIGN_BUFFER(size) (((size) + MEM_ALIGNMENT - 1U))
#endif/** Align a memory pointer to the alignment defined by MEM_ALIGNMENT* so that ADDR % MEM_ALIGNMENT == 0*//* 数据起始地址对齐 */
#ifndef LWIP_MEM_ALIGN
#define LWIP_MEM_ALIGN(addr) ((void *)(((mem_ptr_t)(addr) + MEM_ALIGNMENT - 1) & ~(mem_ptr_t)(MEM_ALIGNMENT-1)))
#endif

LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size)

  该宏 u8_t variable_name[LWIP_MEM_ALIGN_BUFFER(size)] 用来分配指定大小的内存缓冲区,该缓冲区的起始地址使用 LWIP_MEM_ALIGN 对齐。内存堆和内存池的内存空间都是由该宏值申请的!

LWIP_MEM_ALIGN_SIZE(size)

  这个宏 (((size) + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT-1U)) 的作用就是将指定的大小处理成对其后大小,对其的大小由用户提供宏值 MEM_ALIGNMENT 来决定。其中 size 为想要分配的大小。下面来看看这个宏:

  • ~(MEM_ALIGNMENT-1U): 这一步就是按照对应的对齐字节数据,将二进制的最后最后几位置为 0。例如,MEM_ALIGNMENT 为 4 ,则该步就将后 2 位置为了 0。
  • ((size) + MEM_ALIGNMENT - 1U): 这里其实就是为了在处理时,能够向上取整。

  下面是使用 LWIP_MEM_ALIGN_SIZE(size) 这个宏,针对其中不同 size 值的计算结果(MEM_ALIGNMENT 表示要对其的字节,size 为想要的大小,ALIG 表示对齐后实际的大小)

 MEM_ALIGNMENT = 1       MEM_ALIGNMENT = 2       MEM_ALIGNMENT = 4       MEM_ALIGNMENT = 8
size = 1   ALIG = 1     size = 1   ALIG = 2     size = 1   ALIG = 4     size = 1   ALIG = 8
size = 2   ALIG = 2     size = 2   ALIG = 2     size = 2   ALIG = 4     size = 2   ALIG = 8
size = 3   ALIG = 3     size = 3   ALIG = 4     size = 3   ALIG = 4     size = 3   ALIG = 8
size = 4   ALIG = 4     size = 4   ALIG = 4     size = 4   ALIG = 4     size = 4   ALIG = 8
size = 5   ALIG = 5     size = 5   ALIG = 6     size = 5   ALIG = 8     size = 5   ALIG = 8
size = 6   ALIG = 6     size = 6   ALIG = 6     size = 6   ALIG = 8     size = 6   ALIG = 8
size = 7   ALIG = 7     size = 7   ALIG = 8     size = 7   ALIG = 8     size = 7   ALIG = 8
size = 8   ALIG = 8     size = 8   ALIG = 8     size = 8   ALIG = 8     size = 8   ALIG = 8
size = 9   ALIG = 9     size = 9   ALIG = 10    size = 9   ALIG = 12    size = 9   ALIG = 16
size = 10  ALIG = 10    size = 10  ALIG = 10    size = 10  ALIG = 12    size = 10  ALIG = 16
size = 11  ALIG = 11    size = 11  ALIG = 12    size = 11  ALIG = 12    size = 11  ALIG = 16
size = 12  ALIG = 12    size = 12  ALIG = 12    size = 12  ALIG = 12    size = 12  ALIG = 16
size = 13  ALIG = 13    size = 13  ALIG = 14    size = 13  ALIG = 16    size = 13  ALIG = 16
size = 14  ALIG = 14    size = 14  ALIG = 14    size = 14  ALIG = 16    size = 14  ALIG = 16
size = 15  ALIG = 15    size = 15  ALIG = 16    size = 15  ALIG = 16    size = 15  ALIG = 16
size = 16  ALIG = 16    size = 16  ALIG = 16    size = 16  ALIG = 16    size = 16  ALIG = 16
size = 17  ALIG = 17    size = 17  ALIG = 18    size = 17  ALIG = 20    size = 17  ALIG = 24
size = 18  ALIG = 18    size = 18  ALIG = 18    size = 18  ALIG = 20    size = 18  ALIG = 24
size = 19  ALIG = 19    size = 19  ALIG = 20    size = 19  ALIG = 20    size = 19  ALIG = 24
size = 20  ALIG = 20    size = 20  ALIG = 20    size = 20  ALIG = 20    size = 20  ALIG = 24

LWIP_MEM_ALIGN(addr)

  这个宏用来处理数据起始地址对齐。其处理方式与上面的数据大小的处理没有任何区别。关于数据类型在 arch.h 的开头部分有定义,这里有个文件 stdint.h 需要注意一下!在某写平台,可能没有该文件,需要用户自己来添加。

#if !LWIP_NO_STDINT_H
#include <stdint.h>
typedef uint8_t   u8_t;
typedef int8_t    s8_t;
typedef uint16_t  u16_t;
typedef int16_t   s16_t;
typedef uint32_t  u32_t;
typedef int32_t   s32_t;
typedef uintptr_t mem_ptr_t;   /* 这个通常为一个 unsigned int 类型 */
#endif

为什么要对齐

  这个其实和各种处理器的硬件设计息息相关,具体可以参见该博文《为什么要内存对齐》。

对齐的本质

  首先,这里所说的对其是指 2 k 2^k 2k 字节的对其(其中 k 取 0,1,2,3… 正整数)。如果你纠结于什么 3 字节的对齐等。这里不适用!在计算机中,所有的数据都是二进制!所谓对齐,就是将一串二进制的最后 N 位抹成 0。具体几位呢?这就是根据自己要对齐的字节数来定了。1 字节对齐时 N == 0;2 字节对齐时 N == 1,4 字节对齐时 N == 2;以此类推。对齐字节数是根据硬件平台来的。

  再一点就是,对齐的抹 0,是需要向上取整的。为什么要向上取整呢?如果向下取整,那么,实际返回的大小就比用户实际需要的要小了。这可就麻烦了,甚至由于不满足自己需要的大小,申请的这块内存都没法使用!

关于任意字节对齐,可以参考一下文章:

  1. 实现任意字节对齐的内存分配和释放
  2. 任意字节对齐的内存分配和释放

内存池实现

  内存池的实现要比内存堆复杂的多,其内部大量使用了 C 语言编程技巧,甚至一些我们平时很少见的用法,例如,枚举里面有 #include xxx.h、结构体里面有 #include xxx.h 等等。通过在 #include xxx.h 前提供不同的宏定义就可以实现不同的内容。

  在看代码时,可以使用编译器将预处理之后的文件输出出来辅助来学习。根据 C 语言规则,.h 文件会被展开到 .c 中引用他们的地方。 针对这里的内存池,最终会被展开到 memp.c 对应的输出文件中。

memp_std.h

  该文件主要定义了 LwIP 内部使用的各种内存池,此外还通过 #include "lwippools.h" 的形式,将用户自定义的内存池(供内存堆使用的内存池)包含进来。该文件是 LwIP 内部使用的文件,外部应用程序不应该直接使用该文件。

  该文件比较有意思,其中的宏值定义全部依赖于宏 LWIP_MEMPOOL(name,num,size,desc),这样,只要外部提供的该宏值不同,则包含该文件后的源文件(通常为 memp.hmemp_priv.h)在预处理后,就会产生不一样的结果。这样,就可以通过在不同的地方多次包含该文件(前面必定提供宏值 MEMPOOL)以产生不同结果!

  该文件的第一部分定义了四个宏值,其中,LWIP_MALLOC_MEMPOOL 就是提供给外部用来自定义内存池的;而 LWIP_PBUF_MEMPOOL 则用来创建 PBUF_POOL 类型的 pbuf 内存(这部分内存同时包含了 pbuf 本身 + payload 内存空间)。

  该文件的第二部分就是使用 LWIP_MEMPOOL 这个宏来具体定义各种 LwIP 内部使用的内存池。其他文件(memp.hmemp_priv.h)包含该文件的地方,通过提供不同的 MEMPOOL,该部分将产生不同的结果。

  其中,注意一下 PBUFPBUF_POOL 这两个内存池,其中,PBUF 这个内存池定义了 PBUF_ROMPBUF_REF 这两种 PBUF 中 PBUF 本身的内存空间,而 PBUF_POOL这个内存池则定义了 PBUF_POOL 类型的 pbuf 内存(这部分内存同时包含了 pbuf 本身 + payload 内存空间)。

  再接下来需要注意一下 #include "lwippools.h",这里是将用户自己定义的内存池引入进来(供内存堆使用的内存池)。memp.h 章节详细介绍了是如何实现的。最后一部分就是将所有宏值进行反定义,这样就可以保证多次包含时,不会出现宏的重复定义!

memp_priv.h

  priv 即 private,该文件定义了内存池的基本数据结构,同样为 LwIP 内部使用的文件,外部应用程序不应该使用该文件。且该文件需要使用 memp.h 中定义枚举值。因此,这个文件的使用位置比较特殊,如下图:

  该文件的第一部分为 MEMP_OVERFLOW_CHECK,其主要用来进行内存池的溢出检查,这里不多做说明。接下来的第二大部分就是内存池各种结构的定义。LwIP 的内存池是通过链表来组织的,第一个结构 struct memp 就是链表的节点的结构了。

#if !MEMP_MEM_MALLOC || MEMP_OVERFLOW_CHECK
struct memp {struct memp *next;     /* 下一个链表 */
#if MEMP_OVERFLOW_CHECKconst char *file;            /* 发生溢出时调用函数的文件名,mem_malloc 调用者的文件 */int line;                   /* 发生溢出时调用函数的行号,mem_malloc 调用者的行数 */
#endif /* MEMP_OVERFLOW_CHECK */
};
#endif /* !MEMP_MEM_MALLOC || MEMP_OVERFLOW_CHECK */

  接下来是一个比较复杂的枚举变量的定义。其实,这里面就定义了两个枚举变量 MEMP_POOL_HELPER_STARTMEMP_POOL_HELPER_END,后面的一堆东西就是给这两个枚举变量赋初值的。这其中就需要 memp.h 中定义的枚举变量 memp_t 中的值。

#if MEM_USE_POOLS && MEMP_USE_CUSTOM_POOLS
/* Use a helper type to get the start and end of the user "memory pools" for mem_malloc */
typedef enum {/* Get the first (via:MEMP_POOL_HELPER_START = ((u8_t) 1*MEMP_POOL_A + 0*MEMP_POOL_B + 0*MEMP_POOL_C + 0)*/MEMP_POOL_HELPER_FIRST = ((u8_t)
#define LWIP_MEMPOOL(name,num,size,desc)
#define LWIP_MALLOC_MEMPOOL_START 1
#define LWIP_MALLOC_MEMPOOL(num, size) * MEMP_POOL_##size + 0
#define LWIP_MALLOC_MEMPOOL_END
#include "lwip/priv/memp_std.h") ,/* Get the last (via:MEMP_POOL_HELPER_END = ((u8_t) 0 + MEMP_POOL_A*0 + MEMP_POOL_B*0 + MEMP_POOL_C*1) */MEMP_POOL_HELPER_LAST = ((u8_t)
#define LWIP_MEMPOOL(name,num,size,desc)
#define LWIP_MALLOC_MEMPOOL_START
#define LWIP_MALLOC_MEMPOOL(num, size) 0 + MEMP_POOL_##size *
#define LWIP_MALLOC_MEMPOOL_END 1
#include "lwip/priv/memp_std.h")
} memp_pool_helper_t;

  前面曾经说过,LwIP 的内存堆可以使用内存池来实现。那么为了实现该部分,内存池这里必须要提供必要的结构、接口来供内存堆实现时调用。其中,这个枚举结构就是定义出了内存池枚举 memp_t 中的用户定义的供内存堆使用的内存池的枚举界限!将这个枚举各部分展开后,就是如下

#if MEM_USE_POOLS && MEMP_USE_CUSTOM_POOLS
/* Use a helper type to get the start and end of the user "memory pools" for mem_malloc */
typedef enum {/* Get the first (via:MEMP_POOL_HELPER_START = ((u8_t) 1*MEMP_POOL_A + 0*MEMP_POOL_B + 0*MEMP_POOL_C + 0)“MEMP_POOL_##size” 这个东西,就是之前定义的枚举memp_t中 ,用户自定义的内存池的枚举值,结果一步步替换后就成该名*/MEMP_POOL_HELPER_FIRST = ((u8_t)#define LWIP_MEMPOOL(name,num,size,desc)#define LWIP_MALLOC_MEMPOOL_START 1#define LWIP_MALLOC_MEMPOOL(num, size) * MEMP_POOL_##size + 0#define LWIP_MALLOC_MEMPOOL_END// #if MEMP_USE_CUSTOM_POOLS// #include "lwippools.h"// #endif /* MEMP_USE_CUSTOM_POOLS */// /* 这里将其展开之后就是如下 */LWIP_MALLOC_MEMPOOL_STARTLWIP_MALLOC_MEMPOOL(20, 256) -> MEMP_POOL_256LWIP_MALLOC_MEMPOOL(10, 512) -> MEMP_POOL_512LWIP_MALLOC_MEMPOOL(5, 1512) -> MEMP_POOL_1512LWIP_MALLOC_MEMPOOL_END#undef LWIP_MEMPOOL#undef LWIP_MALLOC_MEMPOOL#undef LWIP_MALLOC_MEMPOOL_START#undef LWIP_MALLOC_MEMPOOL_END#undef LWIP_PBUF_MEMPOOL) ,  /*最终 MEMP_POOL_HELPER_START = 1*MEMP_POOL_256+0*MEMP_POOL_512+0*MEMP_POOL_1512 = MEMP_POOL_256*//* Get the last (via:MEMP_POOL_HELPER_END = ((u8_t) 0 + MEMP_POOL_A*0 + MEMP_POOL_B*0 + MEMP_POOL_C*1) 结束值展开和上面一样不再说明 */MEMP_POOL_HELPER_LAST = ((u8_t)#define LWIP_MEMPOOL(name,num,size,desc)#define LWIP_MALLOC_MEMPOOL_START#define LWIP_MALLOC_MEMPOOL(num, size) 0 + MEMP_POOL_##size *#define LWIP_MALLOC_MEMPOOL_END 1#include "lwip/priv/memp_std.h")/* 最终 MEMP_POOL_HELPER_LAST = 0 + MEMP_POOL_256 * 0 + MEMP_POOL_512 * 0 + MEMP_POOL_1512 * 1 = MEMP_POOL_1512 */
} memp_pool_helper_t;

  最终,宏 MEMP_POOL_HELPER_FIRST = MEMP_POOL_256,而宏 MEMP_POOL_HELPER_LAST = MEMP_POOL_1512。这样就定义出了内存堆使用的内存堆范围。内存堆在使用内存池时,就是直接遍历这个范围内的内存,从中找一个合适的大小分配出用户需要的内存!

  再接下来就是内存池描述符的结构定义了,在 LwIP1.4.1 版本中,以下各部分都是独立的,最新版中将其统一成了一个结构体,每个成员的具体含义见注释。具体如下:

/** 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 */
};

  该文件的最后一部分就是几个对外接口的声明了,这些接口用来供其他模块初始化自己使用的内存池。需要注意,前面说了,memp_priv.h 是私有文件,外部不应使用,因此,这些接口最终是通过 memp.h 中的各中宏的形式对外提供(其实现均在 memp.c 文件中)。

memp.h

  该文件主要就是包含定义对外提供的内存池使用的宏,结构体以及接口。其中部分结构放在了 memp_priv.h 文件中,如果之前看过 LwIP 1.4.1 的源码就会知道,原来是没有 memp_priv.h 文件的,所有结构都在 memp.h 中,最新的源码进行结构调整。文件开始就是如下:

/* run once with empty definition to handle all custom includes in lwippools.h*/
#define LWIP_MEMPOOL(name,num,size,desc)    /* 定义为空 */
#include "lwip/priv/memp_std.h"               /* 其中的定义均是基于上面宏值得,大部分内容也就成了空 */

  由于这里先将 LWIP_MEMPOOL 定义为了空,也就导致了 #include "lwip/priv/memp_std.h" 中展开后的绝大多数内容都变成了空,最终其实就剩下如下这一部分:

#if MEMP_USE_CUSTOM_POOLS
#include "lwippools.h"
#endif

  不难看出,开头这两句唯一的作用就是,如果文件 lwippools.h 中引入了其他文件或结构时,这里就是将用户自定义的内存池(供内存堆使用的内存池)包含进来。否则以上就会全部为空,啥也不剩!

  接下来的 typedef enum{xxxxx}memp_t; 就是为每个定义的内存池定义一个枚举变量值,外部在使用内存池时,就是通过这个枚举中的各值来指定内存池的类型,从而从对应的内存池中分配内存。

typedef enum {#define LWIP_MEMPOOL(name,num,size,desc)  MEMP_##name,    /* ## 为连接符 且会忽略 ## 前后的空格, 例如:a ## b 其实就是 ab */
#include "lwip/priv/memp_std.h"MEMP_MAX
} memp_t;

  这里首先定义宏值 #define LWIP_MEMPOOL(name,num,size,desc) MEMP_##name, 然后包含 memp_std.h,这样 memp_std.h 中的内容就使用 MEMP_##name, 替换所有 LWIP_MEMPOOL(name,num,size,desc),简单将宏值展开后就是如下(并没有完全展开所有宏):

typedef enum {#if LWIP_RAWMEMP_RAW_PCB,
#endif /* LWIP_RAW */#if LWIP_UDPMEMP_UDP_PCB,
#endif /* LWIP_UDP */#if LWIP_TCPMEMP_TCP_PCB,MEMP_TCP_PCB_LISTEN,MEMP_TCP_SEG,
#endif /* LWIP_TCP */#if LWIP_IPV4 && IP_REASSEMBLYMEMP_REASSDATA,
#endif /* LWIP_IPV4 && IP_REASSEMBLY */
#if (IP_FRAG && !LWIP_NETIF_TX_SINGLE_PBUF) || (LWIP_IPV6 && LWIP_IPV6_FRAG)MEMP_FRAG_PBUF,
#endif /* IP_FRAG && !LWIP_NETIF_TX_SINGLE_PBUF || (LWIP_IPV6 && LWIP_IPV6_FRAG) */#if LWIP_NETCONN || LWIP_SOCKETMEMP_NETBUF,MEMP_NETCONN,
#endif /* LWIP_NETCONN || LWIP_SOCKET */#if NO_SYS==0MEMP_TCPIP_MSG_API,
#if LWIP_MPU_COMPATIBLEMEMP_API_MSG,
#if LWIP_DNSMEMP_DNS_API_MSG,
#endif
#if LWIP_SOCKET && !LWIP_TCPIP_CORE_LOCKINGMEMP_SOCKET_SETGETSOCKOPT_DATA,
#endif
#if LWIP_NETIF_APIMEMP_NETIFAPI_MSG,
#endif
#endif /* LWIP_MPU_COMPATIBLE */
#if !LWIP_TCPIP_CORE_LOCKING_INPUTMEMP_TCPIP_MSG_INPKT,
#endif /* !LWIP_TCPIP_CORE_LOCKING_INPUT */
#endif /* NO_SYS==0 */#if LWIP_IPV4 && LWIP_ARP && ARP_QUEUEINGMEMP_ARP_QUEUE,
#endif /* LWIP_IPV4 && LWIP_ARP && ARP_QUEUEING */#if LWIP_IGMPMEMP_IGMP_GROUP,
#endif /* LWIP_IGMP */#if LWIP_TIMERS && !LWIP_TIMERS_CUSTOMMEMP_SYS_TIMEOUT,
#endif /* LWIP_TIMERS && !LWIP_TIMERS_CUSTOM */#if LWIP_DNS && LWIP_SOCKETMEMP_NETDB,
#endif /* LWIP_DNS && LWIP_SOCKET */
#if LWIP_DNS && DNS_LOCAL_HOSTLIST && DNS_LOCAL_HOSTLIST_IS_DYNAMICMEMP_LOCALHOSTLIST,
#endif /* LWIP_DNS && DNS_LOCAL_HOSTLIST && DNS_LOCAL_HOSTLIST_IS_DYNAMIC */#if LWIP_IPV6 && LWIP_ND6_QUEUEINGMEMP_ND6_QUEUE,
#endif /* LWIP_IPV6 && LWIP_ND6_QUEUEING */#if LWIP_IPV6 && LWIP_IPV6_REASSMEMP_IP6_REASSDATA,
#endif /* LWIP_IPV6 && LWIP_IPV6_REASS */#if LWIP_IPV6 && LWIP_IPV6_MLDMEMP_MLD6_GROUP,
#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */MEMP_PBUF,MEMP_PBUF_POOL,/** Allow for user-defined pools; this must be explicitly set in lwipopts.h* since the default is to NOT look for lwippools.h*/// #if MEMP_USE_CUSTOM_POOLS// #include "lwippools.h"// /* 这里将其展开之后就是如下 */// LWIP_MALLOC_MEMPOOL_START// LWIP_MALLOC_MEMPOOL(20, 256) -> MEMP_POOL_256// LWIP_MALLOC_MEMPOOL(10, 512) -> MEMP_POOL_512// LWIP_MALLOC_MEMPOOL(5, 1512) -> MEMP_POOL_1512// LWIP_MALLOC_MEMPOOL_END// #endif /* MEMP_USE_CUSTOM_POOLS */MEMP_POOL_256,MEMP_POOL_512,MEMP_POOL_1512,MEMP_MAX
} memp_t;

  接下来就是 #include "lwip/priv/memp_priv.h" 了,其具体内容参见上面的 memp_priv.h 这个章节。注意,这个文件的引用必须放在这里,因为其内部会使用上面的枚举值(memp_t)。extern const struct memp_desc* const memp_pools[MEMP_MAX]; 这个将内存池导出供外部使用。

  该文件的最后部分是一些宏定义以及对外接口,其中,这些宏值对外供其他模块来定义及配置自己使用的内存池;各个对外接口则用来供外部使用内存池资源的。对外接口我们在 memp.c 章节详细说明,这里需要重点介绍一下 LWIP_MEMPOOL_DECLARE(name,num,size,desc) 这个宏。

  LWIP_MEMPOOL_DECLARE 这个宏就是用来定义内存池实际使用的内存的。当 MEMP_MEM_MALLOC 为 1 时,表示从内存堆分配内存池使用内存;当 MEMP_MEM_MALLOC 为 0 时,则通过独立定义一堆全局数组来提供内存池内存。

  • LWIP_MEMPOOL_DECLARE_STATS_INSTANCELWIP_MEMPOOL_DECLARE_STATS_REFERENCE 与 STATS 有关,不开启 STATS 时该宏值为空,他们的定义就位于 memp_priv.h 中。DECLARE_LWIP_MEMPOOL_DESC 也类似,用来定义一个文本描述,仅用于特殊目的。
  • const struct memp_desc memp_ ## name = {忽略}; 这个就是定义内存池描述符(每一种内存池对应一个内存池描述符),其中的 LWIP_MEM_ALIGN_SIZE 表示的是内存池中一个元素的对齐后大小。当 MEMP_MEM_MALLOC 为 0 时,其成员要多一些!
  • MEMP_MEM_MALLOC 为 0 时,LWIP_DECLARE_MEMORY_ALIGNED 这个宏就是定义内存池的内存空间(一个名为 memp_memory_ xxx_base[] 的全局数组)
  • static struct memp *memp_tab_ ## name; 则定义了全局变量,这个变量用来索引内存池中的第一个空闲位置,最终会被用在 memp_desc 内部

memp.c

  memp.c 主要就针对上面各种定义的具体实现了,整个文件代码并没多少行,看起来还是比较简单的。前面包含一堆头文件我们不用关心,实际内容部分开头是如下结构:

#define LWIP_MEMPOOL(name,num,size,desc) LWIP_MEMPOOL_DECLARE(name,num,size,desc)
#include "lwip/priv/memp_std.h"
/* 全局型指针数组,指向每类 POOL 的描述符 */
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_MEMPOOL 定义为 LWIP_MEMPOOL_DECLARE(name,num,size,desc),然后再包含 memp_std.h,这样在展开之后就定义出了各种内存池,同样,memp_pools 内部也是这个套路,他们最终被展开后就是下图这样:

  从上图不难看出,前者用来定义各个内存池 u8_t memp_memory_xxx_base[](使用内存堆时没有这部分,因为内存是在分配时使用 mem_malloc 从内存堆分配) 以及内存池描述符 struct memp_desc memp_xxx 等,后者 memp_pools 仅仅是将各个内存池对应的描述符汇总起来,方便后面使用。

  再接下来就是具体的实现函数了,有了上面的各种宏的介绍,理解各接口的实现就简单了,大部分接口都是通过遍历 memp_pools 这个数组来找到需要的内存池。下面就以源码注释的形式进行说明(注意对于其中的调试用的函数或者安全性函数不进行说明)。

memp_init 和 memp_init_pool

  memp_init 内部就是通过 for 循环遍历 memp_pools 中的每一种内存池,然后内部直接调用的 memp_init_pool 来将指定内存池中的多个元素串联为一个链表。代码很简单,这里直接来个图说明一下

memp_malloc 和 do_memp_malloc_pool

  memp_malloc 内部就是直接调用的 do_memp_malloc_pool,因此,重点介绍 do_memp_malloc_pool。 分配过程是如果memp_tab[] 数组中相应链表的指针为空,说明该类型的内存池已经没有了,分配失败;否则选择链表中的 desc->tab 指向的那个内存池元素,并在元素的最开始处预留出 MEMP_SIZE(该值可能是 0) 的空间,最后将有效地址返回给函数调用者。

memp_free 和 do_memp_free_pool

  memp_free 内部就是直接调用的 do_memp_free_pool,因此,重点介绍 do_memp_free_pool。 内存的释放过程非常简单,只需要将指定的内存重新串到链表即可。唯一需要注意一下就是回退预留的 MEMP_SIZE(该值可能是 0)。

参考

  1. https://savannah.nongnu.org/projects/lwip/

LwIP 之六 详解内存池(memp.c/h)动态内存管理策略相关推荐

  1. LwIP 之六 详解动态内存管理 内存池(memp.c/h)

      该文主要是接上一部分LwIP 之 详解动态内存管理 内存堆(mem.c/h),该部分许多内容需要用到上一篇的内容.该部分主要是详细介绍LwIP中的动态内存池.整个内存池的实现相较于内存堆来说,还是 ...

  2. LwIP 之五 详解动态内存管理 内存堆(mem.c/h)

    写在前面   目前网上有很多介绍LwIP内存的文章,但是绝大多数都不够详细,甚至很多介绍都是错误的!无论是代码的说明还是给出的图例,都欠佳!下面就从源代码,到图例详细进行说明.   目前,网络上多数文 ...

  3. JVM常量池最全详解-常量池/运行时常量池/字符串常量池/基本类型常量池,看这一篇就够了

    JVM常量池最全详解-常量池/运行时常量池/字符串常量池/基本类型常量池,看这一篇就够了! 常量池详解 1. 字面量和符号引用 1.1 字面量 1.2 符号引用 2. 常量池vs运行时常量池 3. 常 ...

  4. android内存池,两种常见的内存管理方法:堆和内存池

    描述 本文导读 在程序运行过程中,可能产生一些数据,例如,串口接收的数据,ADC采集的数据.若需将数据存储在内存中,以便进一步运算.处理,则应为其分配合适的内存空间,数据处理完毕后,再释放相应的内存空 ...

  5. Linux常用基本命令详解(二)-------磁盘分区和磁盘管理类命令

    Linux常用基本命令详解(一) Linux常用基本命令详解(二)-------磁盘分区和磁盘管理类命令 Linux常用基本命令详解(三) 1.磁盘分区 磁盘分区(系统分区)是使用分区编辑器(part ...

  6. cglib动态代理jar包_代理模式详解:静态代理+JDK/CGLIB 动态代理实战

    1. 代理模式 代理模式是一种比较好的理解的设计模式.简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标 ...

  7. 任意大小 内存池 c语言,C语言内存池使用模型-1 - Mr.南柯 - 51Testing软件测试网 51Testing软件测试网-软件测试人的精神家园...

    Mo2m4C;i$ZE0在用C语言开发时,特别是在服务器端,内存的使用会成为系统性能的一个瓶颈,如频繁的分配和释放内存,会不断的增加系统的内存碎片,影响内核之 后分配内存的效率,这个时候一个比较可行的 ...

  8. 内存池、自由空间、堆内存 等名称解释(不间断更新。。。)

    简单说说几个名词解释:(c++ primer 第五版) 1. 内存池.自由空间.堆内存 其实是同一种概念的不同叫法. 编译器分配的内存,一般在全局存储区,要么在栈内存等. 堆内存,是程序员自己分配的内 ...

  9. linux直接运行程序加载动态库失败,扣丁学堂Linux培训详解程序运行时加载动态库失败解决方法...

    今天扣丁学堂Linux培训老师给大家介绍一下关于Linux程序运行时加载动态库失败的解决方法,希望对同学们学习有所帮助,下面我们一起来看一下吧. Linux下不能加载动态库问题 当出现下边异常情况 . ...

最新文章

  1. CRUX下实现进程隐藏(3)
  2. c++构造函数以及类中变量初始化顺序
  3. k8s服务网关ambassador部署
  4. Fiori Elements detail table data request logic
  5. 150. 逆波兰表达式求值---JAVA---LeetCode
  6. 开发人员对Spring vs JavaEE的看法
  7. ubuntu linux 搭建ftp虚拟目录
  8. 性能优化篇 之 如何开展优化类的工作(2)
  9. oracle不同值,Oracle一张表中实现对一个字段不同值和总值的统计(多个count)
  10. cadence 怎么拼版_ALLEGRO 拼版教程
  11. Quartz 定时任务 cron 表达式详解
  12. 数据清理中,处理缺失值的方法
  13. leetcode454. 四数相加 II
  14. 支付宝H5拉起唤醒支付宝APP
  15. 二分图的判定最大匹配
  16. Keras自然语言处理(九)
  17. nginx-host绕过实例复现
  18. k8s之hello world
  19. ArcGIS超链接使用方法
  20. 经典扫雷,回忆童年(扫雷c++代码)

热门文章

  1. ctfshow web入门 反序列化 前篇 254-266
  2. c语言 网络授权 破解,[授权码]苹果Mac平台C程序的防盗版功能和License授权管理...
  3. 这次终于有人把企业升级、转型、转行分清楚了
  4. 软酷网·武汉大学软件文化节
  5. 视频播放器选择怎样的丢帧策略~~
  6. 推荐几个积极向上的公众号,期待与你相遇~
  7. 所见非所得: 浅析同形异义词攻击及案例分析
  8. 方案设计——智能SUP桨板充气泵方案
  9. 苹果股价盘初下挫逾0.6% 新款iPhone即将发布
  10. 中国CAE行业现状及发展前景分析