内存分配策略

lwip 内存分配两种,一种是分配固定大小的内存块;另一种是利用内存堆进行动态分配,属于可变长度的内存块。内存分配的本质就是事先准备一大块内存堆(可以理解为一个巨大的数组),然后将该空间起始地址返回给申请者, 这就需要内核必须采用自己独有的一套数据结构来描述、记录哪些内存空间已经分配,哪些内存空间是未使用的,根据使用的机制不同,延伸出多种类型的内存分配策略。

固定大小的内存块

使用固定大小的内存块分配策略,用户只能申请大小固定的内存块,在内存初始化的时候,系统会将所有可用的内存区域划分为 N 块固定大小的内存,然后将这些内存块通过单链表的方式连接起来。
在lwip 中有很多固定的数据结构空间,如 TCP 首部、 UDP 首部, IP 首部,以太网首部等都是固定的数据结构,其大小就是一个固定的值,那么我们就能采用这种方式分配这些固定大小的内存空间,这样子的效率就会大大提高,并且无论怎么申请与释放,都不会产生内存碎片,这就让系统能很稳定地运行。这种分配策略在 LwIP 中被称之为动态内存池分配策略,示意图如下:

可变长度分配

使用此种分配方案,系统运行的时候,各个空闲内存块的大小是不固定的,它会随着用户的申请而改变,刚开始的时候,系统就是一块大的内存堆,随着系统的运行,用户会申请与释放内存块,所以系统的内存块的大小、数量都会随之改变。
LwIP 中也会使用这种内存分配策略,它采用 First Fit(首次拟合)内存管理算法, 申请内存时只要找到一个比所请求的内存大的空闲块,就从中切割出合适的块,并把剩余的部分返回到动态内存堆中, 这种分配策略分配的内存块大小有限制,要求请求的分配大小不能小于 MIN_SIZE,否则请求会被分配到 MIN_SIZE 大小的内存空间, 一般 MIN_SIZE大小为 12 字节,在这 12 个字节中前几个字节会存放内存分配器管理用的私有数据,该数据区域不能被用户程序修改,否则导致致命问题。
内存释放的过程是相反的过程,但分配器会查看该节点前后相邻的内存块是否空闲,如果空闲则合并成一个大的内存空闲块。 当然,采用这种内存堆的分配方式,在申请和释放的时候肯定需要消耗时间,可以类似地看做是以时间换空间的策略。 采用这种分配策略,其优点就是内存浪费小,比较简单,适合
用于小内存的管理,其缺点就是如果频繁的动态分配和释放,可能会造成严重的内存碎片,如果在碎片情况严重的话,可能会导致内存分配不成功从而导致系统崩溃。

内存池的预处理

在内核初始化时,会事先在内存中初始化相应的内存池, 内核会将所有可用的区域根据宏定义的配置以固定的大小为单位进行划分,然后用一个简单的链表将所有空闲块连接起来,这样子就组成一个个的内存池。由于链表中所有节点的大小相同,所以分配时不需要查找,直接取出第一个节点中的空间分配给用户即可。
内核在初始化内存池的时候,是根据用户配置的宏定义进行初始化的,比如,用户定义了 LWIP_UDP 这个宏定义, 在编译的时候, 编译器就会将与 UDP 协议控制块相关的数据构编译编译进去,这样子就将 LWIP_MEMPOOL(UDP_PCB,MEMP_NUM_UDP_PCB, sizeof(struct udp_pcb),“UDP_PCB”)包含进去,在初始化的时候,UDP 协议控制块需要的 POOL 资源就会被初始化,其数量由 MEMP_NUM_UDP_PCB 宏定义决定, 注意了,不同协议的 POOL 内存块的大小是不一样的,这由协议的性质决定,如UDP 协议控制块的内存块大小是 sizeof(struct udp_pcb), 而 TCP 协议控制块的 POOL 大小则为 sizeof(struct tcp_pcb)。通过这种方式,就可以将一个个用户配置的宏定义功能需要的
POOL 包含进去,就使得编程变得更加简便。
注意宏定义 MEMP_NUM_UDP_PCB ,这个是你UDP控制块的数量,有多少个UDP连接最好在留有裕量的基础上定义多少。默认在 opt.h 文件中

/*** MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One* per active UDP "connection".* (requires the LWIP_UDP option)*/
#ifndef MEMP_NUM_UDP_PCB
#define MEMP_NUM_UDP_PCB                28
#endif/*** MEMP_NUM_TCP_PCB: the number of simulatenously active TCP connections.* (requires the LWIP_TCP option)*/
#ifndef MEMP_NUM_TCP_PCB
#define MEMP_NUM_TCP_PCB                5
#endif/*** MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP connections.* (requires the LWIP_TCP option)*/
#ifndef MEMP_NUM_TCP_PCB_LISTEN
#define MEMP_NUM_TCP_PCB_LISTEN         8
#endif

此处需提到一个文件,在 include/lwip/priv目录下, 它里面全是宏定义, 在不同的地方调用#include "lwip/priv/memp_std.h"就能产生不同的效果。该文件中的宏值定义全部依赖于宏 LWIP_MEMPOOL(name,num,size,desc),这样,只要外部提供的该宏值不同,则包含该文件的源文件在编译器的预处理后,就会产生不一样的结果。
以UDP为例:
memp_std.h 文件中

#if LWIP_UDP
LWIP_MEMPOOL(UDP_PCB,        MEMP_NUM_UDP_PCB,         sizeof(struct udp_pcb),        "UDP_PCB")
#endif /* LWIP_UDP */

之后如在 memp.h 文件中

/* 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/memp_std.h"MEMP_MAX
} memp_t;

这一段代码就等效为:

/* 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/memp_std.h"//MEMP_UDP_PCB,MEMP_MAX
} memp_t;

经过编译器处理之后:

typedef enum
{MEMP_RAW_PCB,MEMP_UDP_PCB,MEMP_TCP_PCB,MEMP_TCP_PCB_LISTEN,MEMP_TCP_SEG,MEMP_ALTCP_PCB,MEMP_REASSDATA,MEMP_NETBUF,MEMP_NETCONN,MEMP_MAX} memp_t;

memp_t 类型在整个内存池的管理中是最重要的存在,通过内存池申请函数申请内存的时候,唯一的参数就是 memp_t 类型的, 它将告诉分配的函数在哪种类型的 POOL 中去分配对应的内存块,这样子就直接管理了系统中所有类型的 POOL。
单独的每一块内存池的产生如下:

/** This creates each memory pool. These are named memp_memory_XXX_base (where* XXX is the name of the pool defined in memp_std.h).* To relocate a pool, declare it as extern in cc.h. Example for GCC:*   extern u8_t __attribute__((section(".onchip_mem"))) memp_memory_UDP_PCB_base[];*/
#define LWIP_MEMPOOL(name,num,size,desc) u8_t memp_memory_ ## name ## _base \[((num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size)))];
#include "lwip/memp_std.h"

等效产生了:

memp_memory_UDP_PCB_base[((num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size)))];  //每一个控制块的内存池

接下来看一下 memp_init()

//初始化内存池,其实就是将每一个不同的控制块的内存空间挂载到 memp_tab[]
void
memp_init(void)
{struct memp* memp;u16_t i, j;#if !MEMP_SEPARATE_POOLSmemp = (struct memp*)LWIP_MEM_ALIGN(memp_memory);
#endif /* !MEMP_SEPARATE_POOLS *//* for every pool: */for(i = 0; i < MEMP_MAX; ++i){memp_tab[i] = NULL;
#if MEMP_SEPARATE_POOLSmemp = (struct memp*)memp_bases[i];  //获取每个控制块的基地址,即:memp_memory_ ## name ## _base
#endif /* MEMP_SEPARATE_POOLS *//* create a linked list of memp elements *//* 将每个控制块通过链表连接,挂在 memp_tab[] 上,其中索引为:MEMP_UDP_PCB。。。*/for(j = 0; j < memp_num[i]; ++j){memp->next = memp_tab[i];memp_tab[i] = memp;memp = (struct memp*)(void*)((u8_t*)memp + MEMP_SIZE + memp_sizes[i]
#if MEMP_OVERFLOW_CHECK+ MEMP_SANITY_REGION_AFTER_ALIGNED
#endif);}}
#if MEMP_OVERFLOW_CHECKmemp_overflow_init();/* check everything a first time to see if it worked */memp_overflow_check_all();
#endif /* MEMP_OVERFLOW_CHECK */
}

memp_tab[] 定义如下:

static struct memp* memp_tab[MEMP_MAX];
memp_malloc(memp_t type)
{struct memp* memp;SYS_ARCH_DECL_PROTECT(old_level);LWIP_ERROR("memp_malloc: type < MEMP_MAX", (type < MEMP_MAX), return NULL;);SYS_ARCH_PROTECT(old_level);
#if MEMP_OVERFLOW_CHECK >= 2memp_overflow_check_all();
#endif /* MEMP_OVERFLOW_CHECK >= 2 */memp = memp_tab[type];if(memp != NULL){memp_tab[type] = memp->next;
#if MEMP_OVERFLOW_CHECKmemp->next = NULL;memp->file = file;memp->line = line;
#endif /* MEMP_OVERFLOW_CHECK */MEMP_STATS_INC_USED(used, type);LWIP_ASSERT("memp_malloc: memp properly aligned",((mem_ptr_t)memp % MEM_ALIGNMENT) == 0);memp = (struct memp*)(void*)((u8_t*)memp + MEMP_SIZE); //找到一块合适的控制块}else{LWIP_DEBUGF(MEMP_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("memp_malloc: out of memory in pool %s\n", memp_desc[type]));MEMP_STATS_INC(err, type);}SYS_ARCH_UNPROTECT(old_level);return memp;
}
void
memp_free(memp_t type, void* mem)
{struct memp* memp;SYS_ARCH_DECL_PROTECT(old_level);if(mem == NULL){return;}LWIP_ASSERT("memp_free: mem properly aligned",((mem_ptr_t)mem % MEM_ALIGNMENT) == 0);memp = (struct memp*)(void*)((u8_t*)mem - MEMP_SIZE);SYS_ARCH_PROTECT(old_level);
#if MEMP_OVERFLOW_CHECK
#if MEMP_OVERFLOW_CHECK >= 2memp_overflow_check_all();
#elsememp_overflow_check_element_overflow(memp, type);memp_overflow_check_element_underflow(memp, type);
#endif /* MEMP_OVERFLOW_CHECK >= 2 */
#endif /* MEMP_OVERFLOW_CHECK */MEMP_STATS_DEC(used, type);memp->next = memp_tab[type];memp_tab[type] = memp; //将内存控制块挂载回memp_tab[]#if MEMP_SANITY_CHECKLWIP_ASSERT("memp sanity", memp_sanity());
#endif /* MEMP_SANITY_CHECK */SYS_ARCH_UNPROTECT(old_level);
}

以上为LwIP1.4.1源码。在2.0.3版本,则又有不同。

V2.03版本内存池处理

如整个内存池的生成只有:

#define LWIP_MEMPOOL(name,num,size,desc) LWIP_MEMPOOL_DECLARE(name,num,size,desc)
#include "lwip/priv/memp_std.h"
#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 \};

通过以上代码,则将整个所需的控制块内存池生成
而内存池的挂载管理也不是挂载在 memp_tab[] 上,而是改为 :memp_pools[MEMP_MAX]

const struct memp_desc *const memp_pools[MEMP_MAX] = {#define LWIP_MEMPOOL(name,num,size,desc) &memp_ ## name,
#include "lwip/priv/memp_std.h"
};
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]);
}
void
memp_init_pool(const struct memp_desc *desc)
{#if MEMP_MEM_MALLOCLWIP_UNUSED_ARG(desc);
#elseint 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_CHECKmemp_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_STATSdesc->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_pools[]中的内容则变为:

内存分配如下:删除不重要代码

void *
memp_malloc(memp_t type)
{void *memp;memp = do_memp_malloc_pool(memp_pools[type]);return memp;
}
static void *
do_memp_malloc_pool(const struct memp_desc *desc)
{struct memp *memp;memp = *desc->tab;if (memp != NULL) {*desc->tab = memp->next;memp->next = NULL;return ((u8_t *)memp + MEMP_SIZE);} else {}return NULL;
}

内存池申请函数的核心代码就一句,那就是 memp = desc->tab;, 通过这句代码, 能直接得到对应内存块中的第一个空闲内存块, 并将其取出,并且移动desc->tab 指针, 指向下一个空闲内存块, 然后将((u8_t *)memp + MEMP_SIZE)返回, MEMP_SIZE 偏移的空间大小, 因为内存块需要一些空间存储内存块相关的信息, 该宏定义的值是(LWIP_MEM_ALIGN_SIZE(sizeof(struct memp)) +
MEM_SANITY_REGION_BEFORE_ALIGNED),我们暂时无需理会它,只要知道申请内存块后返回的地址是直接可用的地址即可,而偏移的 MEMP_SIZE 这部分内容是内存分配器管理的空间,用户是不允许触碰的地方,否则就很可能发生错误。
同样的, 内存释放函数也非常简单的, 只需要把使用完毕的内存添加到对应内存池中的空闲内存块链表即可, 只不过释放内存有两个参数, 一个是 POOL 的类型,还有就是内存块的起始地址。

memp_free(memp_t type, void *mem)
{if (mem == NULL) {return;}do_memp_free_pool(memp_pools[type], mem);
}
static void
do_memp_free_pool(const struct memp_desc *desc, void *mem)
{struct memp *memp;/* cast through void* to get rid of alignment warnings */memp = (struct memp *)(void *)((u8_t *)mem - MEMP_SIZE); //根据内存块的地址偏移得到内存块的起始地址,因为前面也说了,内存块中有一部分内容是内存分配器操作的,所以需要进行偏移。memp->next = *desc->tab;//内存块的下一个就是链表中的第一个空闲内存块*desc->tab = memp; //将内存块插入到对应内存池的*desc->tab 中
}

内存池的分配方式核心就三个函数:
1、memp_init()
2、memp_malloc(memp_t type)
3、memp_free(memp_t type, void *mem)
type可取

typedef enum
{MEMP_RAW_PCB,MEMP_UDP_PCB,MEMP_TCP_PCB,MEMP_TCP_PCB_LISTEN,MEMP_TCP_SEG,MEMP_ALTCP_PCB,MEMP_REASSDATA,MEMP_NETBUF,MEMP_NETCONN,MEMP_MAX} memp_t;

动态内存堆

动态内存堆管理(heap)又可以分为两种: 一种是 C 标准库自带的内存管理策略,另一种是 LwIP 自身实现的内存堆管理策略。这两者的选择需要通过宏值MEM_LIBC_MALLOC 来选择,且二者只能选择其一。MEM_LIBC_MALLOC为1表示使用C库malloc、free,在单片机中使用0;
内存池可由内存堆实现,反之,内存堆也可以由内存池实现。通过 MEM_USE_POOLS 和 MEMP_MEM_MALLOC这两个宏定义来选择,且二者只能选择其一。
一般均为 0 ,表示内存池和内存堆分别实现。
在 mem.c 文件中

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; //标记是否使用#if MEM_OVERFLOW_CHECK/** this keeps track of the user allocation size for guard checks */mem_size_t user_size;#endif
};

申请的内存最小为 12 字节,因为一个内存块最起码需要保持 mem结构体的信息,以便于对内存块进行操作,而该结构体在对齐后的内存大小就是 12 字节。

#ifndef MIN_SIZE
#define MIN_SIZE             12
#endif /* MIN_SIZE */

真正的内存堆数组在如下定义

#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)
#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)); //定义一个ram_heap[]数组,大小为 MEM_SIZE
#define LWIP_RAM_HEAP_POINTER ram_heap //定义一个别名
#endif /* LWIP_RAM_HEAP_POINTER */

此段代码可以简单理解为 定义 ram_heap[MEM_SIZE] 大小的数组,之后对此数组进行动态分配
MEM_SIZE 在 lwipopts.h 文件夹中定义。

#define MEM_SIZE                (15*1024)

之后在 mem.c 文件中,定义

/** pointer to the heap (ram_heap): for alignment, ram is now a pointer instead of an array */
static u8_t *ram;  //ram 是一个全局指针变量,指向内存堆对齐后的起始地址,因为真正的内存堆起始地址不一定是按照 CPU 的对齐方式对齐的,而此处就要确保内存堆的起始地址是对齐的。
/** the last entry, always unused! */
static struct mem *ram_end; //mem 类型指针,指向内存堆中最后一个内存块
#if !NO_SYS
static sys_mutex_t mem_mutex; //内存访问的互斥量,用于内存互斥访问
#endif
static struct mem * LWIP_MEM_LFREE_VOLATILE lfree; //指向内存堆中低地址的空闲内存块,简单来说就是空闲内存块链表指针

内存堆的初始化

在内核初始化的时候,会调用 mem_init()函数进行内存堆的初始化,内存堆初始化主要的过程就是对上述所属的内存堆组织结构进行初始化,主要设置内存堆的起始地址,以及初始化空闲列表。

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); //取内存对齐之后的地址/* initialize the start of the heap *///在内存堆起始位置放置一个 mem 类型的结构体, 因为初始化后的内存堆就是一个大的空闲内存块,//每个空闲内存块的前面都需要放置一个 mem 结构体。mem = (struct mem *)(void *)ram;mem->next = MEM_SIZE_ALIGNED; //下一个内存块的偏移量为 MEM_SIZE_ALIGNED, 这相对于直接到内存堆的结束地址了mem->prev = 0; //上一个内存块为空mem->used = 0; //标记未使用/* initialize the end of the heap */ram_end = ptr_to_mem(MEM_SIZE_ALIGNED); //指针移动到内存堆末尾的位置,并且在那里放置一个 mem 类型的结构体,并初始化表示内存堆结束的内存块ram_end->used = 1; //标记使用ram_end->next = MEM_SIZE_ALIGNED;ram_end->prev = MEM_SIZE_ALIGNED;MEM_SANITY();/* 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) {LWIP_ASSERT("failed to create mem_mutex", 0); }
}

经过 mem_init()函数后,内存堆会被初始化为两个内存块,第一个内存块的大小就是整个内存堆的大小,而第二个内存块就是结束内存块,其大小为 0,并且被标记为已使用状态,无法进行分配。
值得注意的是,系统在运行的时候,随着内存的分配与释放, lfree指针的指向地址不断改变,都指向内存堆中低地址空闲内存块,而 ram_end 则不会改变,它指向系统中最后一个内存块,也就是内存堆的结束地址。

内存分配函数根据用户指定申请大小的内存空间进行分配内存, 其大小要大于MIN_SIZE,LwIP 中使用内存分配算法是首次拟合方法, 其分配原理就是在空闲内存块链表中遍历寻找, 直到找到第一个合适用户需求大小的内存块进行分配, 如果该内存块能进行分割, 则将用户需要大小的内存块分割出来, 剩下的空闲内存块则重新插入空闲内存块链表中。
mem_malloc()函数是 LwIP 中内存分配函数,其参数是用户指定大小的内存字节数,如果申请成功则返回内存块的地址,如果内存没有分配成功,则返回 NULL,分配的内存空间会受到内存对其的影响,可能会比申请的内存略大,比如用户需要申请 22 个字节的内存,而 CPU 是按照 4 字节内存对齐的,那么分配的时候就会申请 24 个字节的内存块。
内存块在申请成功后返回的是内存块的起始地址,但是该内存并未进行初始化,可能包含任意的随机数据,用户可以立即对其进行初始化或者写入有效数据以防止数据错误。此外内存堆是一个全局变量,在操作系统的环境中进行申请内存块是不安全的,所以 LwIP使用互斥量实现了对临界资源的保护,在多个线程同时申请或者释放的时候,会因为互斥量的保护而产生延迟。

void *
mem_malloc(mem_size_t size_in)
{mem_size_t ptr, ptr2, size;struct mem *mem, *mem2;LWIP_MEM_ALLOC_DECL_PROTECT();if (size_in == 0) {return NULL;}/* Expand the size of the allocated memory region so that we canadjust for alignment. */size = (mem_size_t)LWIP_MEM_ALIGN_SIZE(size_in); //进行内存对齐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) || (size < size_in)) {return NULL;}/* protect the heap from concurrent access */sys_mutex_lock(&mem_mutex);LWIP_MEM_ALLOC_PROTECT();//遍历空闲内存块链表for (ptr = mem_to_ptr(lfree); ptr < MEM_SIZE_ALIGNED - size;ptr = ptr_to_mem(ptr)->next) {mem = ptr_to_mem(ptr); //得到这个内存块的起始地址//如果这个内存块未使用,且大小不小于用户需要的大小加上mem结构体的大小,那么久满足需求if ((!mem->used) &&(mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size) {/* 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 *///如果这个内存块的大小,除了满足用户的需求之外还要大于 MIN_SIZE_ALIGNED的话,说明可以切割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 = (mem_size_t)(ptr + SIZEOF_STRUCT_MEM + size); //获取剩余能切割的内存块的起始索引LWIP_ASSERT("invalid next ptr",ptr2 != MEM_SIZE_ALIGNED);/* create mem2 struct */mem2 = ptr_to_mem(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) {//如果 mem2 内存块的下一个内存块不是链表中最后一个内存块(结束地址),那就将它下一个的内存块的 prve 指向 mem2。ptr_to_mem(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_to_ptr(mem));}
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
mem_malloc_adjust_lfree:
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT *///如果被分配出去的内存块是 lfree 指向的内存块,那么就需要重新给 lfree 赋值。if (mem == lfree) {struct mem *cur = lfree; //找到第一个低地址的空闲内存块。/* Find next free block after mem and update lowest free pointer */while (cur->used && cur != ram_end) {cur = ptr_to_mem(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);#if MEM_OVERFLOW_CHECKmem_overflow_init_element(mem, size_in);
#endifMEM_SANITY();return (u8_t *)mem + SIZEOF_STRUCT_MEM + MEM_SANITY_OFFSET; //返回内存块可用的起始地址,因为内存块的块头需要使用 mem结构体保存内存块的基本信息}}MEM_STATS_INC(err);LWIP_MEM_ALLOC_UNPROTECT();sys_mutex_unlock(&mem_mutex);LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mem_malloc: could not allocate %"S16_F" bytes\n", (s16_t)size));return NULL; //如果没法分配成功,就释放互斥量并且退出。
}

内存释放

内存释放的操作也是比较简单的, LwIP 是这样子做的: 它根据用户释放的内存块地址,通过偏移 mem 结构体大小得到正确的内存块起始地址, 并且根据 mem 中保存的内存块信息进行释放、 合并等操作, 并将 used 字段清零, 表示该内存块未被使用。
LwIP 为了防止内存碎片的出现,通过算法将内存相邻的两个空闲内存块进行合并,在释放内存块的时候,如果内存块与上一个或者下一个空闲内存块在地址上是连续的,那么就将这两个内存块进行合并

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;}/* Get the corresponding struct mem: *//* cast through void* to get rid of alignment warnings */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;}
#if MEM_OVERFLOW_CHECKmem_overflow_check_element(mem);
#endif/* 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 指向的内存块地址低,则更新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();
#if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXTmem_free_count = 1;
#endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */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); //找到mem的下一个内存块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; //调整mem->nextif (nmem->next != MEM_SIZE_ALIGNED) {ptr_to_mem(nmem->next)->prev = mem_to_ptr(mem); //mem内存块的下下个内存块的->prev指向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; //调整pmem->next,此时pmem内存块包括了memif (mem->next != MEM_SIZE_ALIGNED) {ptr_to_mem(mem->next)->prev = mem_to_ptr(pmem); //mem的下一个内存块的->prev指向pmem}}
}

Lwip中的配置

LwIP 中,内存的选择是通过以下这几个宏值来决定的,根据用户对宏值的定义值来判断使用那种内存管理策略,具体如下:
 MEM_LIBC_MALLOC: 该宏定义是否使用 C 标准库自带的内存分配策略。该值默认情况下为 0,表示不使用 C 标准库自带的内存分配策略。即默认使用 LwIP提供的内存堆分配策略。如果要使用 C 标准库自带的分配策略,则需要把该值定义为 1。
当该宏定义为 0 表示使用 LwIP 自己实现的动态内存管理策略。 LwIP 的动态内存管理策略又分为两种实现形式:一种通过内存堆(HEAP)管理策略来实现内存管理(大数组),另一种是通过内存池(POOL)管理策略来实现内存管理(事先开辟好的内存池)。
 MEMP_MEM_MALLOC: 该宏定义表示是否使用 LwIP 内存堆分配策略实现内存池分配(即:要从内存池中获取内存时,实际是从内存堆中分配)。默认情况下为 0,表示不从内存堆中分配,内存池为独立一块内存实现。与MEM_USE_POOLS 只能选择其一。
 MEM_USE_POOLS: 该宏定义表示是否使用 LwIP 内存池分配策略实现内存堆的分配(即:要从内存堆中获取内存时,实际是从内存池中分配)。默认情况下为0,表示不使用从内存池中分配,内存堆为独立一块内存实现。与MEMP_MEM_MALLOC 只能选择其一。

此处需要注意一点的是, 内存池的大小要依次增大, 在编译阶段, 编译器就会将这些内存个数及大小添加到系统的内存池之中,用户在申请内存的时候,根据其需要的大小在这些内存池中选择最合适的大小的内存块进行分配,如果具有最匹配的内存池中的内存块已经用完,则选择更大的内存池进行分配,只不过这样子会浪费更多的内存,当然,内存池的分配效率也是最高的,也相对于是我们常说的以空间换时间。

《lwip学习3》-- 内存管理相关推荐

  1. 深入学习python内存管理

    深入Python的内存管理 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 语言的内存管理是语言设计的一个重要方面.它是决定语言性 ...

  2. python内存管理可以使用del_Python深入学习之内存管理

    语言的内存管理是语言设计的一个重要方面.它是决定语言性能的重要因素.无论是C语言的手工管理,还是Java的垃圾回收,都成为语言最重要的特征.这里以Python语言为例子,说明一门动态类型的.面向对象的 ...

  3. 11.FreeRTOS学习笔记-内存管理

    几种内存分配算法的比较 heap_1.c 管理方案是 FreeRTOS 提供所有内存管理方案中最简单的一个,它只能申请内存而不能进行内存释放,并且申请内存的时间是一个常量 heap_2.c方案支持释放 ...

  4. Box2d源码学习二内存管理之SOA的实现

    本系列博客是由扭曲45原创,欢迎转载,转载时注明出处,http://blog.csdn.net/cg0206/article/details/8258166 SOA,全称small object al ...

  5. Cocos2d-x学习笔记—内存管理机制

    Cocos2d-x 3.x内存管理机制 1:C++内存管理 1-1:内存分配区域 创建对象需要两个步骤:第一步,为对象分配内存:第二步,调用构造函数初始化内存.在第一步中,可以选择几个不同的分配区域. ...

  6. Nginx学习之内存管理

    Nginx对内存管理有自己的一套机制,具体Nginx源码中在ngx_palloc.c中.主要是分为是对大块内存和小内存分配.大体结构图如下(该图为网络上下载的): 小内存是从pool内存池中分配的:大 ...

  7. C语言学习笔记 —— 内存管理

    一.内存模型 对于一个C语言程序而言,内存空间主要由五个部分组成 代码段(text).数据段(data).未初始化数据段(bss),堆(heap) 和 栈(stack) 组成,其中代码段,数据段和BS ...

  8. 深度学习中的内存管理问题研究综述

    点击上方蓝字关注我们 深度学习中的内存管理问题研究综述 马玮良1,2, 彭轩1,2, 熊倩1,2, 石宣化1,2, 金海1,2 1 华中科技大学计算机科学与技术学院,湖北 武汉 430074 2 华中 ...

  9. 操作系统内存管理、Cache调度策略学习

    原文  http://www.cnblogs.com/LittleHann/p/4012086.html 主题 操作系统 HTML 目录 0. 引言 1. 内存管理的概念 2. 内存覆盖与内存交换 3 ...

最新文章

  1. web在线阅读日志文件,response.getOutputStream().write中文乱码原因
  2. c#数组赋初值_JavaScript数组的声明、访问和遍历方法
  3. 一个想法--开发与业务,我们互相依赖
  4. Scrum sprint plan中规模估算的常见方式
  5. Google云服务降价,整合持续集成工具,支持Windows和托管虚拟机
  6. Perl 简单读写XML 文件
  7. 第二单元总结——多线程设计
  8. error LNK2019: unresolved external symbol _main referenced in function ___tmainCRTStartup
  9. 仅109美元 搞一套Evive物联网开发工具包回家耍
  10. java点名程序界面设计_用Java语言编写一个班级点名的程序
  11. nginx简单的rewrite配置
  12. 奥维互动地图恢复旧版及导入谷歌卫星图
  13. OpenCms后台工作间汉化设置10.5
  14. ps制作alpha通道图片—背景透明图片制作
  15. java孢子进化_孢子的进化起源
  16. 用MySQL后电脑频繁蓝屏_电脑容易蓝屏怎么办_电脑突然开始频繁蓝屏修复方法-win7之家...
  17. Redis 实例:开发一个Spring Redis应用程序
  18. ubuntu16.04 运行SVO
  19. 分分钟解决OSPF配置问题
  20. ICEM-圆柱与长方体相切

热门文章

  1. 视频教程-Python全栈9期(第十部分):CRM系统-Python
  2. 中文摩斯密码 - JavaScript库
  3. 一口气学会发布自己的地图查询系统
  4. 开展等级保护的目的是什么?
  5. 【详细】TeamViewer安装使用教程
  6. C语言几个重要的概念
  7. Java基础--Collection方法
  8. ★数学上最大的数是多少?
  9. 模板方法模式-用模板方法排序
  10. Camunda 工作流引擎 demo