LWIP memory leak: solved
LWIP内存泄露问题
最近在项目中遇到了使用LWIP 1.4.1协议栈内存泄露的问题。表现为使用socket进行通信过程中,有时fd 资源已释放的情况下,网络堆内存依然没被释放。经过长时间的积累,导致无法申请网络堆内存。
这种情况在网络物理连接断开的情况下特别容易出现,比如插拔网线。
问题分析
- TCP三握手四挥手 。是否因为close fd 时TimeWait时间较长,导致网络堆内存释放慢。
在代码中没有搜索到与TimeWait相关的参数,因此只能想其他办法。 - LWIP 释放内存出Bug 。
通过内存检测工具定位内存泄露的位置。排查原因。
解决问题过程
-
通过对问题分析中提到的第二点进行了调试,在网上找到一个好用的内存泄露检测工具memleak (非常适合嵌入式C平台), 进行内存调试。
memleak 下载地址
sourceforge下载源码地址:http://sourceforge.net/projects/memleak/通过定位,发现是在tcp 发送相关的lwip函数调用中没有释放内存。还发现通过get_socket函数找到fd并调用tcp_abort函数主动释放堆内存能解决问题。
但这个方法非常不安全,因为破坏了LWIP内部机制。
-
在无意中注意到lwipopts.h中配置文件关于KeepAlive的心跳检测机制的参数。这个正确设置好了就能解决LWIP内存泄露的问题。
#define TCP_KEEPIDLE_DEFAULT 10000UL
#define TCP_KEEPINTVL_DEFAULT 1000UL
#define TCP_KEEPCNT_DEFAULT 10U
搜索代码发现这个参数默认值是上述宏配置,可以通过setsockopt函数配置针对心跳包时间进行修改。
之前工程中有设置这个选项,但不起作用。现在深入看了LWIP源码,才发现里面涉及到单位转换问题。
如果单位搞错了,就不起作用了。
在sockets.h文件中,看到了与时间单位相关的参数。
/*
* Options for level IPPROTO_TCP
*/
#define TCP_NODELAY 0x01 /* don't delay send to coalesce packets */
#define TCP_KEEPALIVE 0x02 /* send KEEPALIVE probes when idle for pcb->keep_idle milliseconds */
#define TCP_KEEPIDLE 0x03 /* set pcb->keep_idle - Same as TCP_KEEPALIVE, but use seconds for get/setsockopt */
#define TCP_KEEPINTVL 0x04 /* set pcb->keep_intvl - Use seconds for get/setsockopt */
#define TCP_KEEPCNT 0x05 /* set pcb->keep_cnt - Use number of probes sent for get/setsockopt */
#endif /* LWIP_TCP */
所以只有正确的设置setsockopt, 心跳包才起作用。下面是我封装的一个设置心跳包参数的函数。
int TCPSetKeepAlive( int sockfd, int keepAlive, int keepIdle, int keepInterval, int keepCount ){
int tvKeepAlive = keepAlive;
int tvKeepIdle = keepIdle/1000;
int tvKeepInterval = keepInterval/1000;
int tvKeepCount = keepCount;
if( setsockopt( sockfd, SOL_SOCKET, SO_KEEPALIVE, ( void * )&tvKeepAlive, sizeof( int ) ) < 0 )
DFULLOGE("Set setsockopt keep alive failed");
if( setsockopt( sockfd, IPPROTO_TCP, TCP_KEEPIDLE, ( void * )&tvKeepIdle, sizeof( int ) ) < 0 )
DFULLOGE("Set setsockopt keep idle failed");
if( setsockopt( sockfd, IPPROTO_TCP, TCP_KEEPINTVL, ( void * )&tvKeepInterval, sizeof( int ) ) < 0 )
DFULLOGE("Set setsockopt keep interval failed");
if( setsockopt( sockfd, IPPROTO_TCP, TCP_KEEPCNT, ( void * )&tvKeepCount, sizeof( int ) ) < 0 )
DFULLOGE("Set setsockopt keep count failed");
return 0;
}
调用方法:
//使能心跳包
TCPSetKeepAlive( tvCliSock, 1, 20*1000, 5000, 2 );
关于心跳包的介绍可以参考文章:
https://blog.csdn.net/weixin_37672169/article/details/80283935
总结
绕了一个大弯,后面才意识到原来是心跳检测机制用法不对,导致网络堆内存释放时间长。