打算优化系统的内存分配,接管 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 myprog myprog.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 分配的内存复杂了很多。
方案 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 函数的文件链接时使用 --wrap malloc ,则所有到 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 MALLOC called: 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"。