数据包pbuf的释放是必须的,因为当内核处理完数据就要将这些资源进行回收,否则就会造成内存泄漏,在后续的数据处理中无法再次申请内存。当底层将数据发送出去后或者当应用层将数据处理完毕的时候,数据包就要被释放掉。
当然,既然要释放数据包,那么肯定有条件,pbuf中ref字段就是记录pbuf数据包被引用的次数,在申请pbuf的时候,ref字段就被初始化为1,当释放pbuf的时候,先将ref减1,如果ref减1后为0,则表示能释放pbuf数据包,此外,能被内核释放的pbuf数据包只能是首节点或者其他地方未被引用过的节点,如果用户错误地调用pbuf释放函数,将pbuf链表中的某个中间节点删除了,那么必然会导致错误。
前面我们也说了,一个数据包可能会使用链表的形式将多个pbuf连接起来,那么假如删除一个首节点,怎么保证删除完属于一个数据包的数据呢?很简单,LwIP的数据包释放函数会自动删除属于一个数据包中连同首节点在内所有pbuf,举个例子,假设一个数据包需要3个pbuf连接起来,那么在删除第一个pbuf的时候,内核会检测一下它下一个pbuf释放与首节点是否存储同一个数据包的数据,如果是那就将第二个节点也删除掉,同理第三个也会被删除。但如果删除某个pbuf链表的首节点时,链表中第二个节点的pbuf中ref字段不为0,则表示该节点还在其他地方被引用,那么第二个节点不与第一个节点存储同一个数据包,那么就不会删除第二个节点。
下面用示意图来解释一下删除的过程,假设有4个pbuf链表,链表中每个pbuf的ref都有一个值,具体见图 6‑5,当调用pbuf\_free()删除第一个节点的时候,剩下的pbuf变化情况,具体见。
![](https://box.kancloud.cn/af3e853ba405d070e771a8a232d94895_855x472.png)
![](https://box.kancloud.cn/f655926d2a73c424b51d8301a436caeb_837x462.png)
从这两张图中我们也看到了,当删除第一个节点后,如果后续的pbuf的ref为1(即与第一个节点存储同一个数据包),那么该节点也会被删除。第一个pbuf链表在删除首节点之后就不存在节点;第二个pbuf链表在删除首节点后只存在pbuf3;第三个pbuf链表在删除首节点后还存在pbuf2与pbuf3;第四个链表还不能删除首节点,因为该数据包还在其他地方被引用了。
pbuf\_free()函数源码具体见
```
1 u8_t
2 pbuf_free(struct pbuf *p)
3 {
4 u8_t alloc_src;
5 struct pbuf *q;
6 u8_t count;
7
8 if (p == NULL)
9 {
10 return 0; (1)
11 }
12
13 PERF_START;
14
15 count = 0;
16
17 while (p != NULL)
18 {
19 LWIP_PBUF_REF_T ref;
20
21 SYS_ARCH_DECL_PROTECT(old_level);
22
23 SYS_ARCH_PROTECT(old_level);
24
25 ref = --(p->ref); (2)
26 SYS_ARCH_UNPROTECT(old_level);
27 /* this pbuf is no longer referenced to? */
28 if (ref == 0)
29 {
30 /* remember next pbuf in chain for next iteration */
31 q = p->next; (3)
32
33 alloc_src = pbuf_get_allocsrc(p); (4)
34 /* is this a pbuf from the pool? */
35 if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF_POOL)
36 {
37 memp_free(MEMP_PBUF_POOL, p);
38 /* is this a ROM or RAM referencing pbuf? */
39 }
40 else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_MEMP_PBUF)
41 {
42 memp_free(MEMP_PBUF, p);
43 /* type == PBUF_RAM */
44 }
45 else if (alloc_src == PBUF_TYPE_ALLOC_SRC_MASK_STD_HEAP)
46 {
47 mem_free(p);
48 }
49 else
50 {
51 /* @todo: support freeing other types */
52 LWIP_ASSERT("invalid pbuf type", 0);
53 }
54 count++; (5)
55 /* proceed to next pbuf */
56 p = q; (6)
57 /* p->ref > 0, this pbuf is still referenced to */
58 /* (and so the remaining pbufs in chain as well) */
59 }
60 else
61 {
62 /* stop walking through the chain */
63 p = NULL;
64 }
65 }
66 PERF_STOP("pbuf_free");
67 /* return number of de-allocated pbufs */
68 return count;
69 }
```
(1):如果释放的pbuf地址为空,则直接返回。
(2):将pbuf中ref字段减一。
(3):若ref为0,表示该pbuf被引用次数为0,则可以删除该pbuf,用q记录下当前pbuf的下一个pbuf。
(4):获取当前pbuf的类型,根据不一样的类型进行不一样的释放操作,如果是从内存池中申请的pbuf,则调用memp_free()函数进行释放,如PBUF_POOL、PBUF_ROM和PBUF_REF类型的pbuf,如果是从内存堆中申请的,就调用mem_free()函数进行释放内存,如PBUF_RAM类型的pbuf。
(5):记录删除的pbuf个数。
(6):处理链表中的下一个pbuf,直到pbuf中引用次数不为0才退出。
pbuf的释放要小心,如果pbuf是串成链表的话, pbuf在释放的时候,就会把pbuf的ref值减1,然后函数会判断ref减完之后是不是变成0,如果是0就会根据pbuf的类型调用内存池或者内存堆回收函数进行回收。然后这里就有个很危险的事,对于这个pbuf_free()函数,用户传递的参数必须是链表头指针,假如不是链表头而是指向链表中间的某个pbuf的指针,那就很容易出现问题,因为这个pbuf_free()函数可不会帮我们检查是不是链表头,这样子势必会导致一部分pbuf没被回收,意味着一部分内存池就这样被泄漏了,以后没办法用了。同时,还可能将一些尚未处理的数据回收了,这样子整个系统就乱套了。
- 说明
- 第1章:网络协议简介
- 1.1:常用网络协议
- 1.2:网络协议的分层模型
- 1.3:协议层报文间的封装与拆封
- 第2章:LwIP简介
- 2.1:LwIP的优缺点
- 2.2:LwIP的文件说明
- 2.2.1:如何获取LwIP源码文件
- 2.2.2:LwIP文件说明
- 2.3:查看LwIP的说明文档
- 2.4:使用vscode查看源码
- 2.4.1:查看文件中的符号列表(函数列表)
- 2.4.2:函数定义跳转
- 2.5:LwIP源码里的example
- 2.6:LwIP的三种编程接口
- 2.6.1:RAW/Callback API
- 2.6.2:NETCONN API
- 2.6.3:SOCKET API
- 第3章:开发平台介绍
- 3.1:以太网简介
- 3.1.1:PHY层
- 3.1.2:MAC子层
- 3.2:STM32的ETH外设
- 3.3:MII 和 RMII 接口
- 3.4:PHY:LAN8720A
- 3.5:硬件设计
- 3.6:软件设计
- 3.6.1:获取STM32的裸机工程模板
- 3.6.2:添加bsp_eth.c与bsp_eth.h
- 3.6.3:修改stm32f4xx_hal_conf.h文件
- 第4章:LwIP的网络接口管理
- 4.1:netif结构体
- 4.2:netif使用
- 4.3:与netif相关的底层函数
- 4.4:ethernetif.c文件内容
- 4.4.1:ethernetif数据结构
- 4.4.2:ethernetif_init()
- 4.4.3:low_level_init()
- 第5章:LwIP的内存管理
- 5.1:几种内存分配策略
- 5.1.1:固定大小的内存块
- 5.1.2:可变长度分配
- 5.2:动态内存池(POOL)
- 5.2.1:内存池的预处理
- 5.2.2:内存池的初始化
- 5.2.3:内存分配
- 5.2.4:内存释放
- 5.3:动态内存堆
- 5.3.1:内存堆的组织结构
- 5.3.2:内存堆初始化
- 5.3.3:内存分配
- 5.3.4:内存释放
- 5.4:使用C库的malloc和free来管理内存
- 5.5:LwIP中的配置
- 第6章:网络数据包
- 6.1:TCP/IP协议的分层思想
- 6.2:LwIP的线程模型
- 6.3:pbuf结构体说明
- 6.4:pbuf的类型
- 6.4.1:PBUF_RAM类型的pbuf
- 6.4.2:PBUF_POOL类型的pbuf
- 6.4.3:PBUF_ROM和PBUF_REF类型pbuf
- 6.5:pbuf_alloc()
- 6.6:pbuf_free()
- 6.7:其它pbuf操作函数
- 6.7.1:pbuf_realloc()
- 6.7.2:pbuf_header()
- 6.7.3:pbuf_take()
- 6.8:网卡中使用的pbuf
- 6.8.1:low_level_output()
- 6.8.2:low_level_input()
- 6.8.3:ethernetif_input()
- 第7章:无操作系统移植LwIP
- 7.1:将LwIP添加到裸机工程
- 7.2:移植头文件
- 7.3:移植网卡驱动
- 7.4:LwIP时基
- 7.5:协议栈初始化
- 7.6:获取数据包
- 7.6.1:查询方式
- 7.6.2:ping命令详解
- 7.6.3:中断方式
- 第8章:有操作系统移植LwIP
- 8.1:LwIP中添加操作系统
- 8.1.1:拷贝FreeRTOS源码到工程文件夹
- 8.1.2:添加FreeRTOS源码到工程组文件夹
- 8.1.3:指定FreeRTOS头文件的路径
- 8.1.4:修改stm32f10x_it.c
- 8.2:lwipopts.h文件需要加入的配置
- 8.3:sys_arch.c/h文件的编写
- 8.4:网卡底层的编写
- 8.5:协议栈初始化
- 8.6:移植后使用ping测试基本响应
- 第9章:LwIP一探究竟
- 9.1:网卡接收数据的流程
- 9.2:内核超时处理
- 9.2.1:sys_timeo结构体与超时链表
- 9.2.2:注册超时事件
- 9.2.3:超时检查
- 9.3:tcpip_thread线程
- 9.4:LwIP中的消息
- 9.4.1:消息结构
- 9.4.2:数据包消息
- 9.4.3:API消息
- 9.5:揭开LwIP神秘的面纱
- 第10章:ARP协议
- 10.1:链路层概述
- 10.2:MAC地址的基本概念
- 10.3:初识ARP
- 10.4:以太网帧结构
- 10.5:IP地址映射为物理地址
- 10.6:ARP缓存表
- 10.7:ARP缓存表的超时处理
- 10.8:ARP报文
- 10.9:发送ARP请求包
- 10.10:数据包接收流程
- 10.10.1:以太网之数据包接收
- 10.10.2:ARP数据包处理
- 10.10.3:更新ARP缓存表
- 10.11:数据包发送流程
- 10.11.1:etharp_output()函数
- 10.11.2:etharp_output_to_arp_index()函数
- 10.11.3:etharp_query()函数
- 第11章:IP协议
- 11.1:IP地址.md
- 11.1.1:概述
- 11.1.2:IP地址编址
- 11.1.3:特殊IP地址