lwip源码分析 之 内存池管理

一,lwip内存池简介

**lwip内存池就是把一块大的连续内存区域,按照用户定义的pool的种类和数量,切分成固定大小的内存区域。当使用动态内存分配时,会为用户分配一个指定的pool,释放时也会将该pool整个的回收。**这种动态内存分配方法的优点是速度快,缺点是pool中的内存可能没被利用。故适合用来为固定大小的数据结构分配内存,提供空间利用。
在这里插入图片描述

二,内存池的数据结构

lwip的内存池结构体非常简单,是个单向的链表,每种类型的pool维持一条单向链表。

struct memp {
    
    
  struct memp *next;
};

lwip内存池所使用的内存其实就是一个全局数组,其定义如下:

//内存池的真正内存区域
static u8_t memp_memory[MEM_ALIGNMENT - 1 
#define LWIP_MEMPOOL(name,num,size,desc) + ( (num) * (MEMP_SIZE + MEMP_ALIGN_SIZE(size) ) )
#include "lwip/memp_std.h"
];

需要关注的是这个数组的大小是在编译的时候才会被确定。这个数组大小的计算公式是:

pool_a的大小 × pool_a的数量 + pool_b的大小 × pool_b的数量 + pool_b的大小 × pool_b的数量 +…+MEM_ALIGNMENT -1

lwip/memp_std.h中有各种类型的pool的定义如下:

#if LWIP_RAW
LWIP_MEMPOOL(RAW_PCB,        MEMP_NUM_RAW_PCB,         sizeof(struct raw_pcb),        "RAW_PCB")
#endif 

当编译器执行时,这些宏被展开成类似如下:

+MEMP_NUM_RAW_PCB * MEMP_ALIGN_SIZE(sizeof(struct raw_pcb))
这就对应了**“pool_a的大小 × pool_a的数量”**,所有宏展开后就成为上面的公式,也就是所需的内存池的总大小。

另外还有一些变量以类似的形式被编译器编译:

memp_sizes[MEMP_MAX]数组存放了各种类型pool的大小。MEMP_MAX=pool种类。

const u16_t memp_sizes[MEMP_MAX] = {
    
    
#define LWIP_MEMPOOL(name,num,size,desc)  LWIP_MEM_ALIGN_SIZE(size),
#include "lwip/memp_std.h"
};

memp_num[MEMP_MAX]数组存放了各种类型pool的数量。MEMP_MAX=pool种类。

static const u16_t memp_num[MEMP_MAX] = {
    
    
#define LWIP_MEMPOOL(name,num,size,desc)  (num),
#include "lwip/memp_std.h"
};
static struct memp *memp_tab[MEMP_MAX];	//保存了各种类型pool链的第一个空闲pool

三,内存管理函数

1,初始化函数

void
memp_init(void)
{
    
    
  struct memp *memp;
  u16_t i, j;

  //将所有内存池初始化为0
  for (i = 0; i < MEMP_MAX; ++i) {
    
    
    MEMP_STATS_AVAIL(used, i, 0);
    MEMP_STATS_AVAIL(max, i, 0);
    MEMP_STATS_AVAIL(err, i, 0);
    MEMP_STATS_AVAIL(avail, i, memp_num[i]);
  }

  memp = (struct memp *)LWIP_MEM_ALIGN(memp_memory);  //获取内存池首地址
  //将内存池分配成不同类型的pool
  for (i = 0; i < MEMP_MAX; ++i) {
    
    
    memp_tab[i] = NULL;
    
    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]);
    }
  }
}

初始完成后的内存池如图,不同类型的pool其实是在一片连续的内存memp_memory 上
在这里插入图片描述

2,内存分配函数

void *
memp_malloc(memp_t type)
{
    
    
  struct memp *memp;
  
  SYS_ARCH_DECL_PROTECT(old_level);//临界保护
  SYS_ARCH_PROTECT(old_level);

  memp = memp_tab[type];  //获取指定类型的pool链
  
  if (memp != NULL) {
    
    
    memp_tab[type] = memp->next;  //memp_tab指向下一个pool

    MEMP_STATS_INC_USED(used, type);  //pool使用记录
    memp = (struct memp*)(void *)((u8_t*)memp + MEMP_SIZE); //调节memp地址,这里的MEMP_SIZE为0,所以不变
  } else {
    
    
    MEMP_STATS_INC(err, type);
  }
  SYS_ARCH_UNPROTECT(old_level);//退出保护

  return memp;
}

使用这个函数申请pool时,从指定类型pool链中取出一个空闲的pool并返回地址。

3,释放内存

void
memp_free(memp_t type, void *mem)
{
    
    
  struct memp *memp;
  SYS_ARCH_DECL_PROTECT(old_level);	
	//判断释放的内存是否空指针
  if (mem == NULL) {
    
    
    return;
  }

  memp = (struct memp *)(void *)((u8_t*)mem - MEMP_SIZE);	//MEMP_SIZE为0

  SYS_ARCH_PROTECT(old_level);//临界保护

  MEMP_STATS_DEC(used, type); //used置位0
  
  memp->next = memp_tab[type];//将pool插入链表 
  memp_tab[type] = memp;

  SYS_ARCH_UNPROTECT(old_level);//退出临界保护
}

释放内存时只需要将pool重归链表。
内存池的分配方式总体上就比较简单,在lwip中使用内存池的常常是固定大小的结构体,如udp控制块,tcp控制块等。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44821644/article/details/114103768
今日推荐