malloc
和 free
来申请和释放内存,但是这种分配方式可能会产生很多堆碎片,最后造成没有内存可以分配,而且运行地越久寻找新内存的时间可能也越长,这里不建议使用这种方式。下面通过代码来详细地看一下内存堆和内存池的实现
来看看LwIP中实现的堆内存管理的实现:
先来看看mem_init
函数,相关结构体和宏定义写在前面:
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
};
#define MEM_ALIGNMENT 4
#define LWIP_MEM_ALIGN_SIZE(size) (((size) + MEM_ALIGNMENT - 1U) & ~(MEM_ALIGNMENT-1U))
#define SIZEOF_STRUCT_MEM LWIP_MEM_ALIGN_SIZE(sizeof(struct mem))
/* 用户自定义的堆内存大小 */
#define MEM_SIZE (32 * 1024)
#define MEM_SIZE_ALIGNED LWIP_MEM_ALIGN_SIZE(MEM_SIZE)
#define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) u8_t variable_name[LWIP_MEM_ALIGN_BUFFER(size)]
LWIP_DECLARE_MEMORY_ALIGNED(ram_heap, MEM_SIZE_ALIGNED + (2U * SIZEOF_STRUCT_MEM));
#define LWIP_RAM_HEAP_POINTER ram_heap
static struct mem * ptr_to_mem(mem_size_t ptr)
{
return (struct mem *)(void *)&ram[ptr];
}
static struct mem * LWIP_MEM_LFREE_VOLATILE lfree;
static u8_t *ram;
---------------------------------------------------------------
void mem_init(void)
{
struct mem *mem;
/* 检查struct mem是否是四字节对齐 */
LWIP_ASSERT("Sanity check alignment",
(SIZEOF_STRUCT_MEM & (MEM_ALIGNMENT - 1)) == 0);
/* 将ram变量的指针指向上面声明的ram_heap数组,其大小为(MEM_SIZE+2*mem结构体大小) */
ram = (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER);
/* 上面ram_heap数组的大小多了两个mem结构体,分别填充在ram_heap的起始和结尾 */
/* 初始化堆的起始 */
mem = (struct mem *)(void *)ram;
/* 下一个内存的偏移量为MEM_SIZE_ALIGNED,即结束 */
mem->next = MEM_SIZE_ALIGNED;
/* 前一个内存的偏移量 */
mem->prev = 0;
/* 当前内存没有被使用 */
mem->used = 0;
/* 初始化堆的结束:指向上一个内存的结束位置 */
ram_end = ptr_to_mem(MEM_SIZE_ALIGNED);
/* 由于当前结构体为标记堆内存的结束,无实际大小,标记使用 */
ram_end->used = 1;
/* 标记下一个内存的偏移量:下一个还是自己表示到头了 */
ram_end->next = MEM_SIZE_ALIGNED;
/* 标记前一个内存的偏移量 */
ram_end->prev = MEM_SIZE_ALIGNED;
/* 合理性检验:一般在每次mem_free()后调用,确保堆链表结构体内内存的合法性和是否对齐 */
MEM_SANITY();
/* 将堆内存中第一块可用内存的指针赋值给lfree */
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);
}
}
初始化后ram_heap
数组的内存分配如下所示:
首先来看看内存分配的函数,它的原理是First Fit
,即遍历空闲内存,找到第一个满足大小的堆内存进行分配,并将剩下的返回内存堆。
对于mem_malloc
有三种方式,第一种为C库的malloc
,一种为使用内存池(memory pool
)方式,还有一种就是内存堆方式,这里对内存堆分配方式进行介绍(具体实现见代码注释):
该函数中有一个宏定义:LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT
,这是允许用户可以在中断或不允许等待信号量的上下文中释放pbuf
,它的原理就是使用信号量和SYS_ARCH_PROTECT
保护mem_malloc
函数,使用SYS_ARCH_PROTECT
保护mem_free
函数。只有mem_malloc
在SYS_ARCH_UNPROTECTs
后,mem_free
才可以允许。
mem_malloc
的速度会非常慢,一般都不会打开,所以这里不对相关宏定义的代码进行解析。函数中对于overflow检查的相关代码也将省略
void* mem_malloc(mem_size_t size_in)
{
mem_size_t ptr, ptr2, size;
struct mem *mem, *mem2;
/* 声明一个unsigned long的变量lev用于同步 */
LWIP_MEM_ALLOC_DECL_PROTECT();
/* 分配的内存大小为0则直接返回 */
if (size_in == 0)
{
return NULL;
}
/* 保证分配的内存是字节对齐的 */
size = (mem_size_t)LWIP_MEM_ALIGN_SIZE(size_in);
/* 分配的内存的大小必须比MIN_SIZE_ALIGNED(12)大:防止分配过多小内存造成内存碎片 */
if (size < MIN_SIZE_ALIGNED)
{
/* every data block must be at least MIN_SIZE_ALIGNED long */
size = MIN_SIZE_ALIGNED;
}
/* 如果size大于总的堆内存的大小或者小于待分配内存的大小,则返回NULL */
if ((size > MEM_SIZE_ALIGNED) || (size < size_in))
{
return NULL;
}
/* 获得在mem_init中创建的互斥量,以并发地使用内存管理函数 */
sys_mutex_lock(&mem_mutex);
/* 在有OS的情况下:如果当前函数在中断中运行,则lev=basepri并设置优先级高于lev的中断才能打断;
否则进入临界区 */
LWIP_MEM_ALLOC_PROTECT();
/* 从lfree指针开始用first fit方式寻找第一个足够大的内存 */
for (ptr = mem_to_ptr(lfree); ptr < MEM_SIZE_ALIGNED - size;
ptr = ptr_to_mem(ptr)->next)
{
mem = ptr_to_mem(ptr);
/* 如果该内存没有used且大小满足(size+内存头mem结构体)的大小 */
if ((!mem->used) && (mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size)
{
/* 如果该内存除去我们申请的内存后,剩余的大小还比(struct mem + 最小申请内存MIN_SIZE_ALIGNED)大
则将剩下的内存返回内存堆中 */
if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >= (size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED))
{
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)
{
ptr_to_mem(mem2->next)->prev = ptr2;
}
}
else
{
/* 剩下的内存不足以分配一个新的内存堆,只好浪费,标记整块内存used */
mem->used = 1;
}
/* 如果分配的内存为第一块,则需要更新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;
LWIP_ASSERT("mem_malloc: !lfree->used", ((lfree == ram_end) || (!lfree->used)));
}
/* 对应前面的protect,若在中断中则使能中断并清空basepri寄存器;
若不在中断中,则退出临界区 */
LWIP_MEM_ALLOC_UNPROTECT();
/* 释放互斥锁 */
sys_mutex_unlock(&mem_mutex);
/* 返回申请的内存的地址,这里不打开完整性检查,即MEM_SANITY_OFFSET为0 */
return (u8_t *)mem + SIZEOF_STRUCT_MEM + MEM_SANITY_OFFSET;
}
}
/* 找不到足够大的内存,释放锁 */
LWIP_MEM_ALLOC_UNPROTECT();
sys_mutex_unlock(&mem_mutex);
return NULL;
}
mem_free
函数也很好理解,具体见注释:
void mem_free(void *rmem)
{
struct mem *mem;
/* 和mem_malloc一样,声明一个lev变量 */
LWIP_MEM_FREE_DECL_PROTECT();
if (rmem == NULL) {
return;
}
/* 由于分配的内存都是四字节对齐的,若不对齐则传入的参数错误 */
if ((((mem_ptr_t)rmem) & (MEM_ALIGNMENT - 1)) != 0) {
LWIP_MEM_ILLEGAL_FREE("mem_free: sanity check alignment");
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) {
return;
}
/* 痛mem_malloc中的LWIP_MEM_ALLOC_PROTECT() */
LWIP_MEM_FREE_PROTECT();
/* 如果mem->used为0,说明参数出错,直接返回 */
if (!mem->used) {
return;
}
/* 该函数检查mem->prev和mem->next的参数合法性 */
if (!mem_link_valid(mem)) {
LWIP_MEM_FREE_UNPROTECT();
return;
}
/* 标记该mem为not used */
mem->used = 0;
/* 如果释放的mem为第一块内存,则更新lfree */
if (mem < lfree) {
/* the newly freed struct is now the lowest */
lfree = mem;
}
/* 最后,如果释放的内存的prev和next有not used的话,则合并 */
plug_holes(mem);
LWIP_MEM_FREE_UNPROTECT();
}
在LwIP的数据段中保留了一个固定大小的静态内存,这段内存被分为多个内存池(pools
),用来描述各种不同的数据结构,比如TCP、UDP、IP等报文的首部,它们的长度是固定的,这样每次分配和释放都不会产生内存碎片。比如有一个内存池用于TCP控制结构体struct tcp_pcb
,另一个内存池用于UDP控制结构体struct udp_pcb
。每个内存池都可以配置它可以容量的最大数量的数据结构,比如上面的TCP和UDP控制块的个数可以通过MEMP_NUM_TCP_PCB
和MEMP_NUM_UDP_PCB
进行修改。
下面来对内存池的代码进行分析,代码中去掉了统计分析和overflow检查的相关代码。
首先是内存池的初始化函数:
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]);
}
}
可以看到内存池的初始化函数非常简单,就是对每一个定义的内存池进行初始化。现在来看看memp_pools
是什么:
const struct memp_desc *const memp_pools[MEMP_MAX] = {
#define LWIP_MEMPOOL(name,num,size,desc) &memp_ ## name,
#include "lwip/priv/memp_std.h"
};
其中MEMP_MAX
的定义如下:
typedef enum {
#define LWIP_MEMPOOL(name,num,size,desc) MEMP_##name,
#include "lwip/priv/memp_std.h"
MEMP_MAX
} memp_t;
可以看出来,在memp_std.h
中定义了多个内存池,每个内存池的参数包括:名字(name
)、个数(num
)、大小(size
)和描述(desc
),下面来看一下这个文件:
#define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) u8_t variable_name[LWIP_MEM_ALIGN_BUFFER(size)]
#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 \
};
#define LWIP_MEMPOOL(name,num,size,desc) LWIP_MEMPOOL_DECLARE(name,num,size,desc)
------------
#if LWIP_RAW
LWIP_MEMPOOL(RAW_PCB, MEMP_NUM_RAW_PCB, sizeof(struct raw_pcb), "RAW_PCB")
#endif /* LWIP_RAW */
#if LWIP_UDP
LWIP_MEMPOOL(UDP_PCB, MEMP_NUM_UDP_PCB, sizeof(struct udp_pcb), "UDP_PCB")
#endif /* LWIP_UDP */
#if LWIP_TCP
LWIP_MEMPOOL(TCP_PCB, MEMP_NUM_TCP_PCB, sizeof(struct tcp_pcb), "TCP_PCB")
LWIP_MEMPOOL(TCP_PCB_LISTEN, MEMP_NUM_TCP_PCB_LISTEN, sizeof(struct tcp_pcb_listen), "TCP_PCB_LISTEN")
LWIP_MEMPOOL(TCP_SEG, MEMP_NUM_TCP_SEG, sizeof(struct tcp_seg), "TCP_SEG")
#endif /* LWIP_TCP */
#if LWIP_ALTCP && LWIP_TCP
LWIP_MEMPOOL(ALTCP_PCB, MEMP_NUM_ALTCP_PCB, sizeof(struct altcp_pcb), "ALTCP_PCB")
#endif /* LWIP_ALTCP && LWIP_TCP */
#if LWIP_IPV4 && IP_REASSEMBLY
LWIP_MEMPOOL(REASSDATA, MEMP_NUM_REASSDATA, sizeof(struct ip_reassdata), "REASSDATA")
#endif /* LWIP_IPV4 && IP_REASSEMBLY */
#if (IP_FRAG && !LWIP_NETIF_TX_SINGLE_PBUF) || (LWIP_IPV6 && LWIP_IPV6_FRAG)
LWIP_MEMPOOL(FRAG_PBUF, MEMP_NUM_FRAG_PBUF, sizeof(struct pbuf_custom_ref),"FRAG_PBUF")
#endif /* IP_FRAG && !LWIP_NETIF_TX_SINGLE_PBUF || (LWIP_IPV6 && LWIP_IPV6_FRAG) */
#if LWIP_NETCONN || LWIP_SOCKET
LWIP_MEMPOOL(NETBUF, MEMP_NUM_NETBUF, sizeof(struct netbuf), "NETBUF")
LWIP_MEMPOOL(NETCONN, MEMP_NUM_NETCONN, sizeof(struct netconn), "NETCONN")
#endif /* LWIP_NETCONN || LWIP_SOCKET */
可以看到一个宏定义LWIP_MEMPOOL
就根据参数声明了一个内存堆数组,还有memp
和memp_desc
结构体。
现在回过头来看MEMP_MAX
,前面的#define LWIP_MEMPOOL(name,num,size,desc) MEMP_##name
就是暂时用一下文件中这个定义,将LWIP_MEMPOOL
的宏定义再进行一层替换,作为enum的值的名称,所以MEMP_MAX
就是内存堆的总个数。
而memp_pools
就是将在memp_std.h
中用LWIP_MEMPOOL
声明的各个内存池的memp_desc
结构体的地址包含进来。
最后再回来看memp_init_pool
函数:
struct memp {
struct memp *next;
};
struct memp_desc {
/** Element size */
u16_t size;
/** 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;
};
--------
void memp_init_pool(const struct memp_desc *desc)
{
int i;
struct memp *memp;
*desc->tab = NULL;
memp = (struct memp *)LWIP_MEM_ALIGN(desc->base);
/* 初始化为0:如果MEMP_MEM_INIT宏定义为1的话 */
memset(memp, 0, (size_t)desc->num * (MEMP_SIZE + desc->size));
/* create a linked list of memp elements */
for (i = 0; i < desc->num; ++i) {
memp->next = *desc->tab;
*desc->tab = memp;
/* cast through void* to get rid of alignment warnings */
memp = (struct memp *)(void *)((u8_t *)memp + MEMP_SIZE + desc->size);
}
}
其中base
就是通过LWIP_MEMPOOL_DECLARE
声明的第一项大小为num*(MEMP_SIZE+MEMP_ALIGN_SIZE(size))
的数组。除了每个内存池的大小是固定的,它的原理和内存堆类似,内存池的大小包括内存池的实际存数据大小和内存池结构体struct memp
的大小。
tab
就是通过LWIP_MEMPOOL_DECLARE
声明的struct *memp
指针变量的地址,它表示内存池中第一个没有使用的pool。
所以这个函数就是将每个声明的内存池通过链表连接起来,其中desc->tab
为每个内存池的基地址。
最后,根据memp_init_pool
函数,memp_pools
中的每个memp_desc
初始化后的示意图如下:
来看一下内存池分配函数:
void *memp_malloc(memp_t type)
{
void*memp;
LWIP_ERROR("memp_malloc: type < MEMP_MAX", (type < MEMP_MAX), return NULL;);
memp = do_memp_malloc_pool_fn(memp_pools[type], file, line);
return memp;
}
参数type
的定义如下:
typedef enum {
#define LWIP_MEMPOOL(name,num,size,desc) MEMP_##name,
#include "lwip/priv/memp_std.h"
MEMP_MAX
} memp_t;
故memp_t
就是根据LWIP_MEMPOOL
定义的各个内存池的name
而组成的枚举类型,我们就通过这个枚举类型来申请相应的内存池中的内存。
然后来看看do_memp_malloc_pool_fn
函数:
static void* do_memp_malloc_pool(const struct memp_desc *desc)
{
struct memp *memp;
/* 与内存堆中的作用一样:为了执行关中断/进临界区等操作,这里不再解释 */
SYS_ARCH_DECL_PROTECT(old_level);
SYS_ARCH_PROTECT(old_level);
/* 获取对于memp的tab */
memp = *desc->tab;
if (memp != NULL) {
/* 指向下一个memp结构体 */
*desc->tab = memp->next;
SYS_ARCH_UNPROTECT(old_level);
/* 将内存的首地址返回 */
return ((u8_t *)memp + MEMP_SIZE);
} else {
SYS_ARCH_UNPROTECT(old_level);
}
return NULL;
}
void memp_free(memp_t type, void *mem)
{
if (mem == NULL) {
return;
}
do_memp_free_pool(memp_pools[type], mem);
}
同样地来看看do_memp_free_pool
函数:
static void do_memp_free_pool(const struct memp_desc *desc, void *mem)
{
struct memp *memp;
SYS_ARCH_DECL_PROTECT(old_level);
/* 获得该内存前面的结构体的地址 */
memp = (struct memp *)(void *)((u8_t *)mem - MEMP_SIZE);
SYS_ARCH_PROTECT(old_level);
/* 将该内存地址加入到desc->tab的最前面,表示空闲 */
memp->next = *desc->tab;
*desc->tab = memp;
SYS_ARCH_UNPROTECT(old_level);
}
(1)MEM_LIBC_MALLOC
:使用C库中的malloc
和free
管理内存
(2)MEMP_MEM_MALLOC
:使用内存堆方式来分配内存池的内存。上面内存池的分配中我把相关代码注释掉了,如果此宏打开,在分配和释放内存时会直接使用mem_malloc
和mem_free
。
(3)MEM_USE_POOLS
:使用内存池的方式来分配内存堆的内存。同上。
若此宏打开,还需将宏MEMP_MEM_MALLOC
关闭,并将MEMP_USE_CUSTOM_POOLS
宏打开,表示创建一个lwippools.h
文件,然后用LWIP_MALLOC_MEMPOOL
来声明自己的内存。
下面给出一个lwippools.h
的配置例子:
/* OPTIONAL: Pools to replace heap allocation
* Optional: Pools can be used instead of the heap for mem_malloc. If
* so, these should be defined here, in increasing order according to
* the pool element size.
*
* LWIP_MALLOC_MEMPOOL(number_elements, element_size)
*/
#if MEM_USE_POOLS
LWIP_MALLOC_MEMPOOL_START
LWIP_MALLOC_MEMPOOL(100, 256)
LWIP_MALLOC_MEMPOOL(50, 512)
LWIP_MALLOC_MEMPOOL(20, 1024)
LWIP_MALLOC_MEMPOOL(20, 1536)
LWIP_MALLOC_MEMPOOL_END
#endif /* MEM_USE_POOLS */
/* Optional: Your custom pools can go here if you would like to use
* lwIP's memory pools for anything else.
*/
LWIP_MEMPOOL(SYS_MBOX, 22, 100, "SYS_MBOX")