Memory heap of lwip source code analysis

1. Introduction to the memory heap

Lwip uses the memory heap for dynamic memory allocation, which makes up for the shortcomings of the memory pool.

The flexibility of the memory heap is manifested in that the size of each memory block can be changed at will. Every time a user applies for memory, the first memory block with a length not less than the required size will be found from the memory heap, and then the memory After the block is cut properly, the remaining memory is formed into a new memory block and returned to the memory heap.

When reclaiming a memory block, if the memory blocks before and after it are also free, multiple memory blocks can be combined into one large memory block. Prevent memory fragmentation.

The memory heap at the beginning is a large memory block. After continuous application and release, the following situation may appear. Therefore, it is necessary to follow "application, release, application, release" when using it.

Second, the memory heap data structure

The memory block structure is a two-way linked list, which is also continuous in memory. Among them, next and prev represent the offset relative to the first address.

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

Insert picture description here

In addition, there are several global variables:

ram_heap is the real memory of the memory heap, and MEM_SIZE_ALIGNED is the total size of the memory heap defined by the user. SIZEOF_STRUCT_MEM refers to the size of the mem structure, and MEM_ALIGNMENT is for byte alignment. Why is SIZEOF_STRUCT_MEM multiplied by 2? Because there are two memory blocks in the initial memory heap, respectively at the beginning and the end, but the memory block at the end is marked as used. This is convenient for the allocation and recovery of the code.

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;//指向最低地址的可用内存块

Third, the memory heap implementation function

1. Initialization of the memory heap

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);
  }
}

The initialization process is to create two memory blocks for the memory heap, and initialize the global variables ram, ram_end, lfree, and the semaphore (the semaphore is used because the memory heap is a global array)
Insert picture description here

2. Memory application

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;
}

The main logic of this function is to find a suitable memory block from lfree. If the memory block is large enough, a new mem2 is split from the memory block, and the mem is inserted into the linked list, and finally the required memory address is returned. This can improve memory utilization.

3. Memory release

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();
}

This function is very simple, mainly plug_holes(mem); realizes the merging of free memory.

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);
  }
}

Insert picture description here

Guess you like

Origin blog.csdn.net/weixin_44821644/article/details/114106606