当前位置 : 主页 > 网络编程 > 其它编程 >

LWIP之Mem原理分析

来源:互联网 收集:自由互联 发布时间:2023-07-02
前言前一章我们讨论了内存池的分配和回收的一些内幕这一节我们将来讨论一下lwip的mem内存堆机制那有的人就很好奇既然 前言 前一章我们讨论了内存池的分配和回收的一些内幕这一节
前言前一章我们讨论了内存池的分配和回收的一些内幕这一节我们将来讨论一下lwip的mem内存堆机制那有的人就很好奇既然

前言 前一章我们讨论了内存池的分配和回收的一些内幕这一节我们将来讨论一下lwip的mem内存堆机制那有的人就很好奇既然有了内存池的管理机制了为什么还要多此一举搞个内存堆管理呢?二者有什么区别又或者各有什么优缺点呢? 这些疑惑将在这一节揭晓。

1、memp相关宏以及变量的解释 【1】宏定义解释

1、MEM_USE_POOLS         //使用内存池分配内存堆   2、MEM_LIBC_MALLOC       //使用标准c函数库分配   3、MIN_SIZE                //最小内存池大小   4、LWIP_RAM_HEAP_POINTER    //定义的内存池的头部 6、MEM_USE_POOLS_TRY_BIGGER_POOL  //如果当前内存池枯竭将尝试其他大的内存池  

【2】数据结构

struct memp_malloc_helper   {      memp_t poolnr;   }; //当使用MEM_USE_POOLS时mem内存用于区分哪一类型内存池     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;   };  //当不使用MEM_USE_POOLS是内存管理结构

以上就是内存堆管理最重要的两个基本结构单元

【3】变量

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;  //始终指向内存堆中最低空闲内存块的地址

有人就要问了就上面几个变量吗?你可不要耍我啊对你没有看错在你眼中如此冗杂的内存管理仅仅使用上图这么几个简答的变量由此可以看出其编码的巧妙。这也告诉我们看起来冗杂的东西不要害怕maybe只是虚胖哈哈哈。。。

2、Mem的内存机制原理

这里为了让大家更加直观的认识我觉得从上而下开始讲解。其实MEM提供几种分配机制。

1、使用使用LWIP自己的内存分配机制

2、使用系统的库(malloc和free)来分配

而决定上面的分配方式是通过MEM_LIBC_MALLOC来实现的当为1时采用c中的malloc和free来实现的。这里我们着重分析LWIP自己的内存分配机制。好了现在LWIP是通过自己的内存分配机制来实现内存堆了那么就结束了吗??? 不不不怎么可能LWIP在此基础上又分了两种构造机制。

  • 使用MEMP机制来分配内存堆
  • 使用自定义的物理内存来分配内存堆
  • 由此才将所谓的内存申请动作真正映射到物理内存故一般我将第二个宏的作用定义为决定内存的来源。

    本着有简到难的观点出发我们首先来看一下USE POOLS。

     

    上图为使用内存池来分配内存堆的图示由图我们可以看出内存申请后lwip在申请的内存前加了一个poolnr的结构这个正是我们前面提到的struct memp_malloc_helper  结构它专门用来管理使用内存池分配内存堆的。

    有的人可能就要问了为什么要引入这样的结构呢?按照我们以往的经验来说不应该是分配好内存到释放的时候直接使用这个地址来释放就好了吗? 是的你说的没错但是你忽略了一点那就是不同的内存池的大小不一样也就是在释放的时候必须还要指定具体是哪一个尺寸的内存池给你分配的内存因此这个结构主要就是记录这个信息。

    有的人又要问那我使用一个外部变量来管理不就可以了确实如此但是假象一下当你申请了100个内存块此时如果你自己来管理将是多磨的冗杂并且容易出错因此lwip为我们很好的解决了这个问题。

    讲完了mem使用内存池的申请我们接下来看看内存堆自己对申请和释放是如何管理的。首先来看一下内存堆初始的模型

     

    由图可以看出在初始化完成其实内存堆被分为了两个块一块是真正的内存卡及ram_heap和ram_end之间的内存另一个是ram_end之后的内存。至于为什么其后面存在多余的空闲的内存主要是考虑的内存对齐的开销因此增加了适量的内存。

     

    上图所示为mem在初始化完成申请的第一个内存可以看出Ifree会随着分配的进行指向最低空闲内存块同是由struct mem结构来连接新形成的内存块使其形成一个链式的结构使用used域来标识该内存块是否使用。

     

    上图表示申请了n次后的一个内存状况可以看出经过n次的内存申请和释放后内存趋于分散化此时将形成内存碎片而不是像memp一样0碎片化。而对于如何优化和处理碎片也是众多内存分配和释放算法的一个重要的区别。对于这个小的碎片我们需要在适时对其进行合并以达到一些大的内存申请需要。Lwip也提供了这样的机制我们将在源码分析中对其进行讲解。我们可以通过下图加以理解。

     

    由于图片太大貌似截图都不太清晰文章后边将附上这些图的下载链接供读者自行下载。

    3、Mem的源码分析 【1】内存池的初始化

       这里需要注意一点对于内存池分配方式而言是不需要mem_init()的初始化的原因在于mem_init主要是为内存堆准备起始环境而如果使用内存池分配不需要该环境而是需要初始化memp_init()内存池环境。

    void   mem_init(void)   {     struct mem *mem;        LWIP_ASSERT("Sanity check alignment",     (SIZEOF_STRUCT_MEM 0);        /* align the heap */     ram (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER);     /* initialize the start of the heap */     mem (struct mem *)(void *)ram;     mem->next MEM_SIZE_ALIGNED;     mem->prev 0;     mem->used 0;     /* initialize the end of the heap */     ram_end (struct mem *)(void *)     ram_end->used 1;     ram_end->next MEM_SIZE_ALIGNED;     ram_end->prev MEM_SIZE_ALIGNED;        /* initialize the lowest-free pointer to the start of the heap */     lfree (struct mem *)(void *)ram;            MEM_STATS_AVAIL(avail, MEM_SIZE_ALIGNED);            if(sys_mutex_new( ERR_OK) {       LWIP_ASSERT("failed to create mem_mutex", 0);     }   } 由上面可以看出内存堆初始化主要做了这几件事。

              1、初始化内存管理的基本结构即mem结构。

              2、初始化尾部ram_end及Ifree。

    【2】内存池的分配

        1、使用内存池分配 

    void *   mem_malloc(mem_size_t size)   {     void *ret;     struct memp_malloc_helper *element;     memp_t poolnr;     mem_size_t required_size size LWIP_MEM_ALIGN_SIZE(sizeof(struct memp_malloc_helper));  //计算实际需要的内存        for (poolnr MEMP_POOL_FIRST; poolnr < MEMP_POOL_LAST; poolnr (memp_t)(poolnr 1)) {   #if MEM_USE_POOLS_TRY_BIGGER_POOL    again:   #endif /* MEM_USE_POOLS_TRY_BIGGER_POOL */       /* is this pool big enough to hold an element of the required size         plus a struct memp_malloc_helper that saves the pool this element came from? */      if (required_size < memp_sizes[poolnr]) {         break;  //适配到需要的大小相当的内存池     }     }      if (poolnr > MEMP_POOL_LAST) {       LWIP_ASSERT("mem_malloc(): no pool is that big!", 0);       return NULL;     }     element (struct memp_malloc_helper*)memp_malloc(poolnr);  //从该内存池的链表上进行内存申请但是此时不一样链表上存在内存   if (element NULL) {       /* No need to DEBUGF or ASSERT: This error is already         taken care of in memp.c */   #if MEM_USE_POOLS_TRY_BIGGER_POOL  //如果定义了 可以查找其他的内存池链     /** Try a bigger pool if this one is empty! */       if (poolnr poolnr poolnr;     /* and return a pointer to the memory directly after the struct memp_malloc_helper */     ret (u8_t*)element LWIP_MEM_ALIGN_SIZE(sizeof(struct memp_malloc_helper));            return ret;   }   

    内存池的分配方式高效并且不会产生碎片这在memp内存池这一章已经讲过了mem的主要优点在于可以对不同内存大小的分配更加的灵活。以上的分配的策略主要是

  • 查找当前内存是否存在大于需求的内存池。存在将返回内存池的label
  • 通过label查找该内存池的空闲块看是否存在空闲内存不存在将分配失败。
  • 当然如果 使能了MEM_USE_POOLS_TRY_BIGGER_POOL宏的话将再次搜索下一个大于需求内存的label直到找到或者全遍历。

    2、使用内存堆分配

    mem_malloc(mem_size_t size)   {     mem_size_t ptr, ptr2;     struct mem *mem, *mem2;   #if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT     u8_t local_mem_free_count 0;   #endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */     LWIP_MEM_ALLOC_DECL_PROTECT();        if (size 0) {       return NULL;     }        /* Expand the size of the allocated memory region so that we can       adjust for alignment. */     size LWIP_MEM_ALIGN_SIZE(size);        if(size MEM_SIZE_ALIGNED) {       return NULL;     }            /* protect the heap from concurrent access */     sys_mutex_lock(     LWIP_MEM_ALLOC_PROTECT();   #if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT     /* run as long as a mem_free disturbed mem_malloc or mem_trim */     do {       local_mem_free_count 0;   #endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */              /* Scan through the heap searching for a free block that is big enough,       * beginning with the lowest free block.       */      //搜索空闲内存      for (ptr (mem_size_t)((u8_t *)lfree - ram); ptr next) {         mem (struct mem *)(void *)  //获取内存块首地址 #if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT         mem_free_count 0;         LWIP_MEM_ALLOC_UNPROTECT();         /* allow mem_free or mem_trim to run */         LWIP_MEM_ALLOC_PROTECT();         if (mem_free_count ! 0) {           /* If mem_free or mem_trim have run, we have to restart since they             could have altered our current struct mem. */           local_mem_free_count 1;           break;         }   #endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */          //查找空闲内存是否大于需求的内存       if ((!mem->used) SIZEOF_STRUCT_MEM)) > size) {         /* mem is not used and at least perfect fit is possible:           * mem->next - (ptr SIZEOF_STRUCT_MEM) gives us the user data size of mem */           if (mem->next - (ptr SIZEOF_STRUCT_MEM) > (size SIZEOF_STRUCT_MEM MIN_SIZE_ALIGNED)) {  //查看内存后边的空闲内存是否可以继续维持一个空闲内存块           /* (in addition to the above, we test if another struct mem (SIZEOF_STRUCT_MEM) containing             * at least MIN_SIZE_ALIGNED of data also fits in the user data space of mem)             * -> split large block, create empty remainder,             * remainder must be large enough to contain MIN_SIZE_ALIGNED data: if             * mem->next - (ptr (2*SIZEOF_STRUCT_MEM)) size,             * struct mem would fit in but no data between mem2 and mem2->next             * todo we could leave out MIN_SIZE_ALIGNED. We would create an empty             *       region that couldnt hold data, but when mem->next gets freed,             *       the 2 regions would be combined, resulting in more free memory             */             ptr2 ptr SIZEOF_STRUCT_MEM size; //分配内存后边的内存地址             /* create mem2 struct */             mem2 (struct mem *)(void *) //后边内存形成一个新的内存块             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) {  //不是尾部结束块需要将其指向前面的内存块形成双向链表             ((struct mem *)(void *) ptr2;             }             MEM_STATS_INC_USED(used, (size SIZEOF_STRUCT_MEM));           }            else {   //后边内存过小出现碎片化           /* (a mem2 struct does no fit into the user data space of mem and mem->next will always             * be used at this point: if not we have 2 unused structs in a row, plug_holes should have             * take care of this).             * -> near fit or excact fit: do not split, no mem2 creation             * also cant move mem->next directly behind mem, since mem->next             * will always be used at this point!             */             mem->used 1;             MEM_STATS_INC_USED(used, mem->next - (mem_size_t)((u8_t *)mem - ram));           }   #if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT   mem_malloc_adjust_lfree:   #endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */           if (mem lfree) { //Ifree更新到新的空闲内存地址             struct mem *cur lfree;             /* Find next free block after mem and update lowest free pointer */             while (cur->used ram_end) {   #if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT               mem_free_count 0;               LWIP_MEM_ALLOC_UNPROTECT();               /* prevent high interrupt latency... */               LWIP_MEM_ALLOC_PROTECT();               if (mem_free_count ! 0) {                 /* If mem_free or mem_trim have run, we have to restart since they                   could have altered our current struct mem or lfree. */                 goto mem_malloc_adjust_lfree;               }   #endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */               cur (struct mem *)(void *)             }             lfree cur;             LWIP_ASSERT("mem_malloc: !lfree->used", ((lfree ram_end) || (!lfree->used)));           }           LWIP_MEM_ALLOC_UNPROTECT();           sys_mutex_unlock(           LWIP_ASSERT("mem_malloc: allocated memory not above ram_end.",            (mem_ptr_t)mem SIZEOF_STRUCT_MEM size < (mem_ptr_t)ram_end);           LWIP_ASSERT("mem_malloc: allocated memory properly aligned.",            ((mem_ptr_t)mem SIZEOF_STRUCT_MEM) % MEM_ALIGNMENT 0);           LWIP_ASSERT("mem_malloc: sanity check alignment",             (((mem_ptr_t)mem) 0);                  return (u8_t *)mem SIZEOF_STRUCT_MEM; //返回申请的内存地址        }       }   #if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT       /* if we got interrupted by a mem_free, try again */     } while(local_mem_free_count ! 0);   #endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */     LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mem_malloc: could not allocate %"S16_F" bytes\n", (s16_t)size));     MEM_STATS_INC(err);     LWIP_MEM_ALLOC_UNPROTECT();     sys_mutex_unlock(     return NULL;  //申请失败 }   由上边的源码分析可知内存堆的分配只要是通过struct mem结构来形成一个双向的链表进行管理的而管理整个内存链的过程中如何区分已使用的内存和空闲的内存块呢? 于是引入used字段因此在这个内存链中used字段将至关重要。还有一点大家需要明白mem分配和memp分配有一个较大的差异memp分配会将以使用的内存从空闲内存链中去除而mem中整个内存是一个完整的内存链因此即使内存已经分配出去还是存在于系统的内存堆链中。

    【3】内存的释放

    内存的申请存在两种方式那么必然对应着两种释放方式。

    1、内存池分配方式的释放

    void   mem_free(void *rmem)   {     struct memp_malloc_helper *hmem;       LWIP_ASSERT("rmem ! NULL", (rmem ! NULL));     LWIP_ASSERT("rmem MEM_ALIGN(rmem)", (rmem LWIP_MEM_ALIGN(rmem)));        /* get the original struct memp_malloc_helper */     hmem (struct memp_malloc_helper*)(void*)((u8_t*)rmem - LWIP_MEM_ALIGN_SIZE(sizeof(struct memp_malloc_helper)));  //找到分配的真正首地址          LWIP_ASSERT("hmem ! NULL", (hmem ! NULL));     LWIP_ASSERT("hmem MEM_ALIGN(hmem)", (hmem LWIP_MEM_ALIGN(hmem)));     LWIP_ASSERT("hmem->poolnr poolnr poolnr, hmem);  //通过struct memp_malloc_helper结构得到pool类型hmem得到首地址从而进行释放 }  其实对于内存池的申请比较简单这里就不讲了读者可以通过我们的上一篇关于memp的文章来学习这里带过。

    2、内存堆分配方式的释放

    void   mem_free(void *rmem)   {     struct mem *mem;     LWIP_MEM_FREE_DECL_PROTECT();        if (rmem NULL) { //合法性检查     LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS, ("mem_free(p NULL) was called.\n"));      return;     }     LWIP_ASSERT("mem_free: sanity check alignment", (((mem_ptr_t)rmem) 0);            LWIP_ASSERT("mem_free: legal memory", (u8_t *)rmem > (u8_t *)ram         //合法性检查       if ((u8_t *)rmem (u8_t *)ram_end) {          SYS_ARCH_DECL_PROTECT(lev);           LWIP_DEBUGF(MEM_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("mem_free: illegal memory\n"));           /* protect mem stats from concurrent access */           SYS_ARCH_PROTECT(lev);           MEM_STATS_INC(illegal);           SYS_ARCH_UNPROTECT(lev);           return;     }     /* protect the heap from concurrent access */     LWIP_MEM_FREE_PROTECT();     /* Get the corresponding struct mem ... */     mem (struct mem *)(void *)((u8_t *)rmem - SIZEOF_STRUCT_MEM);  //获取真实的内存首地址   /* ... which has to be in a used state ... */     LWIP_ASSERT("mem_free: mem->used", mem->used);     /* ... and is now unused. */     mem->used 0;  //释放内存          if (mem next - (mem_size_t)(((u8_t *)mem - ram)));            /* finally, see if prev or next are free also */     plug_holes(mem);  //内存合并 #if LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT     mem_free_count 1;   #endif /* LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT */     LWIP_MEM_FREE_UNPROTECT();   }  由上可以内存堆的释放同样比较简单主要是两个工作。

  • 释放内存及将used字段清0。
  • 检查释放内存的前后如果存在空闲内存将进行内存的合并。
  • 当然mem中还存在其他的一些功能函数如void *mem_trim(void *rmem, mem_size_t newsize)进行内存的压缩等等。读者可根据兴趣自行阅读和分析。

    最后在指出一点就是当系统使用了默认的c分配函数如malloc和free是将存在如下的内存分配函数。

    void *mem_calloc(mem_size_t count, mem_size_t size)   {     void *p;        /* allocate count objects of size size */     p mem_malloc(count * size);     if (p) {       /* zero the memory */       memset(p, 0, count * size);     }     return p;   }   其同样完成两个工作。

  • 内存的分配
  • 对分配的内存进行清0操作maybe这样的清0操作将使得数据更安全。
  • #ifndef mem_free   #define mem_free free   #endif   #ifndef mem_malloc   #define mem_malloc malloc   #endif   #ifndef mem_calloc   #define mem_calloc calloc   #endif   这是使用c系统函数分配的声明和定义这里我就不一一讲解了读者可以自行在mem.h函数中查找这也就是模块化编程的最大优点模块间相互区里耦合性较低。

    关于mem内存堆的相关问题的分析就到这里

    上一篇:前端学习(2592):当前用户显示
    下一篇:没有了
    网友评论