配置:
单片机:stm32h743VIT6
有线网卡:lan8720
操作系统:r-thread
协议栈:lwip
bug:
pbuf_free: p->ref > 0
Assertion: 747 in …\components\net\lwip-2.0.2\src\core\pbuf.c, thread tcpip
问题描述:
当调试的时候,中途断点的停顿时间超20s,就会触发以上bug。
参考资料:
问题定位+解决方法:
**问题定位:**pbuf_free函数释放pbuf出错,因为被释放的p中的p->ref是0。
**问题再定位:**在pbuf_free函数中,LWIP_ASSERT("pbuf_free: p->ref > 0", p->ref > 0);
这一语句之前加if(p->ref == 0){p->ref = 1;}
,在p->ref = 1打断点,单步调试跟踪程序运行。
1.得知函数调用关系:pbuf_free()——>ethernet_input()——>tcpip_thread()。
2.每次单步运行,pbuf内容都会变化。猜测网络数据包是在中断函数里接收。
3.pbuf地址有四个,轮换着用,猜测是定义好的pbuf全局/静态数组。
**问题再再定位:**中断使得数据不断在变化不好操作,因此在前面的if语句加上关中断:if(p->ref == 0){p->ref = 1;HAL_NVIC_DisableIRQ(ETH_IRQn);}
,还有头文件#include "stm32h7xx_hal.h"
。在p->ref = 1打断点,全速运行查看数据包状态。
1.对比第一与第二张图,中断关闭前后,p中数据都有变化。说明关中断前更新了网络数据。
2.比对第二,三,四,五张图,每个pbuf的地址都不一样,且pbuf中的ref都不等于0,所以程序正常运行。
3.比对第二,六张图,两个pbuf的地址一样,且后者包括ref等数据都是0。运行完第六张图后就报错。可以推测是该pbuf在第二张图被释放后,在第六张图又释放一次。所以就报错了。(这里之所以没报内存错误,是因为pbuf->ref的判断在释放内存之前)
**问题再再再定位:**想知道为什么会释放两次pbuf。要知道<1>pbuf到底是什么。<2>pbuf从哪里来的。这就需要看看lwip相关资料。这里参考pbuf链表结构和数据包接收过程+pbuf传递流程,可以得到:
<1>pbuf是一个链表,包含next指针,payload数据包指针,tot_len该pbuf及后面链表数据包的长度,len表示该pbuf的数据包长度。ref表示该pbuf在其他pbuf后面的次数(被几个pbuf的next指针指向)。具体见下图:(但在工程中,单个pbuf的长度够用,不需要串成链表)
<2>low_level_input()获取网络数据包并返回pbuf指针,netif->input()(即tcpip_input())调用tcpip_inpkt()把pbuf指针打包进msg,最后调用sys_mbox_trypost()把msg的地址作为邮件发送到邮箱。接收——打包——发送这个流程都是在中断服务函数ETH_IRQHandler()实现。
以上是打包发送pbuf数据包,再来是接收处理数据包:tcpip_thread()接收邮箱中的msg指针,根据msg里边设置的数据包处理函数ethernet_input(),处理pbuf数据包。流程如下图:
<3>在捋顺数据传递过程并了解pbuf结构的过程中,发现pbuf是已经定义好的静态数组,数量为4。而邮箱可以容纳8个邮件。
上面就有可能出现这一情况:有8个msg装进邮箱,前四个msg14分别打包的是pbuf14,而后四个msg58也是打包的pbuf14。接收邮件端在处理并前四个msg14并释放pbuf14后,再处理msg5时(对应pbuf1),发现此时ref为零(因为pbuf1前面已经被释放)。因而报错。
所以解决的方法是:把邮箱的大小改到3(比pbuf静态数组小,即小于4)。因为等于4的时候依然有可能报错:pbuf1被释放后,又被封装成msg1发送到邮箱。当处理端处理msg1时(对应pbuf1),发现此时ref为零(因为pbuf1前面已经被释放)。因而报错。如图所示: