LwIP 协议栈源码详解 ——TCP/IP 协议的实现(五:pbuf 释放)

来源:互联网 发布:公众号互粉平台源码 编辑:程序博客网 时间:2024/05/10 20:37
4 pbuf 释放
牢骚发完,Go On。昨天说到了数据缓冲 pbuf 的内存申请,今天继续来探究一下它的
内存释放过程。由于 pbuf 的申请主要是通过内存堆分配和内存池分配来实现,所以,pbuf
的释放也必须按照这两种情况分别讨论。
别慌,在展开讨论之前,还得说说某个 pbuf 能被释放的前提。在 LWIP 中这点很容易
判断,因为前节说到 pbuf 的 ref 字段表示该 pbuf 被引用的次数,当 pbuf 被创建时,该字段
的初始值为 1,由此可判断,当 pbuf 的 ref 字段为 1 时,该 pbuf 才可以被删除,所以位于
pbufs 链表中间的 pbuf 结构是不会被删除成功的,因为他们的 ref 值至少是 2。由此总之一
下,能被删除的 pbuf 必然是某个 pbufs 链的首节点。当然 pbuf 的删除工作远不如此的简单,
其中另一个需要特别注意的地方是,想想,很可能的情况是某个 pbufs 链的首节点删除成功
后,该 pbufs 链的第二个节点就自然的成为该 pbufs 链的首节点,此时,该节点的 ref 值可
能变为 1(该节点没有被引用了),这种情况下,该节点也会被删除,因为 LWIP 认为它和第
一个节点一起存储同一数据包。当第二个节点也被删除后,LWIP 又会去看看第三个节点是
否满足删除条件…就这样一直删一下去。当然,如果首节点删除后,第二个节点的 ref 值大
于 1,表示该节点还在其他地方被引用,不能再被删除,删除工作至此结束。这段话写的很
口水,不如我们举个例子来看看这个删除过程。假如现在我们的 pbufs 链表有 A,B,C 三
个 pbuf 结构连接起来,结构为 A--->B--->C,利用 pbuf_free(A)函数来删除 pbuf 结构,下面
用 ABC 的几组不同 ref 值来看看删除结果:
(1)1->2->3  函数执行后变为...1->3,节点 BC 仍在;
(2)3->3->3  函数执行后变为 2->3->3,节点 ABC 仍在;
(3)1->1->2  函数执行后变为......1,节点 C 仍在;
(4)2->1->1 函数执行后变为 1->1->1,节点 ABC 仍在;
(5)1->1->1 函数执行后变为.......,节点全部被删除。
如果您能说醍醐灌顶,那将是我最大的动力。
当可以删除某个 pbuf 结构时,LWIP 首先检查这个 pbuf 是属于前节讲到的四个类型中
的哪种,根据类型的不同,调用不同的内存释放函数进行删除。PBUF_POOL 类型和
PBUF_ROM 类型、PBUF_REF 类型需要通过 memp_free()函数删除,PBUF_RAM 类型需要
通过 mem_free()函数删除,原因不解释。
PBUF_RAM 类型来自于内存堆,所以需通过 mem_free()函数将 pbuf 释放回内存堆。这
里,先得来看看内存堆的组织结构,见下图,在内存堆内部,内存堆管理模块通过在每一个
内存分配块的顶部放置一个比较小的结构体来保存内存分配纪录(注意这个小小的结构体是
内存管理模块自动附加上去的,独立于用户的申请大小)。这个结构体拥有三个成员变量,
两个指针和一个标志,如图。next  与prev 分别指向内存的下一个和上一个分配块,used 标
志表示该内存块是否已被分配。图中需要注意的两个地方,first,图中每个内存块的大小是
不同且可能随时变化的。Second,当系统初始化的时候,整个内存堆就是一个内存块,下图
中是经过多次分配释放后内存堆呈现出来的结果。
内存堆管理模块根据所申请分配的大小来搜索所有᳾被使用的内存分配块,检索到的
最先满足条件的内存块将分配给申请者,注意这里并不包括前面说到的那个小小结构体,所
以用户得到的是 used 后的那个地址。当分配成功后,内存管理模块会马上在已经分配走了

的数据区后面再插一个小小的结构体,并用 next 和 prev 指针将这个结构体串起来,以便于

下次分配。经过几次的申请与释放,我们就看到了图中的内存堆组织模型。
当内存释放的时候,为了防止内存碎片的产生,上一个与下一个分配块的使用标志会
被检查,如果他们中的任何一个还᳾被使用,这个内存块将被合并到一个更大的᳾使用内存
块中。内存堆管理模块是这样做的,它根据用户提供的释放地址退后几个字节去寻找这个小
小的结构体,利用这个结构体来实现内存堆得合并等操作。已经分配的内存块被回收后,使
用标志used  清零。当然,如果上一个与下一个分配块都已被使用,这时的释放就是最简单
的情况,但这也是产生内存堆碎片问题的根源。
哎,要了老命了。
接着来讲其他三种结构通过 memp_free()函数将 pbuf 释放回内存池的情况。前面的内容
已经讲过了内存池 POOL 的结构,PBUF_POOL 型 pbuf 主要使用的是 MEMP_PBUF_POOL
类型的 POOL, PBUF_ROM 和 PBUF_REF 型 pbuf 主要使用的是 MEMP_PBUF 型的 POOL。
这句话太绕了,你应该多读两遍。POOL 结构的起始处有个 next 指针,用于指向同类型的
下一个 POOL,用于将同类型的 POOL 连接成一个单向链表,这里应该有必要仔细看看 POOL
池是怎样初始化的,代码很简单:
memp = LWIP_MEM_ALIGN(memp_memory); 
for (i = 0; i < MEMP_MAX; ++i) { //对各种类型的 POOL 依次操作
memp_tab[i] = NULL; //空闲链表头初始为空
for (j = 0; j < memp_num[i]; ++j) { //把同类 POOL 链成链表
memp->next = memp_tab[i]; 
memp_tab[i] = memp; 
memp = (struct memp *)((u8_t *)memp + MEMP_SIZE + memp_sizes[i]);//取得下
} //一个 POOL 的地址


E-mail:for_rest@foxmail.com    老衲五木出品


上面代码中有几个重要的全局变量, memp_memory 是缓冲池的起始地址,前面已有所讨论;
MEMP_MAX 是 POOL 类型数; memp_tab 用于指向某类 POOL 空闲链表的起始节点;
memp_num 表示各种类型 POOL 的个数;memp_sizes 表示各种类型单个 POOL 的大小,对
于 MEMP_PBUF_POOL 和 MEMP_PBUF 型的 POOL,其大小是 pbuf 头和 pbuf 可装载数据
大小的总和。
在这样的基础之上, POOL 池的释放就简单了,首先根据 POOL 的类型找到相应空闲链
表头 memp_tab,将该 POOL 插在链表头上,并把 memp_tab 指向链表头,简单快捷。至于
POOL 池的的申请那自然而然的也就是对 memp_tab 头的操作了,这个相信你懂。
好了,到这里,LWIP 的内存相关机制就基ᴀ介绍完毕。当然,它不可能这样简单,在
以后使用到的地方,我会再加说明。
整理整理,收工。周᳿来啦….冬眠一天是必不可少的….哈哈