1. 程式人生 > >在應用程式中替換Linux中Glibc的malloc的5種方法

在應用程式中替換Linux中Glibc的malloc的5種方法

打算優化系統的記憶體分配,接管glibc提供的記憶體管理,但是整個工程的程式碼量很大,使用malloc、realloc、calloc和free的地方到處都是,如果自己寫好的介面需要重新命名所有的呼叫,先不說工作量,部分沒有許可權檢視程式碼的.a檔案就搞不定了。所以需要替換掉系統的malloc,保證原有呼叫的名稱不變。經過嘗試,共有四種方法可以替換,各有優缺點吧。

方案1 使用環境變數LD_PRELOAD

         環境變數LD_PRELOAD指定程式執行時優先載入的動態連線庫,這個動態連結庫中的符號優先順序是最高的。標準C的各種函式都是存放在libc.so.6的檔案中,在程式執行時自動連結。使用LD_PRELOAD後,自己編寫的malloc的載入順序高於glibc中的malloc,這樣就實現了替換。用法:

         [littlefang]$LD_PRELOAD=" ./mymalloc.so"

         這是最實用的替換方法,動態連結庫載入過程中提供了初始化函式,可以輕易的獲得系統malloc的控制代碼,再將它做進一步的管理,Hoard(參見深入Linux的記憶體管理,關於PTMalloc3、Hoard和TCMalloc)的就是這樣實現的。

方案2  malloc除錯變數

         __malloc_hook是一組glibc提供的malloc除錯變數中的一個,這組變數包括:

void *(*__malloc_hook)(size_t size, const void *caller); void *(*__realloc_hook)(void *ptr, size_t size, const void *caller); void *(*__memalign_hook)(size_t alignment, size_t size, const void *caller); void (*__free_hook)(void *ptr, const void *caller); void (*__malloc_initialize_hook)(void); void (*__after_morecore_hook)(void); 

只要你在程式中寫上”__malloc_hook= my_malloc_hook;”,之後的malloc呼叫都會使用my_malloc_hook函式,方便易行。但是這組除錯變數不是執行緒安全的,當你想用系統malloc的時候不得不把他們改回來,多執行緒呼叫就得上鎖了。因此方案2不很適用於系統記憶體優化,勉強用來簡單管理執行緒記憶體使用。

         詳細用法請猛擊這裡

方案3 編譯自己的libmalloc.a

         關於過載glibc的malloc庫,ChinaUnix上有這樣的討論

如果我用cc -o myprogmyprog.c -lmylib,而不想修改預設的ld的命令列引數或者linker指令碼,不知可不可以?

這個方法確實比較理想,只需要make一次就OK了,不用更改環境變數,省得擔心後臺執行的問題。後面有人回覆讓樓主試試,不知道樓主試了沒有,我試了一下。

若要把系統記憶體管理起來,首先還是要向作業系統申請記憶體,這個問題對於LD_PRELOAD方案很簡單,連結庫載入時就可以把glibc中的malloc載入進來,以後直接呼叫就可以了,如:

real_malloc =dlsym(RTLD_NEXT, "malloc");

但是你如果使用自己編譯的malloc庫,在你呼叫dlsym這個函式時,dlsym會呼叫dlerror,dlerror會呼叫calloc,calloc要呼叫malloc,而你的malloc正在初始化等待dlsym返回中,於是死迴圈了。有人說,在呼叫沒有初始化完畢的malloc時,返回NULL,我試了dlsym不認賬,載入可恥的失敗了。在滿世界的尋找dlsym的替代品未果後,我把目光瞄住了tcmalloc(參見深入Linux的記憶體管理,關於PTMalloc3、Hoard和TCMalloc)。Tcmalloc使用時需要在連結時加上-ltcmalloc即可,它程式碼裡也沒使用dlsym,大略了看了下它的程式碼,它使用mmap從系統獲取的記憶體。

void *mmap(void*addr, size_t len, int prot, int flags, int fildes, off_t off);

這種方法從頁面級就要對系統記憶體進行管理,Glibc中的malloc就是使用mmap和brk兩個函式從程式堆中獲得記憶體的。無疑,這比起用malloc分配的記憶體複雜了很多。

2015-12-22更新:

這個方法對於執行時動態連結的庫中呼叫的malloc/new都可以接管,但是對於程式啟動後使用dlopen且開啟RTLD_DEEPBIND選項載入的動態連結庫,其中的malloc和new是不能替換的。

方案4 連結過程控制

ld中有一個選項 –wrap,當查詢某個符號時,它優先先解析__wrap_symbol, 解析不到才去解析symbol。例如:

void *__wrap_malloc (size_t c) { printf ("malloc called with %zu/n", c); return __real_malloc (c); } 

當其它檔案與你實現__wrap_malloc函式的檔案連結時使用--wrapmalloc ,則所有到malloc的呼叫都是會連結到__wrap_malloc上。只有呼叫__reall_malloc時才會呼叫真正的malloc。

#include <stdio.h> #include <stdlib.h> void *__real_malloc(size_t); void *__wrap_malloc(size_t c) { printf("My MALLOC called: %d/n", c); return __real_malloc(c); } int main (int argc, char *argv[]) { void *ptr = malloc(12); return 0; } 

編譯 

[littlefang]$gcc wrap.c -o wrap -Wl,-wrap,malloc

執行 

[littlefang]$./wrap

My MALLOCcalled: 12

Gcc或g++編譯使用 –Wl選項,以指定連結器引數,比如同時替換malloc,free,realloc就要用

gcc wrap.c -o wrap -Wl,-wrap,malloc  -Wl,-wrap,free  -Wl,-wrap,realloc。

特別需要注意的是,如果你的__wrap_malloc是用C++實現的,千萬不要忘記加上extern“C”做修飾,不然會出現"undefine reference to __wrap_malloc"。

 

 當然也有方法5,巨集替換malloc。
 在所有.c檔案都會包含的一個基礎base.h裡定義函式
 inline void* my_test_malloc(size_t len){
 
 printf("my test malloc");
 return malloc(len);
 }
 
 #define malloc(len) my_test_malloc(len)