1. 程式人生 > >一種對共享資源和獨享資源的檢查方法

一種對共享資源和獨享資源的檢查方法

技巧 參考 fine 數量 代碼檢測 void linker 移除 基本

一種對共享資源和獨享資源的檢查方法

1. 背景

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

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

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

2. 原理

針對上面的三個例子使用中的資源,我們可以歸納成兩類:數量有限的共享資源,比如上面空間有限的內存和數量有限的文件句柄;需要獨占的互斥資源,比如上面例子中提到的鎖。 下面就分別針對這兩種情況,分別展開分析。

2.1 對共享資管的檢查

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

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

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的博文,下面列出了主要的幾個步驟:

  • 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,我們能夠實現一種輕巧的、幾乎不用改動已有代碼的對開發者非常友好的資源檢查功能。

一種對共享資源和獨享資源的檢查方法