LWIP中“pbuf_free: p->ref > 0“ failed 问题分析

配置:

单片机:STM32H743VIT6

有线网卡:DM9162A

操作系统:FreeRTOS CMSISV1接口

协议栈:lwip

bug:

pbuf_free: p->ref > 0
Assertion: 747 in ......\components\net\lwip-2.0.2\src\core\pbuf.c, thread tcpip

问题描述:当项目实际应用中,将UDP包在应用层的Receive接收线程上传到消息队列,供Analysis解析线程使用,当接收速率过大(实测百兆网的STM32H743,接收速率接近94 Mbps,非常优秀),Analysis消化消息不及时,导致Receive一直阻塞在上传消息,然而应用层,则会出现此问题。然而应用层的事又怎么影响到的底层(Receive优先级不管大于还是小于ethernet_input,都会影响)。问题解决的办法是加快了Analysis的处理速度,使得上传和消化的速度达到平衡。

个人分析:由于在大量发送数据时无此现象,说明该问题主要是在接收时产生的,接收线程(无论是上层还是底层)由于某种情况被阻塞,初步猜测,是接收的缓冲pbuf试图释放,而pbuf仍待处理,导致pbuf无法正常被free掉。

进一步断点调试,得知函数调用关系:pbuf_free()——>ethernet_input()——>tcpip_thread()。

资料查询:6.6:pbuf_free() · LwIP应用开发实战指南 · 看云

        数据包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变化情况,具体见。

        从这两张图中我们也看到了,当删除第一个节点后,如果后续的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没被回收,意味着一部分内存池就这样被泄漏了,以后没办法用了。同时,还可能将一些尚未处理的数据回收了,这样子整个系统就乱套了。

目前的问题虽然稀里糊涂的解决了,但底层原因还是没研究透彻,先把对解决该问题有帮助的资料都堆在这。

持续更新中。

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值