标题:
《LwIP协议栈源码详解——TCP/IP协议的实现》pbuf释放
[打印本页]
作者:
yuyang911220
时间:
2016-9-16 17:09
标题:
《LwIP协议栈源码详解——TCP/IP协议的实现》pbuf释放
不停的写东西,只是为了逃避某种心情。(PS:现在不停的C+V,只是迫于某人的鸭梨和对知识传播的追求,霍霍)牢骚发完,
GoOn
。昨天说到了数据缓冲
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
=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_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
的内存相关机制就基本介绍完毕。当然,它不可能这样简单,在以后使用到的地方,我会再加说明。
整理整理,收工。周末来啦
….
冬眠一天是必不可少的
….
哈哈
欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/)
Powered by Discuz! 7.0.0