天天看點

記錄一次kernel記憶體洩漏的查找定位過程

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/voidreturn/article/details/72357242

Bug描述:壓力測試一個小工程時發現記憶體逐漸減少,10個小時後出現OOM

Bug定位過程:

  • 對整個工程子產品進行分解,逐漸縮小範圍,由于整個工程包括幾個相對獨立的小子產品,而整個工程采用單程序多線程的模型,導緻進行分解時,要特别注意互相之間的耦合,隻能逐漸分離各個子產品,運作測試(這裡如果采用多程序模型,定位會更快一些,一個完整的功能,放在一個程序和多程序中,多程序天然的将功能細化了,定位問題,範圍更小)
  • 在經過一段折磨人的拆分過程後,最後把問題定位到整個工程中一個小子產品功能内。在對該子產品進行了反複的代碼review後,沒有發現什麼異常,甚至沒有記憶體申請的操作。
  • 代碼層面沒有找到突破的情況下,重新通過各種指令檢視了記憶體狀态,由于在此之前一直通過free指令檢視記憶體,發現長時間後free指令輸出的可用記憶體在逐漸減少,但忽略了一點:通過top指令單獨檢視子產品程序占用的記憶體時,該程序的rss段一直保持穩定,沒有大幅度增長。
  • 基于前一步的發現,懷疑是kernel的記憶體有洩漏,檢視/proc/meminfo發現一個疑點:slab記憶體占用很高,且SUnreclaim的slab一直在增加,此時基本确定kernel記憶體洩漏。
  • 通過kmemleak對核心記憶體進行了分析,定位在到一個函數接口中:
char *wr_pr_debug_begin(u8 const *data, u32 len, char *string)
{
    int ii;
    string = kmalloc(len * 2 + 1, GFP_KERNEL);
    for (ii = 0; ii < len; ii++)
        sprintf(&string[ii * 2], "%02X", data[ii]);
    string[len * 2] = 0;
    return string;
}
char *wr_pr_debug_end(char *string)
{
    kfree(string);
    return "";
}
void test()
{
    char *read = 0;
    pr_debug("%s RD%02X%02X%02X -> %s%s\n", st->hw->name,
         i2c_addr, reg, length,
         wr_pr_debug_begin(data, length, read),
         wr_pr_debug_end(read));
}           

一眼可能不容易看出上面的有什麼問題,有kmalloc,有kfree啊,好像成對出現的。

考驗基本功的時候到了,熟悉函數調用傳參的人應該會知道編譯器一般對參數的處理采用堆棧的方式,是一個先進後出的過程,這樣參數的執行一般是逆序的(由于編譯器實作的不同,這個過程不是确定的),這樣kfree會在kmalloc之前運作,導緻每次運作都會洩漏一點記憶體。上面是一個debug輸出,暫時注釋掉後壓測,問題解決,記憶體保持穩定。

總結:整個定位過程其實比較簡單,如果第一步看下/proc/meminfo可能會更快的定位問題(由于這個kernel driver是“大廠”提供,以為不會出問題,一直從上層的角度去找問題,是以沒有太關注kernel相關記憶體的使用),導緻記憶體洩漏的原因也很簡單,出現這種問題的原因,首先編寫者的基本功一般,更主要的原因是編寫者出于“炫技”的方式去寫了這段代碼,如果老老實實封裝一個debug函數,按照正常順序調用也就沒有問題了,而且這種每次列印進行kmalloc的方式,對性能也是有些影響的。總之基本功還是很重要,而且不要駕馭自己駕馭不了的編碼方式。

繼續閱讀