lwip源码分析 之 内存堆

一,内存堆简介

lwip使用内存堆进行动态内存分配,弥补了内存池的缺点。

内存堆的灵活性表现在,每个内存块的大小是可以随意变化的,每次用户申请内存时,都会从内存堆中寻找第一个长度不小于所需大小的内存块,然后将该内存块裁剪合适后,将剩余的内存组成新的内存块返回内存堆中。

回收内存块时,若其前后的内存块也处于空闲,则可将多个内存块合并成一个大内存块。防止内存的碎片化。

一开始的内存堆就是一个大的内存块,经过不断的申请和释放后,可能呈现出如下的局面,所以在使用时要遵循“申请,释放,申请,释放”。

二,内存堆数据结构

内存块结构体就是一个双向的链表,这个链表在内存上也是连续的。其中next和prev表示的是相对首地址的偏移量。

struct mem {
    
    
  mem_size_t next;	//下一个内存块
  mem_size_t prev;	//上一个内存块
  u8_t used;	//是否被使用
};

在这里插入图片描述

此外还有几个全局变量:

ram_heap是内存堆的真实内存,MEM_SIZE_ALIGNED由用户定义内存堆总大小。SIZEOF_STRUCT_MEM指mem结构体的大小,MEM_ALIGNMENT是为了字节对齐。为什么SIZEOF_STRUCT_MEM要乘以2?因为在初始的内存堆中有两个内存块,分别在首尾,但尾部的内存块标志为used,这样做是方便了分配,回收代码的编写

u8_t ram_heap[MEM_SIZE_ALIGNED + (2*SIZEOF_STRUCT_MEM) + MEM_ALIGNMENT];

static u8_t *ram;//指向内存堆对齐后的首地址

static struct mem *ram_end;//指向最后一个内存块

static struct mem *lfree;//指向最低地址的可用内存块

三,内存堆实现函数

1,内存堆初始化

void
mem_init(void)
{
    
    
  struct mem *mem;

  ram = (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER);  //内存堆对齐
  //初始化内存堆的头部,初始内存堆只有一个空闲的内存块
  mem = (struct mem *)(void *)ram;
  mem->next = MEM_SIZE_ALIGNED; //MEM_SIZE_ALIGNED是内存堆对齐后的总大小,这里表示该内存的next是内存堆末尾
  mem->prev = 0;//prev指向自己
  mem->used = 0;//未使用

  //初始化内存堆末尾
  ram_end = (struct mem *)(void *)&ram[MEM_SIZE_ALIGNED]; //末尾地址
  ram_end->used = 1;  //末尾不可使用
  ram_end->next = MEM_SIZE_ALIGNED;//next和prev指向自己
  ram_end->prev = MEM_SIZE_ALIGNED;

  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,ram_end,lfree,以及信号量(使用信号量是因为内存堆是全局数组)
在这里插入图片描述

2,内存申请

void *
mem_malloc(mem_size_t size)
{
    
    
  mem_size_t ptr, ptr2;//相对ram的偏移量
  struct mem *mem, *mem2;//新的内存块

  LWIP_MEM_ALLOC_DECL_PROTECT();

  if (size == 0) {
    
    
    return NULL;
  }

  //申请的内存大小也要字节对齐
  size = LWIP_MEM_ALIGN_SIZE(size);
  //size必须满足最小条件
  if(size < MIN_SIZE_ALIGNED) {
    
    
    size = MIN_SIZE_ALIGNED;
  }
  //size不能太大
  if (size > MEM_SIZE_ALIGNED) {
    
    
    return NULL;
  }

  sys_mutex_lock(&mem_mutex); //请求信号量
  LWIP_MEM_ALLOC_PROTECT();

    //从lfree开始遍历men链表,直到找到第一个不小于size大小的内存块
    for (ptr = (mem_size_t)((u8_t *)lfree - ram); ptr < MEM_SIZE_ALIGNED - size;
         ptr = ((struct mem *)(void *)&ram[ptr])->next) {
    
    
      mem = (struct mem *)(void *)&ram[ptr];  //获取当前内存块

      //(mem->next - (ptr + SIZEOF_STRUCT_MEM))表示内存块可用空间。若可用空间>=size,说明这个内存块适合;
      if ((!mem->used) &&
          (mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size) {
    
    
        //MIN_SIZE_ALIGNED一个内存块的最小空间;
        //若mem的可用空间减去size后,其空间还能作为一个内存块存在,则裁剪出新的内存块,并插入链表
        if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >= (size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED)) {
    
    
          //计算新的内存块偏移
          ptr2 = ptr + SIZEOF_STRUCT_MEM + size;
          //初始化新内存块,插入mem后
          mem2 = (struct mem *)(void *)&ram[ptr2];
          mem2->used = 0;
          mem2->next = mem->next;
          mem2->prev = ptr;
          //更新mem
          mem->next = ptr2;
          mem->used = 1;//标记已使用

          //若mem2的next不是最后一个内存块
          if (mem2->next != MEM_SIZE_ALIGNED) {
    
    
            //则将mem2的next的prev指向mem2,因为最后一个内存块的prev必须指向自己
            ((struct mem *)(void *)&ram[mem2->next])->prev = ptr2;  
          }
          MEM_STATS_INC_USED(used, (size + SIZEOF_STRUCT_MEM));
        } else {
    
      //直接把mem返回
          mem->used = 1;
          MEM_STATS_INC_USED(used, mem->next - (mem_size_t)((u8_t *)mem - ram));
        }
        //若mem是lfree,则需要更新lfree
        if (mem == lfree) {
    
    
          //遍历mem之后的内存块更新lfree
          while (lfree->used && lfree != ram_end) {
    
    
            LWIP_MEM_ALLOC_UNPROTECT();
            /* prevent high interrupt latency... */
            LWIP_MEM_ALLOC_PROTECT();
            lfree = (struct mem *)(void *)&ram[lfree->next];
          }
          LWIP_ASSERT("mem_malloc: !lfree->used", ((lfree == ram_end) || (!lfree->used)));
        }
        LWIP_MEM_ALLOC_UNPROTECT();
        sys_mutex_unlock(&mem_mutex);//释放信号量
        return (u8_t *)mem + SIZEOF_STRUCT_MEM; //返回内存块可用地址
      }
    }
  MEM_STATS_INC(err); //分配内存失败
  LWIP_MEM_ALLOC_UNPROTECT();
  sys_mutex_unlock(&mem_mutex);
  return NULL;
}

这个函数的主要逻辑就是从lfree开始寻找合适的内存块,若内存块足够大,则从内存块中分出一个新的mem2,并将该mem插入链表,最后返回所需要的内存地址。这样能提高内存利用率。

3,内存释放

void
mem_free(void *rmem)
{
    
    
  struct mem *mem;
  LWIP_MEM_FREE_DECL_PROTECT();
  //检查指针
  if (rmem == NULL) {
    
    
    return;
  }

  //检查指针超出内存堆范围
  if ((u8_t *)rmem < (u8_t *)ram || (u8_t *)rmem >= (u8_t *)ram_end) {
    
    
    SYS_ARCH_DECL_PROTECT(lev);
    SYS_ARCH_PROTECT(lev);
    MEM_STATS_INC(illegal);
    SYS_ARCH_UNPROTECT(lev);
    return;
  }
  LWIP_MEM_FREE_PROTECT();
  mem = (struct mem *)(void *)((u8_t *)rmem - SIZEOF_STRUCT_MEM); //获取内存块对应起始地址
  mem->used = 0;//消除标记

  //是否需要更新lfree
  if (mem < lfree) {
    
    
    /* the newly freed struct is now the lowest */
    lfree = mem;
  }

  MEM_STATS_DEC_USED(used, mem->next - (mem_size_t)(((u8_t *)mem - ram)));
  
  plug_holes(mem);  //合并前后内存块
  LWIP_MEM_FREE_UNPROTECT();
}

这个函数非常简单,主要是plug_holes(mem);实现了空闲内存的合并。

static void
plug_holes(struct mem *mem)
{
    
    
  struct mem *nmem; //next内存块
  struct mem *pmem; //prev内存块

  nmem = (struct mem *)(void *)&ram[mem->next]; //获取下一个内存块
  //若 mem和nmem都不是最后一个内存块且nmen未使用
  if (mem != nmem && nmem->used == 0 && (u8_t *)nmem != (u8_t *)ram_end) {
    
    
    if (lfree == nmem) {
    
    
      lfree = mem;
    }
    //将mem和nmem合并
    mem->next = nmem->next;
    ((struct mem *)(void *)&ram[nmem->next])->prev = (mem_size_t)((u8_t *)mem - ram);
  }

  pmem = (struct mem *)(void *)&ram[mem->prev];//获取上一个内存块
  //pmem未使用,则合并mem和pmem
  if (pmem != mem && pmem->used == 0) {
    
    
    if (lfree == mem) {
    
    
      lfree = pmem;
    }
    pmem->next = mem->next;
    ((struct mem *)(void *)&ram[mem->next])->prev = (mem_size_t)((u8_t *)pmem - ram);
  }
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44821644/article/details/114106606