企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
数据包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没被回收,意味着一部分内存池就这样被泄漏了,以后没办法用了。同时,还可能将一些尚未处理的数据回收了,这样子整个系统就乱套了。