首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

《LwIP协议栈源码详解——TCP/IP协议的实现》pbuf释放

《LwIP协议栈源码详解——TCP/IP协议的实现》pbuf释放

不停的写东西,只是为了逃避某种心情。(PS:现在不停的C+V,只是迫于某人的鸭梨和对知识传播的追求,霍霍)牢骚发完,GoOn。昨天说到了数据缓冲pbuf的内存申请,今天继续来探究一下它的内存释放过程。由于pbuf的申请主要是通过内存堆分配和内存池分配来实现,所以,pbuf的释放也必须按照这两种情况分别讨论。
别慌,在展开讨论之前,还得说说某个pbuf能被释放的前提。在LWIP中这点很容易判断,因为前节说到pbufref字段表示该pbuf被引用的次数,当pbuf被创建时,该字段的初始值为1,由此可判断,当pbufref字段为1时,该pbuf才可以被删除,所以位于pbufs链表中间的pbuf结构是不会被删除成功的,因为他们的ref值至少是2。由此总之一下,能被删除的pbuf必然是某个pbufs链的首节点。当然pbuf的删除工作远不如此的简单,其中另一个需要特别注意的地方是,想想,很可能的情况是某个pbufs链的首节点删除成功后,该pbufs链的第二个节点就自然的成为该pbufs链的首节点,此时,该节点的ref值可能变为1(该节点没有被引用了),这种情况下,该节点也会被删除,因为LWIP认为它和第一个节点一起存储同一数据包。当第二个节点也被删除后,LWIP又会去看看第三个节点是否满足删除条件就这样一直删一下去。当然,如果首节点删除后,第二个节点的ref值大于1,表示该节点还在其他地方被引用,不能再被删除,删除工作至此结束。这段话写的很口水,不如我们举个例子来看看这个删除过程。假如现在我们的pbufs链表有ABC三个pbuf结构连接起来,结构为A--->B--->C,利用pbuf_free(A)函数来删除pbuf结构,下面用ABC的几组不同ref值来看看删除结果:
11->2->3函数执行后变为 ...1->3,节点BC仍在;
23->3->3函数执行后变为2->3->3,节点ABC仍在;
31->1->2函数执行后变为......1,节点C仍在;
42->1->1函数执行后变为1->1->1,节点ABC仍在;
51->1->1函数执行后变为.......,节点全部被删除。
如果您能说醍醐灌顶,那将是我最大的动力。
当可以删除某个pbuf结构时,LWIP首先检查这个pbuf是属于前节讲到的四个类型中的哪种,根据类型的不同,调用不同的内存释放函数进行删除。PBUF_POOL类型和PBUF_ROM类型、PBUF_REF类型需要通过memp_free()函数删除,PBUF_RAM类型需要通过mem_free()函数删除,原因不解释。
PBUF_RAM类型来自于内存堆,所以需通过mem_free()函数将pbuf释放回内存堆。这里,先得来看看内存堆的组织结构,见下图,在内存堆内部,内存堆管理模块通过在每一个内存分配块的顶部放置一个比较小的结构体来保存内存分配纪录(注意这个小小的结构体是内存管理模块自动附加上去的,独立于用户的申请大小)。这个结构体拥有三个成员变量,两个指针和一个标志,如图。nextprev分别指向内存的下一个和上一个分配块,used标志表示该内存块是否已被分配。图中需要注意的两个地方,first,图中每个内存块的大小是不同且可能随时变化的。Second,当系统初始化的时候,整个内存堆就是一个内存块,下图中是经过多次分配释放后内存堆呈现出来的结果。
内存堆管理模块根据所申请分配的大小来搜索所有未被使用的内存分配块,检索到的最先满足条件的内存块将分配给申请者,注意这里并不包括前面说到的那个小小结构体,所以用户得到的是used后的那个地址。当分配成功后,内存管理模块会马上在已经分配走了的数据区后面再插一个小小的结构体,并用nextprev指针将这个结构体串起来,以便于下次分配。经过几次的申请与释放,我们就看到了图中的内存堆组织模型。
当内存释放的时候,为了防止内存碎片的产生,上一个与下一个分配块的使用标志会被检查,如果他们中的任何一个还未被使用,这个内存块将被合并到一个更大的未使用内存块中。内存堆管理模块是这样做的,它根据用户提供的释放地址退后几个字节去寻找这个小小的结构体,利用这个结构体来实现内存堆得合并等操作。已经分配的内存块被回收后,使用标志used清零。当然,如果上一个与下一个分配块都已被使用,这时的释放就是最简单的情况,但这也是产生内存堆碎片问题的根源。



哎,要了老命了。
接着来讲其他三种结构通过memp_free()函数将pbuf释放回内存池的情况。前面的内容已经讲过了内存池POOL的结构,PBUF_POOLpbuf主要使用的是MEMP_PBUF_POOL类型的POOLPBUF_ROMPBUF_REFpbuf主要使用的是MEMP_PBUF型的POOL。这句话太绕了,你应该多读两遍。POOL结构的起始处有个next指针,用于指向同类型的下一个POOL,用于将同类型的POOL连接成一个单向链表,这里应该有必要仔细看看POOL池是怎样初始化的,代码很简单:
  memp =LWIP_MEM_ALIGN(memp_memory);
  for (i = 0; i< MEMP_MAX; ++i){   //对各种类型的POOL依次操作
   memp_tab =NULL;          //空闲链表头初始为空
   for (j = 0; j < memp_num; ++j){  //把同类POOL链成链表
     memp->next = memp_tab;
     memp_tab = memp;
     memp = (struct memp *)((u8_t *)memp + MEMP_SIZE +memp_sizes);//取得下
   }   //一个POOL的地址
  }
上面代码中有几个重要的全局变量,memp_memory是缓冲池的起始地址,前面已有所讨论;MEMP_MAXPOOL类型数;memp_tab用于指向某类POOL空闲链表的起始节点;memp_num表示各种类型POOL的个数;memp_sizes表示各种类型单个POOL的大小,对于MEMP_PBUF_POOLMEMP_PBUF型的POOL,其大小是pbuf头和pbuf可装载数据大小的总和。
在这样的基础之上,POOL池的释放就简单了,首先根据POOL的类型找到相应空闲链表头memp_tab,将该POOL插在链表头上,并把memp_tab指向链表头,简单快捷。至于POOL池的的申请那自然而然的也就是对memp_tab头的操作了,这个相信你懂。
好了,到这里,LWIP的内存相关机制就基本介绍完毕。当然,它不可能这样简单,在以后使用到的地方,我会再加说明。
整理整理,收工。周末来啦….冬眠一天是必不可少的….哈哈
继承事业,薪火相传
返回列表