5.5.2 ptmalloc_init()函数
ptmalloc_init() 函数比较长,将分段对这个函数做介绍。
static void ptmalloc_init (void) { #if __STD_C const char* s; #else char* s; #endif int secure = 0; if(__malloc_initialized >= 0) return; __malloc_initialized = 0;
首先检查全局变量 __malloc_initialized 是否大于等于 0 ,如果该值大于 0 ,表示 ptmalloc 已经初始化,如果改值为 0 ,表示 ptmalloc 正在初始化,全局变量 __malloc_initialized 用来保证全局只初始化 ptmalloc 一次。
#ifdef _LIBC # if defined SHARED && !USE___THREAD /* ptmalloc_init_minimal may already have been called via __libc_malloc_pthread_startup, above. */ if (mp_.pagesize == 0) # endif #endif ptmalloc_init_minimal(); #ifndef NO_THREADS # if defined _LIBC /* We know __pthread_initialize_minimal has already been called, and that is enough. */ # define NO_STARTER # endif # ifndef NO_STARTER /* With some threads implementations, creating thread-specific data or initializing a mutex may call malloc() itself. Provide a simple starter version (realloc() won't work). */ save_malloc_hook = __malloc_hook; save_memalign_hook = __memalign_hook; save_free_hook = __free_hook; __malloc_hook = malloc_starter; __memalign_hook = memalign_starter; __free_hook = free_starter; # ifdef _LIBC /* Initialize the pthreads interface. */ if (__pthread_initialize != NULL) __pthread_initialize(); # endif /* !defined _LIBC */ # endif /* !defined NO_STARTER */ #endif /* !defined NO_THREADS */
为多线程版本的 ptmalloc 的 pthread 初始化做准备,保存当前的 hooks 函数,并把 ptmalloc 为初始化时所有使用的分配 / 释放函数赋给 hooks 函数,因为在线程初始化一些私有实例时, ptmalloc 还没有初始化,所以需要做特殊处理。从这些 hooks 函数可以看出,在 ptmalloc 未初始化时,不能使用 remalloc 函数。在相关的 hooks 函数赋值以后,执行 pthread_initilaize() 初始化 pthread 。
mutex_init(&main_arena.mutex); main_arena.next = &main_arena;
初始化主分配区的 mutex ,并将主分配区的 next 指针指向自身组成环形链表。
#if defined _LIBC && defined SHARED /* In case this libc copy is in a non-default namespace, never use brk. Likewise if dlopened from statically linked program. */ Dl_info di; struct link_map *l; if (_dl_open_hook != NULL || (_dl_addr (ptmalloc_init, &di, &l, NULL) != 0 && l->l_ns != LM_ID_BASE)) __morecore = __failing_morecore; #endif
Ptmalloc需要保证只有主分配区才能使用 sbrk() 分配连续虚拟内存空间,如果有多个分配区使用 sbrk() 就不能获得连续的虚拟地址空间,大多数情况下 Glibc 库都是以动态链接库的形式加载的,处于默认命名空间,多个进程共用 Glibc 库, Glibc 库代码段在内存中只有一份拷贝,数据段在每个用户进程都有一份拷贝。但如果 Glibc 库不在默认名字空间,或是用户程序是静态编译的并调用了 dlopen 函数加载 Glibc 库中的 ptamalloc_init() ,这种情况下的 ptmalloc 不允许使用 sbrk() 分配内存,只需修改 __morecore 函数指针指向 __failing_morecore 就可以禁止使用 sbrk() 了, __morecore 默认指向 sbrk() 。
mutex_init(&list_lock); tsd_key_create(&arena_key, NULL); tsd_setspecific(arena_key, (Void_t *)&main_arena); thread_atfork(ptmalloc_lock_all, ptmalloc_unlock_all, ptmalloc_unlock_all2);
初始化全局锁 list_lock , list_lock 主要用于同步分配区的单向循环链表。然后创建线程私有实例 arena_key ,该私有实例保存的是分配区( arena )的 malloc_state 实例指针。 arena_key 指向的可能是主分配区的指针,也可能是非主分配区的指针,这里将调用 ptmalloc_init() 的线程的 arena_key 绑定到主分配区上。意味着本线程首选从主分配区分配内存。
然后调用 thread_atfork() 设置当前进程在 fork 子线程( linux 下线程是轻量级进程,使用类似 fork 进程的机制创建)时处理 mutex 的回调函数,在本进程 fork 子线程时,调用 ptmalloc_lock_all() 获得所有分配区的锁,禁止所有分配区分配内存,当子线程创建完毕,父进程调用 ptmalloc_unlock_all() 重新 unlock 每个分配区的锁 mutex ,子线程调用 ptmalloc_unlock_all2() 重新初始化每个分配区的锁 mutex 。这几个回调函数将在 5.5.3 节介绍。
#ifndef NO_THREADS # ifndef NO_STARTER __malloc_hook = save_malloc_hook; __memalign_hook = save_memalign_hook; __free_hook = save_free_hook; # else # undef NO_STARTER # endif #endif
当pthread 初始化完成后,将相应的 hooks 函数还原为原值。
#ifdef _LIBC secure = __libc_enable_secure; s = NULL; if (__builtin_expect (_environ != NULL, 1)) { char **runp = _environ; char *envline; while (__builtin_expect ((envline = next_env_entry (&runp)) != NULL, 0)) { size_t len = strcspn (envline, "="); if (envline[len] != '=') /* This is a "MALLOC_" variable at the end of the string without a '=' character. Ignore it since otherwise we will access invalid memory below. */ continue; switch (len) { case 6: if (memcmp (envline, "CHECK_", 6) == 0) s = &envline[7]; break; case 8: if (! secure) { if (memcmp (envline, "TOP_PAD_", 8) == 0) mALLOPt(M_TOP_PAD, atoi(&envline[9])); else if (memcmp (envline, "PERTURB_", 8) == 0) mALLOPt(M_PERTURB, atoi(&envline[9])); } break; case 9: if (! secure) { if (memcmp (envline, "MMAP_MAX_", 9) == 0) mALLOPt(M_MMAP_MAX, atoi(&envline[10])); #ifdef PER_THREAD else if (memcmp (envline, "ARENA_MAX", 9) == 0) mALLOPt(M_ARENA_MAX, atoi(&envline[10])); #endif } break; #ifdef PER_THREAD case 10: if (! secure) { if (memcmp (envline, "ARENA_TEST", 10) == 0) mALLOPt(M_ARENA_TEST, atoi(&envline[11])); } break; #endif case 15: if (! secure) { if (memcmp (envline, "TRIM_THRESHOLD_", 15) == 0) mALLOPt(M_TRIM_THRESHOLD, atoi(&envline[16])); else if (memcmp (envline, "MMAP_THRESHOLD_", 15) == 0) mALLOPt(M_MMAP_THRESHOLD, atoi(&envline[16])); } break; default: break; } } } #else if (! secure) { if((s = getenv("MALLOC_TRIM_THRESHOLD_"))) mALLOPt(M_TRIM_THRESHOLD, atoi(s)); if((s = getenv("MALLOC_TOP_PAD_"))) mALLOPt(M_TOP_PAD, atoi(s)); if((s = getenv("MALLOC_PERTURB_"))) mALLOPt(M_PERTURB, atoi(s)); if((s = getenv("MALLOC_MMAP_THRESHOLD_"))) mALLOPt(M_MMAP_THRESHOLD, atoi(s)); if((s = getenv("MALLOC_MMAP_MAX_"))) mALLOPt(M_MMAP_MAX, atoi(s)); } s = getenv("MALLOC_CHECK_"); #endif if(s && s[0]) { mALLOPt(M_CHECK_ACTION, (int)(s[0] - '0')); if (check_action != 0) __malloc_check_init(); }
从环境变量中读取相应的配置参数值,这些参数包括 MALLOC_TRIM_THRESHOLD_ , MALLOC_TOP_PAD_ , MALLOC_PERTURB_ , MALLOC_MMAP_THRESHOLD_ , MALLOC_CHECK_ , MALLOC_MMAP_MAX_ , MALLOC_ ARENA_MAX,MALLOC_ ARENA_TEST, 如果这些选项中的某些项存在,调用 mallopt() 函数设置相应的选项。如果这段程序是在 Glibc 库初始化中执行的,会做更多的安全检查工作。
void (*hook) (void) = force_reg (__malloc_initialize_hook); if (hook != NULL) (*hook)(); __malloc_initialized = 1; }
在ptmalloc_init() 函数结束处,查看是否存在 __malloc_initialize_hook 函数,如果存在,执行该 hook 函数。最后将全局变量 __malloc_initialized 设置为 1 ,表示 ptmalloc_init() 已经初始化完成。