我想大多数人想了解一个库的源代码肯怕第一件事就是急切寻找函数的入口,我也是。对于如此神奇的代码,我已经迫不及待想一窥它的究竟,没有耐心在网上搜索,在代码里寻找了,我选择了最简单有效的办法--调试。
Tcmalloc的函数入口在tcmalloc.cc中定义,malloc和free的入口函数代码如下:
// CAVEAT: The code structure below ensures that MallocHook methods are always
// called from the stack frame of the invoked allocation function.
// heap-checker.cc depends on this to start a stack trace from
// the call to the (de)allocation function.
extern "C" PERFTOOLS_DLL_DECL void* tc_malloc(size_t size) __THROW {
void* result = do_malloc_or_cpp_alloc(size);
MallocHook::InvokeNewHook(result, size);
return result;
}
extern "C" PERFTOOLS_DLL_DECL void tc_free(void* ptr) __THROW {
MallocHook::InvokeDeleteHook(ptr);
do_free(ptr);
}
// The default "do_free" that uses the default callback.
inline void do_free(void* ptr) {
return do_free_with_callback(ptr, &InvalidFree);
}
1) 测试程序只是简单的链接了下tcmalloc的库,调用malloc时咋就跑这来了?
我想大概是修改glibc的malloc函数入口地址。但是目前这个不是我关心的重点,目前我关注的重点是内存是怎么分配的,为何这么高效。
tc_malloc函数代码非常简单,只有两行。不难理解,第一行实现内存的分配。第二行是干嘛的呢?从注释里可以看出大概是用来跟踪调试的,这个同样不是我目前关心的重点。
继续看do_malloc_or_cpp_alloc代码,代码如下:
// TODO(willchan): Investigate whether or not lining this much is harmful to
// performance.
// This is equivalent to do_malloc() except when tc_new_mode is set to true.
// Otherwise, it will run the std::new_handler if set.
inline void* do_malloc_or_cpp_alloc(size_t size) {
return tc_new_mode ? cpp_alloc(size, true) : do_malloc(size);
}
看来该函数也只是一个路由函数,根据tc_new_mode来选择不同的分配函数。这个tc_new_mode到底是啥东东,作何用处?
在tcmalloc文件中搜索tc_new_mode,发现它的设置函数:
// This function behaves similarly to MSVC's _set_new_mode.
// If flag is 0 (default), calls to malloc will behave normally.
// If flag is 1, calls to malloc will behave like calls to new,
// and the std_new_handler will be invoked on failure.
// Returns the previous mode.
extern "C" PERFTOOLS_DLL_DECL int tc_set_new_mode(int flag) __THROW {
int old_mode = tc_new_mode;
tc_new_mode = flag;
return old_mode;
}
从注释中不难看出,原来通过设置该值来决定是使用普通的malloc模式还是使用C++里面new的模式,如果该值被设置为1,当内存分配失败时std_new_handler就会被调用,从而向应用程序抛出异常。
真正实现内存分配的函数就是do_malloc,代码如下:
inline void* do_malloc(size_t size) {
void* ret = NULL;
// The following call forces module initialization
ThreadCache* heap = ThreadCache::GetCache();
if (size <= kMaxSize) {
size_t cl = Static::sizemap()->SizeClass(size);
size = Static::sizemap()->class_to_size(cl);
if ((FLAGS_tcmalloc_sample_parameter > 0) && heap->SampleAllocation(size)) {
ret = DoSampledAllocation(size);
} else {
// The common case, and also the simplest. This just pops the
// size-appropriate freelist, after replenishing it if it's empty.
ret = CheckedMallocResult(heap->Allocate(size, cl));
}
} else {
ret = do_malloc_pages(heap, size);
}
if (ret == NULL) errno = ENOMEM;
return ret;
}
下一篇分析TCMalloc如何将size规整对齐。