lwip源码分析 之 内存池管理

一,lwip内存池简介

c语言的动态内存分配,会造成内存碎片,lwip使用内存池这样的结构来解决该问题。因为网络数据传输需要经常动态分配内存。

lwip的内存池按照用户给出的结构体,把一段连续的内存切分成一个个大小相同的内存区域(大小为结构体大小)。当使用动态内存分配时,会为用户分配一个内存块,释放时也会将整个内存块的回收。

这种动态内存分配方法的优点是速度快,缺点是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链的第一个空闲块

三,内存管理函数

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_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链中取出一个空闲的内存块,并返回地址。

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];//将内存块插入链表 
  memp_tab[type] = memp;

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

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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值