本文主要講用戶態(tài)進程的內(nèi)存管理,而不是內(nèi)核的內(nèi)存管理。簡單地說,就是和 malloc 和 free 相關的內(nèi)存管理。
簡介
Linux 環(huán)境下,進程的內(nèi)存管理器默認是使用 glibc 實現(xiàn)的 ptmalloc 。另外,還有兩個比較有名的內(nèi)存管理器:google 的 tcmalloc 和
fackbook 的 jemalloc 。總體來說, tcmalloc 和 jemalloc 在多核多線程的場景下,性能要優(yōu)于 ptmalloc 。
HOOK 機制
我們先簡單了解一下, malloc 和 free 如何調(diào)用到我們自定義的函數(shù)。
在 Linux 下,內(nèi)存管理器一般通過 HOOK 來實現(xiàn)自定義的malloc函數(shù),具體就是通過覆蓋__malloc_hook等函數(shù)指針來實現(xiàn)。glibc 提供了__malloc_hook
、__realloc_hook
、__free_hook
、__memalign_hook
四個全局函數(shù)hook指針。簡單地說,就是 malloc 調(diào)用的是 __malloc_hook 指針指向的函數(shù),所以 jemalloc 或者 tcmalloc 通過覆蓋 __malloc_hook 使程序調(diào)用到它們自定義的malloc。
接下來,我們做個小實驗,覆蓋掉__malloc_hook和__malloc_free。
#include <malloc.h>
// 兩個函數(shù)聲明
static void *my_malloc_hook (size_t, const void *);
static void my_free_hook (void*, const void *);
// 兩個全局變量
void* (*old_malloc_hook) (size_t size, const void *caller);
void (*old_free_hook) (void *ptr, const void *caller);
static void my_init (void)
{
old_malloc_hook = __malloc_hook;
old_free_hook = __free_hook;
__malloc_hook = my_malloc_hook;
__free_hook = my_free_hook;
}
static void* my_malloc_hook (size_t size, const void *caller)
{
void *result;
__malloc_hook = old_malloc_hook;
__free_hook = old_free_hook;
result = malloc (size);
old_malloc_hook = __malloc_hook;
old_free_hook = __free_hook;
printf ("malloc (%u) returns %p\n", (unsigned int) size, result);
__malloc_hook = my_malloc_hook;
__free_hook = my_free_hook;
return result;
}
static void my_free_hook (void *ptr, const void *caller)
{
__malloc_hook = old_malloc_hook;
__free_hook = old_free_hook;
free (ptr);
old_malloc_hook = __malloc_hook;
old_free_hook = __free_hook;
printf ("freed pointer %p\n", ptr);
__malloc_hook = my_malloc_hook;
__free_hook = my_free_hook;
}
int main ()
{
my_init ();
void* p = malloc(1);
free(p);
}
輸出:
$ ./mem_hook
malloc (1) returns 0x1ad2010
freed pointer 0x1ad2010
不過編譯的時候編譯器告警說這些hook指針已經(jīng)廢棄了(還是可以使用)。這里描述了其它調(diào)用自定義malloc函數(shù)的方法,有興趣的話可以嘗試一下。
jemalloc 的 HOOK 代碼 (jemalloc.c),不止覆蓋了 *_hook 函數(shù)指針,還有與
__lib_* 系列函數(shù)綁定匿名關系(這應該也是一種覆蓋默認函數(shù)的方式)。
#if defined(JEMALLOC_IS_MALLOC) && defined(JEMALLOC_GLIBC_MALLOC_HOOK)
/*
* glibc provides the RTLD_DEEPBIND flag for dlopen which can make it possible
* to inconsistently reference libc's malloc(3)-compatible functions
* (https://bugzilla.mozilla.org/show_bug.cgi?id=493541).
*
* These definitions interpose hooks in glibc. The functions are actually
* passed an extra argument for the caller return address, which will be
* ignored.
*/
JEMALLOC_EXPORT void (*__free_hook)(void *ptr) = je_free;
JEMALLOC_EXPORT void *(*__malloc_hook)(size_t size) = je_malloc;
JEMALLOC_EXPORT void *(*__realloc_hook)(void *ptr, size_t size) = je_realloc;
# ifdef JEMALLOC_GLIBC_MEMALIGN_HOOK
JEMALLOC_EXPORT void *(*__memalign_hook)(size_t alignment, size_t size) =
je_memalign;
# endif
# ifdef CPU_COUNT
/*
* To enable static linking with glibc, the libc specific malloc interface must
* be implemented also, so none of glibc's malloc.o functions are added to the
* link.
*/
# define ALIAS(je_fn) __attribute__((alias (#je_fn), used))
/* To force macro expansion of je_ prefix before stringification. */
# define PREALIAS(je_fn) ALIAS(je_fn)
# ifdef JEMALLOC_OVERRIDE___LIBC_CALLOC
void *__libc_calloc(size_t n, size_t size) PREALIAS(je_calloc);
# endif
# ifdef JEMALLOC_OVERRIDE___LIBC_FREE
void __libc_free(void* ptr) PREALIAS(je_free);
# endif
# ifdef JEMALLOC_OVERRIDE___LIBC_MALLOC
void *__libc_malloc(size_t size) PREALIAS(je_malloc);
# endif
# ifdef JEMALLOC_OVERRIDE___LIBC_MEMALIGN
void *__libc_memalign(size_t align, size_t s) PREALIAS(je_memalign);
# endif
# ifdef JEMALLOC_OVERRIDE___LIBC_REALLOC
void *__libc_realloc(void* ptr, size_t size) PREALIAS(je_realloc);
# endif
# ifdef JEMALLOC_OVERRIDE___LIBC_VALLOC
void *__libc_valloc(size_t size) PREALIAS(je_valloc);
# endif
# ifdef JEMALLOC_OVERRIDE___POSIX_MEMALIGN
int __posix_memalign(void** r, size_t a, size_t s) PREALIAS(je_posix_memalign);
# endif
# undef PREALIAS
# undef ALIAS
# endif
#endif
jemalloc
后面,我們以 jemalloc 為例子對進程的內(nèi)存管理器進行簡單的學習。
- 從 github 下載 jemalloc 的代碼。
git clone https://github.com/jemalloc/jemalloc.git
- 編譯安裝 jemalloc。
$ ./autogen.sh
$ ./configure --enable-debug
$ make
$ sudo make install_bin install_include install_lib
$ sudo ldconfig # 刷新動態(tài)庫路徑信息
- 例子
#include <malloc.h>
void func()
{
void* p = malloc(1);
free(p);
}
int main(int argc, char ** argv)
{
func();
}
編譯鏈接到 jemalloc
gcc -g -o mem_test mem_test.c -ljemalloc # 鏈接jemalloc
查看鏈接到的動態(tài)庫
$ ldd mem_test
linux-vdso.so.1 => (0x00007ffedc246000)
libjemalloc.so.2 => /usr/local/lib/libjemalloc.so.2 (0x00007fb32f629000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb32f25f000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fb32ef56000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fb32ebd4000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fb32e9b7000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fb32e7b3000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fb32e59d000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb32faed000)
加入編譯命令沒有指定 jemalloc,默認鏈接到 ptmalloc
gcc -g -o mem_test mem_test.c # 默認鏈接ptmalloc
則鏈接到的動態(tài)庫為:
$ ldd mem_test
linux-vdso.so.1 => (0x00007ffdb0b09000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f39e832c000)
/lib64/ld-linux-x86-64.so.2 (0x00007f39e86f6000)