如何自動檢查內存泄漏和句柄耗盡

如何自動檢查內存泄漏和句柄耗盡

1. 背景

當程序的子模塊數量和規模擴大以後,在開發階段,系統長時間容許後常常會碰到下面一些bug:程序員

  • 內存泄漏。隨着時間容許,系統可用的內存愈來愈少,最後kernel 出現oom 錯誤;
  • 文件句柄耗盡。程序能夠打開的文件、套接字、管道愈來愈少,最後出錯在用完了最後一個可用句柄的代碼附近;
  • 死鎖。線程擁有一把鎖A,正在申請鎖B;但在此時鎖B被另一個線程擁有,且那個線程又在申請鎖A。造成一個循環等待、佔用且不可釋放的狀態。

調試這些問題,固然能夠從代碼流程和邏輯出發,結合ps/gdb/proc/core等命令和信息,一步步挖出root cause。但通常要求對代碼、線程關係和相關命令比較熟悉,通常耗時較長。因此,通常大型公司都封裝了標準的glibc,作了一個wraaper,而後再wrapper裏面加入了對上面調試的支持。還有的可採用專業內存泄漏等檢查工具,去作代碼檢測。那麼,對於咱工程師而言,可否能本身設計並實現一個資源檢查工具呢?數據結構

2. 原理

針對上面的三個例子使用中的資源,咱們能夠概括成兩類:數量有限的共享資源,好比上面空間有限的內存和數量有限的文件句柄;須要獨佔的互斥資源,好比上面例子中提到的鎖。 下面就分別針對這兩種狀況,分別展開分析。app

2.1 對共享資管的檢查

共享資源的特色是:總量有限,經過申請接口得到,使用完了以後經過釋放接口歸還。爲了保證不浪費資源,這就要求程序在使用完了申請獲得的資源以後,必須及時釋放。而共享資源出現問題的狀況,大部分是因爲程序員遺忘沒有釋放形成的。所以,須要一種內部機制可以記錄哪些資源使用了尚未釋放,能夠經過下面的步驟實現:ide

  • 預備一張表,初始化爲空;
  • 分配的時候,把剛分配的資源的地址等信息記錄到表中去;
  • 釋放的時候,把將釋放的資源的地址對應的信息從表中移除;
  • 檢查的時候:表爲空就代表申請的資源都已釋放,不然還有資源沒被釋放;

2.2 對獨享資管的檢查

獨享資源的特色是:互斥使用,基本上是先到先得,經過標誌設置是否以備佔有。爲了保證不死鎖,這就要求:程序在申請某個互斥資源的時候,須要檢查它已經擁有的資源,是否被它正在申請的互斥資源的擁有者申請。
若是是,會死鎖;不然,不會死鎖。一樣也能夠經過下面的步驟實現:函數

  • 預備一張映射表,初始化爲空:它描述一個用戶擁有哪些互斥資源,同時根據互斥資源可以索引到它的擁有者;
  • 分配檢查的時候:檢查當前用戶已經擁有的資源,是否被它正在申請的互斥資源的擁有者申請。同時讓這個互斥資源可以索引到當前正在申請它的用戶;
  • 執行分配的時候:把剛分配的互斥資源的地址添加到當前用戶擁有的互斥資源的列表中去,同時讓這個互斥資源可以索引到當前擁有它的用戶。
  • 釋放的時候,把將釋放的資源的地址從互斥資源列表移除,斷開這個互斥資源和當前用戶的索引關係。
  • 多個用戶死鎖時的分析:

3. 實現

根據上面原理的分析,咱們不難結合以前講過的xlink、程序堆棧等技巧,選用合適的數據結構來實現。工具

3.1 共享資源檢查的實現

根據2.1中的分析,須要先構造一張表來記錄這些資源的地址,這張表要求插入方便,刪去也迅速。爲此,咱們能夠用基於平衡二叉樹、優先級隊列或者hash的方法去實現這個表。對這個表的操做包括PQInsert()/PQRemove()/PQEmpty()等。 此後,就能夠開始參考下面列出的針對共享資源泄漏檢查的步驟去實現了。線程

3.1.1 聲明支持資源泄漏檢查的wrapper函數

能夠基於標準的open/close/malloc/free等直接申請、是否公共資源的函數,去實現wrapper。
下面以open()、close()爲例,僞碼示例以下:
int wrapper_open(char * dev);
int wrapper_free(int fd);設計

3.1.2 實現支持資源泄漏檢查的wrapper函數

仍是下面以open()、close()爲例:調試

#define FILE ('f'<<24|'i<<16|'l'<<8|'e')

int wrapper_open(char * dev)
{
    int fd = real_open(dev);
    PQInsert(FILE, fd);
    return fd;
}

int wrapper_close(int fd)
{
    int ret = 0;
    PQRemove(FILE, fd);
    ret = real_close(fd);
    return ret; 
}

3.1.3 調用支持資源泄漏檢查的wrapper函數

有兩種方式可使用支持資源泄漏檢查的wrapper函數,一種是代碼中之間調用open/close等函數對應的wrapper函數,另一種是藉助gcc Xlink 的支持讓標準的open/close函數「重定向」到wrapper_open/wrapper_close函數。顯然,後面一種方法工做量最小、最優雅。具體的實現,能夠參考前面關於Xlink的博文,下面列出了主要的幾個步驟:code

  • gcc編譯的flag中加入Xlinker改動

    Xlinker --wrap=open -Xlinker --undefined=wrapper_open Xlinker --wrap=close -Xlinker --undefined=wrapper_close
  • 新的頭文件中加入聲明

    typeof(open)         wrapper_open;
    typeof(close)        wrapper_close;
    typeof(open)         real_open;
    typeof(close)        real_close;

3.1.4 檢查資源是否泄漏的函數

一般,在程序快要結束退出的時候,會釋放資源,末了能夠經過共享資源泄漏檢查函數去檢查是否正的有資源泄漏。這個函數的主要實現的示例以下:

int FileRes_check(void)
{
    if PQEmpty(FILE) {
        return PASS;
    } esle {
        PQDump(FILE);
        return FAIL;
    }
}

3.1.5 記錄可能申請了被泄漏的資源代碼的位置

根據上一篇博文中,關於如何獲得程序堆棧中介紹的方法,咱們能夠在申請共享資源的時候,具體來講就是調用PQInsert(FILE, fd)時,取得當前程序的stack, 把堆棧信息和fd一塊兒做爲一項紀錄插入到基於優先級隊列、散列或者平衡二叉樹實現的表中去。 一樣,PQDump()除了打印沒有關閉的文件句柄以外,還輸出這個句柄對應打開時候的程序堆棧,根據這個堆棧信息,程序員可以定位到打開了這個沒被關閉的句柄的代碼的位置。而一般,這個句柄的關閉應該在它以後附近。

3.2 獨享資源檢查的實現

對獨享資源死鎖的檢查的具體實現依賴的技術同上,差異就在死鎖檢查的邏輯和流程。讀者能夠自行嘗試,等有時間了我也能夠再來完善。

4. 總結

經過上面的這麼多介紹能夠看到,基於對共享資源和互斥資源使用特色的分析,咱們可以提出一種針對共享資源泄漏和獨享資源死鎖檢查的通用原理。藉助於合適的數據結構(二叉樹、優先級隊列或散列),基於 Xlinker方法、堆棧獲取的API,咱們可以實現一種輕巧的、幾乎不用改動已有代碼的對開發者很是友好的資源檢查功能。

相關文章
相關標籤/搜索