在應用程式中替換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)