第一次使用lwip,调试的时候故意将信号量以及内存给小,便于发现可能存在的泄漏,通过测试发现了两种泄漏情况,一个是接收数据包(应用层不处理接收,相当于只发送)存在溢出风险,一个是新建连接的时候,消息邮箱存在溢出,这2个溢出查了很久,中间的过程我就不一一道明了,只讲结果,就是sys_mbox_valid()接口的问题,这个接口是用于检查一个消息邮箱是否有效,通过这次调试也算是把lwip机制弄清楚了,一切都是围绕着这个消息邮箱的,在数据接收的时候,会申请内存,将内存指针写入到消息邮箱队列,lwip核心线程会读取队列,处理数据,当你只发送不接收,或者接收数据量太大,不足以处理的时候,这个时候消息队列是可能存在满了的,而这个sys_mbox_valid()接口很关键,他只有2个状态,有效或者无效,当邮箱满了的时候,必须返回无效,让lwip知道现在不能再写入消息了,这个时候lwip会将收到的数据释放掉,不会再写入到消息邮箱中了,流程如下:
首先是,完成了从网卡接收数据包,申请内存pbuf,写入到连接所在的邮箱队列
if (isLwIP_Init == TRUE) //LWIP初始化完成了才进行数据接收处理
{
p = pbuf_alloc(PBUF_RAW, RxLen, PBUF_POOL); //从内存池分配内存
if (p != NULL)
{
memcpy((u8_t*)((u8_t*)p->payload), pData, RxLen);
p->tot_len = p->len = RxLen; //重新设置接收数据长度
ethernetif_input(p, &xnetif); //调用lwip数据接收处理
}
else
{
DEBUG("内存不足\r\n");
}
}
如果数据被正常的应用层接收读取后,就会被释放(这只是个接收数据的例子,但是数据pbuf肯定要释放掉)
//接收数据回调,注意:接收数据必须存放在RTU_FRAME_BUFF中
int lwip_ReadData(u8** pDataBuff, u8 ByteTimeOut, u16 TimeOutMs, u16* pReceiveDelay)
{
err_t err;
struct netbuf* buf;
void* data;
u16_t len;
if (pReceiveDelay != NULL) *pReceiveDelay = 0;
err = netconn_recv(sg_conn, &buf); //阻塞等待接收数据
if (err == ERR_OK) //接收成功
{
if ((err = netbuf_data(buf, &data, &len)) == ERR_OK)
{
memcpy(RTU_FRAME_BUFF, (u8*)data, len);
netbuf_delete(buf);
*pDataBuff = RTU_FRAME_BUFF;
return len;
}
else
{
printf("err:netbuf_data(buf, &data, &len):%d.\r\n", err);
return 0;
}
}
else if (ERR_TIMEOUT == err) //接收超时了
{
return 0;
}
else
{
printf("连接出现了错误:%d.\r\n", err);
return -1;
}
}
正常是不存在什么问题的,但是一旦接收数据量大了,或者你不处理接收的数据,这个时候消息邮箱满了,sys_mbox_valid()函数就会返回无效(不能返回有效,返回有效会导致邮箱溢出,内存泄漏更严重),这个时候接收到的数据,在lwip的api_msg.c文件的 static err_t recv_tcp(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) 函数内,如果有效无效,就释放掉了;
if (!NETCONN_MBOX_VALID(conn, &conn->recvmbox)) {
/* recvmbox already deleted */
if (p != NULL) {
tcp_recved(pcb, p->tot_len);
pbuf_free(p);
}
return ERR_OK;
}
所以你的邮箱满了,必须返回无效,如果返回有效,就会将带有pbuf指针的消息写入进去,而实际上并未写入成功,因此***也读取不出来,也不可能被释放,内存就这么泄漏掉了,随着运行时间的增加,内存泄漏会变多,但是实际上由于sys_mbox_valid()的返回无效,就避免了内存的泄漏;
但是也正是因为 sys_mbox_valid()在消息队列满了后,返回无效,也带来了两个方面的泄漏,1是没有取出正处于消息邮箱队列中的数据,这些数据都是申请额内存,会随着netconn_delete(sg_conn);调用而泄漏,还有一个就是你的conn创建的时候一起创建的消息邮箱,也会随着netconn_delete(sg_conn);调用而一起泄漏,原因就是netconn_delete(sg_conn);会检查消息邮箱是否有效,如果有效,就会一一读取出当前消息邮箱队列中的数据,并将内存一一释放,最后将消息邮箱删除,释放内存,但是如果你的消息邮箱当前处于满状态, sys_mbox_valid()返回无效,就会导致netconn_delete(sg_conn);不再释放内存,也不会删除掉邮箱,这个时候就造成了泄漏;
netconn_delete()函数调用的netconn_prepare_delete(),这个函数调用的netconn_apimsg(),里面又调用err = tcpip_send_msg_wait_sem(fn, apimsg, LWIP_API_MSG_SEM(apimsg));是个回调函数里面进行释放内存的,通过仿真可以找到位置,在api_msg.c的netconn_drain()函数里面完成的;
/* Delete and drain the recvmbox. */
if (sys_mbox_valid(&conn->recvmbox)) {
while (sys_mbox_tryfetch(&conn->recvmbox, &mem) != SYS_MBOX_EMPTY) {
#if LWIP_NETCONN_FULLDUPLEX
if (!lwip_netconn_is_deallocated_msg(mem))
#endif /* LWIP_NETCONN_FULLDUPLEX */
{
#if LWIP_TCP
if (NETCONNTYPE_GROUP(conn->type) == NETCONN_TCP) {
err_t err;
if (!lwip_netconn_is_err_msg(mem, &err)) {
pbuf_free((struct pbuf *)mem);
}
} else
#endif /* LWIP_TCP */
{
netbuf_delete((struct netbuf *)mem);
}
}
}
sys_mbox_free(&conn->recvmbox);
sys_mbox_set_invalid(&conn->recvmbox);
}
上面就是核心了,先判断邮箱是否有效
if (sys_mbox_valid(&conn->recvmbox))
由于你的邮箱当前处于满状态,所以返回的无效,那么不好意思,你的邮箱内的数据我不会管了,你的这个邮箱我都不会删除了;
目前我的解决办法是,在竟可能不改动lwip代码的情况下,自定义的消息邮箱结构体中增加一个标记,如下:
//LWIP消息邮箱结构体
typedef struct
{
OS_EVENT* pQ; //UCOS中指向事件控制块的指针
void* pvQEntries[LWIP_ONE_QUEUE_SIZE];//消息队列 MAX_QUEUE_ENTRIES消息队列中最多消息数
bool isValid; //自定义一个标志位,用于连接关闭后设置为无效,以便当邮箱溢出时依旧可以告诉netconn_delete()邮箱有效,好让netconn_delete()释放内存,删除邮箱
}lwip_mbox;
这个 isValid就是我自定义添加的,当消息邮箱被创建的时候,将isValid初始化为TRUE;
//创建一个消息邮箱
//*mbox:消息邮箱
//size:邮箱大小
//返回值:ERR_OK,创建成功
// 其他,创建失败
err_t sys_mbox_new(sys_mbox_t* mbox, int size)
{
static u32 cnt = 0;
//uart_printf("OSQCreate:%d\r\n", cnt++);
if (size > LWIP_ONE_QUEUE_SIZE)size = LWIP_ONE_QUEUE_SIZE; //消息队列最多容纳LWIP_MAX_QUEUES_COUNT消息数目
mbox->pQ = OSQCreate(&(mbox->pvQEntries[0]), size); //使用UCOS创建一个消息队列
uart_printf("OSQCreate:0x%X %d\r\n",(u32)mbox->pQ, cnt++);
LWIP_ASSERT("OSQCreate", mbox->pQ != NULL);
if (mbox->pQ != NULL)
{
mbox->isValid = TRUE; //有效状态
return ERR_OK; //返回ERR_OK,表示消息队列创建成功 ERR_OK=0
}
else
{
mbox->isValid = FALSE; //无效状态
return ERR_MEM; //消息队列创建错误
}
}
然后在netconn_delete()函数中,增加一行代码 conn->recvmbox.isValid = FALSE; //消息邮箱不再被使用了
/**
* @ingroup netconn_common
* Close a netconn 'connection' and free its resources.
* UDP and RAW connection are completely closed, TCP pcbs might still be in a waitstate
* after this returns.
*
* @param conn the netconn to delete
* @return ERR_OK if the connection was deleted
*/
err_t
netconn_delete(struct netconn *conn)
{
err_t err;
/* No ASSERT here because possible to get a (conn == NULL) if we got an accept error */
if (conn == NULL) {
return ERR_OK;
}
//2021-08-13 增加一个状态标记
conn->recvmbox.isValid = FALSE; //消息邮箱不再被使用了
#if LWIP_NETCONN_FULLDUPLEX
if (conn->flags & NETCONN_FLAG_MBOXINVALID) {
/* Already called netconn_prepare_delete() before */
err = ERR_OK;
} else
#endif /* LWIP_NETCONN_FULLDUPLEX */
{
err = netconn_prepare_delete(conn);
}
if (err == ERR_OK) {
netconn_free(conn);
}
return err;
}
然后修改sys_mbox_valid()函数,一旦消息邮箱是有效的,但是isValid==FALSE就直接返回有效,因为这个时候意味着连接已经被删除了,需要释放内存了,让netconn_delete()能知道邮箱还是有效的,将邮箱里面没有处理的数据全部读取出来,并进行释放,同时删除掉邮箱;
//检查一个消息邮箱是否有效
//*mbox:消息邮箱
//返回值:1,有效.
// 0,无效
int sys_mbox_valid(sys_mbox_t* mbox)
{
sys_mbox_t* m_box = mbox;
u8_t ucErr;
int ret;
OS_Q_DATA q_data;
if (m_box==NULL || m_box->pQ == NULL)
{
//DEBUG("OSQQuery:异常\r\n");
return 0; //消息队列无效了
}
memset(&q_data, 0, sizeof(OS_Q_DATA));
ucErr = OSQQuery(m_box->pQ, &q_data);
if (ucErr == OS_ERR_NONE && mbox->isValid == FALSE)
{
printf("邮箱要被删除了,直接返回1!\r\n");
return 1; //邮箱需要被删除了,告诉lwip邮箱还是有效的
}
// uart_printf("消息数量:%d\r\n", q_data.OSNMsgs);
ret = (ucErr < 2 && (q_data.OSNMsgs < q_data.OSQSize)) ? 1 : 0;
if (q_data.OSNMsgs >= LWIP_ONE_QUEUE_SIZE)//超过队列大小了
{
printf("LWIP_ONE_QUEUE_SIZE(%d) full !please increase!!\r\n", q_data.OSNMsgs);//提示需要增大LWIP_ONE_QUEUE_SIZE.
}
return ret;
}
如果sys_mbox_valid()能返回3个状态,有效(可写),无效(不可写),满(不能写)三个状态,就不存在这个问题了,当然这个只要你的邮箱不满(个很大),也不存在这个问题了;
————————————————
版权声明:本文为CSDN博主「cp1300」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cp1300/article/details/119726408
|
|