LWIP memory leak: solved

LWIP內存泄露問題

最近在項目中遇到了使用LWIP 1.4.1協議棧內存泄露的問題。表現爲使用socket進行通信過程中,有時fd 資源已釋放的情況下,網絡堆內存依然沒被釋放。經過長時間的積累,導致無法申請網絡堆內存。
這種情況在網絡物理連接斷開的情況下特別容易出現,比如插拔網線。

問題分析

  1. TCP三握手四揮手 。是否因爲close fd 時TimeWait時間較長,導致網絡堆內存釋放慢。
    在代碼中沒有搜索到與TimeWait相關的參數,因此只能想其他辦法。
  2. LWIP 釋放內存出Bug
    通過內存檢測工具定位內存泄露的位置。排查原因。

解決問題過程

  1. 通過對問題分析中提到的第二點進行了調試,在網上找到一個好用的內存泄露檢測工具memleak (非常適合嵌入式C平臺), 進行內存調試。

    memleak 下載地址
    sourceforge下載源碼地址:http://sourceforge.net/projects/memleak/

    通過定位,發現是在tcp 發送相關的lwip函數調用中沒有釋放內存。還發現通過get_socket函數找到fd並調用tcp_abort函數主動釋放堆內存能解決問題。

    但這個方法非常不安全,因爲破壞了LWIP內部機制。

  2. 在無意中注意到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

總結

繞了一個大彎,後面才意識到原來是心跳檢測機制用法不對,導致網絡堆內存釋放時間長。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章