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
總結
繞了一個大彎,後面才意識到原來是心跳檢測機制用法不對,導致網絡堆內存釋放時間長。