5.5.3
ptmalloc_lock_all(),ptmalloc_unlock_all(),ptmalloc_unlock_all2()
/* Magic value for the thread-specific arena pointer when
malloc_atfork() is in use. */
#define ATFORK_ARENA_PTR ((Void_t*)-1)
/* The following hooks are used while the `atfork' handling mechanism
is active. */
static Void_t*
malloc_atfork(size_t sz, const Void_t *caller)
{
Void_t *vptr = NULL;
Void_t *victim;
tsd_getspecific(arena_key, vptr);
if(vptr == ATFORK_ARENA_PTR) {
/* We are the only thread that may allocate at all. */
if(save_malloc_hook != malloc_check) {
return _int_malloc(&main_arena, sz);
} else {
if(top_check()<0)
return 0;
victim = _int_malloc(&main_arena, sz+1);
return mem2mem_check(victim, sz);
}
} else {
/* Suspend the thread until the `atfork' handlers have completed.
By that time, the hooks will have been reset as well, so that
mALLOc() can be used again. */
(void)mutex_lock(&list_lock);
(void)mutex_unlock(&list_lock);
return public_mALLOc(sz);
}
}
当父进程中的某个线程使用
fork
的机制创建子线程时,如果进程中的线程需要分配内存,将使用
malloc_atfork()
函数分配内存。
malloc_atfork()
函数首先查看自己的线程私有实例中的分配区指针,如果该指针为
ATFORK_ARENA_PTR
,意味着本线程正在
fork
新线程,并锁住了全局锁
list_lock
和每个分配区,当前只有本线程可以分配内存,如果在
fork
线程前的分配函数不是处于
check
模式,直接调用内部分配函数
_int_malloc()
。否则在分配内存的同时做检查。如果线程私有实例中的指针不是
ATFORK_ARENA_PTR
,意味着当前线程只是常规线程,有另外的线程在
fork
子线程,当前线程只能等待
fork
子线程的线程完成分配,于是等待获得全局锁
list_lock
,如果获得全局锁成功,表示
fork
子线程的线程已经完成
fork
操作,当前线程可以分配内存了,于是是释放全局所
list_lock
,并调用
public_mALLOc()
分配内存。
static void
free_atfork(Void_t* mem, const Void_t *caller)
{
Void_t *vptr = NULL;
mstate ar_ptr;
mchunkptr p; /* chunk corresponding to mem */
if (mem == 0) /* free(0) has no effect */
return;
p = mem2chunk(mem); /* do not bother to replicate free_check here */
#if HAVE_MMAP
if (chunk_is_mmapped(p)) /* release mmapped memory. */
{
munmap_chunk(p);
return;
}
#endif
#ifdef ATOMIC_FASTBINS
ar_ptr = arena_for_chunk(p);
tsd_getspecific(arena_key, vptr);
_int_free(ar_ptr, p, vptr == ATFORK_ARENA_PTR);
#else
ar_ptr = arena_for_chunk(p);
tsd_getspecific(arena_key, vptr);
if(vptr != ATFORK_ARENA_PTR)
(void)mutex_lock(&ar_ptr->mutex);
_int_free(ar_ptr, p);
if(vptr != ATFORK_ARENA_PTR)
(void)mutex_unlock(&ar_ptr->mutex);
#endif
}
当父进程中的某个线程使用
fork
的机制创建子线程时,如果进程中的线程需要释放内存,将使用
free_atfork()
函数释放内存。
free_atfork()
函数首先通过需
free
的内存块指针获得
chunk
的指针,如果该
chunk
是通过
mmap
分配的,调用
munmap()
释放该
chunk
,否则调用
_int_free()
函数释放内存。在调用
_int_free()
函数前,先根据
chunk
指针获得分配区指针,并读取本线程私用实例的指针,如果开启了
ATOMIC_FASTBINS
优化,这个优化使用了
lock-free
的技术优化
fastbins
中单向链表操作。如果没有开启了
ATOMIC_FASTBINS
优化,并且当前
线程没
有正在
fork
新子线程,则
对分配区加锁,然后调用
_int_free()
函数,然后对分配区解锁。而对于正在
fork
子线程的线程来说,是不需要对分配区加锁的,因为该线程已经对所有的分配区加锁了。
/* Counter for number of times the list is locked by the same thread. */
static unsigned int atfork_recursive_cntr;
/* The following two functions are registered via thread_atfork() to
make sure that the mutexes remain in a consistent state in the
fork()ed version of a thread. Also adapt the malloc and free hooks
temporarily, because the `atfork' handler mechanism may use
malloc/free internally (e.g. in LinuxThreads). */
static void
ptmalloc_lock_all (void)
{
mstate ar_ptr;
if(__malloc_initialized < 1)
return;
if (mutex_trylock(&list_lock))
{
Void_t *my_arena;
tsd_getspecific(arena_key, my_arena);
if (my_arena == ATFORK_ARENA_PTR)
/* This is the same thread which already locks the global list.
Just bump the counter. */
goto out;
/* This thread has to wait its turn. */
(void)mutex_lock(&list_lock);
}
for(ar_ptr = &main_arena;;) {
(void)mutex_lock(&ar_ptr->mutex);
ar_ptr = ar_ptr->next;
if(ar_ptr == &main_arena) break;
}
save_malloc_hook = __malloc_hook;
save_free_hook = __free_hook;
__malloc_hook = malloc_atfork;
__free_hook = free_atfork;
/* Only the current thread may perform malloc/free calls now. */
tsd_getspecific(arena_key, save_arena);
tsd_setspecific(arena_key, ATFORK_ARENA_PTR);
out:
++atfork_recursive_cntr;
}
当父进程中的某个线程使用
fork
的机制创建子线程时,首先调用
ptmalloc_lock_all()
函数暂时对全局锁
list_lock
和所有的分配区加锁,从而保证分配区状态的一致性。
ptmalloc_lock_all()
函数首先检查
ptmalloc
是否已经初始化,如果没有初始化,退出,如果已经初始化,尝试对全局锁
list_lock
加锁,直到获得全局锁
list_lock
,接着对所有的分配区加锁,接着保存原有的分配释放函数,将
malloc_atfork()
和
free_atfork()
函数作为
fork
子线程期间所使用的内存分配释放函数,然后保存当前线程的私有实例中的原有分配区指针,将
ATFORK_ARENA_PTR
存放到当前线程的私有实例中,用于标识当前现在正在
fork
子线程。为了保证父线程
fork
多个子线程工作正常,也就是说当前线程需要
fork
多个子线程,当一个子线程已经创建,当前线程继续创建其它子线程时,发现当前线程已经对
list_lock
和所有分配区加锁,于是对全局变量
atfork_recursive_cntr
加
1
,表示递归
fork
子线程的层数,保证父线程在
fork
子线程过程中,调用
ptmalloc_unlock_all()
函数加锁的次数与调用
ptmalloc_lock_all()
函数解锁的次数保持一致,同时也保证保证所有的子线程调用
ptmalloc_unlock_all()
函数加锁的次数与父线程调用
ptmalloc_lock_all()
函数解锁的次数保持一致,防止没有释放锁。
static void
ptmalloc_unlock_all (void)
{
mstate ar_ptr;
if(__malloc_initialized < 1)
return;
if (--atfork_recursive_cntr != 0)
return;
tsd_setspecific(arena_key, save_arena);
__malloc_hook = save_malloc_hook;
__free_hook = save_free_hook;
for(ar_ptr = &main_arena;;) {
(void)mutex_unlock(&ar_ptr->mutex);
ar_ptr = ar_ptr->next;
if(ar_ptr == &main_arena) break;
}
(void)mutex_unlock(&list_lock);
}
当进程的某个线程完成
fork
子线程后,父线程和子线程都调用
ptmall_unlock_all()
函数释放全局锁
list_lock
,释放所有分配区的锁。
ptmall_unlock_all()
函数首先检查
ptmalloc
是否初始化,只有初始化后才能调用该函数,接着将全局变量
atfork_recursive_cntr
减
1
,如果
atfork_recursive_cntr
为
0
,才继续执行,这保证了递归
fork
子线程只会解锁一次。接着将当前线程的私有实例还原为原来的分配区,
__malloc_hook
和
__free_hook
还原为由来的
hook
函数。然后遍历所有分配区,依次解锁每个分配区,最后解锁
list_lock
。
#ifdef __linux__
/* In NPTL, unlocking a mutex in the child process after a
fork() is currently unsafe, whereas re-initializing it is safe and
does not leak resources. Therefore, a special atfork handler is
installed for the child. */
static void
ptmalloc_unlock_all2 (void)
{
mstate ar_ptr;
if(__malloc_initialized < 1)
return;
#if defined _LIBC || defined MALLOC_HOOKS
tsd_setspecific(arena_key, save_arena);
__malloc_hook = save_malloc_hook;
__free_hook = save_free_hook;
#endif
#ifdef PER_THREAD
free_list = NULL;
#endif
for(ar_ptr = &main_arena;;) {
mutex_init(&ar_ptr->mutex);
#ifdef PER_THREAD
if (ar_ptr != save_arena) {
ar_ptr->next_free = free_list;
free_list = ar_ptr;
}
#endif
ar_ptr = ar_ptr->next;
if(ar_ptr == &main_arena) break;
}
mutex_init(&list_lock);
atfork_recursive_cntr = 0;
}
#else
#define ptmalloc_unlock_all2 ptmalloc_unlock_all
#endif
函数ptmalloc_unlock_all2()
被
fork
出的子线程调用,在
Linux
系统中,子线程(进程)
unlock
从父线程(进程)中继承的
mutex
不安全,会导致资源泄漏,但重新初始化
mutex
是安全的,所有增加了这个特殊版本用于
Linux
下的
atfork handler
。
ptmalloc_unlock_all2()
函数的处理流程跟
ptmalloc_unlock_all()
函数差不多,使用
mutex_init()
代替了
mutex_unlock()
,如果开启了
PER_THREAD
的优化,将从父线程中继承来的分配区加入到
free_list
中,对于子线程来说,无论全局变量
atfork_recursive_cntr
的值是多少,都将该值设置为
0
,因为
ptmalloc_unlock_all2()
函数只会被子线程调用一次。
分享到:
相关推荐
NULL 博文链接:https://mqzhuang.iteye.com/blog/1064966
Glibc内存管理 Ptmalloc2 源代码分析
glibc内存管理ptmalloc源代码分析-电子资料-高清PDF版-pdf打印版
简介133.2.2内存管理的设计假设 143.2.3内存管理数据结构概述 143.2.4内存分配概述 193.2.5内存回收概述 213.2.6配置选项概述 2
淘宝网的研发人员写的文档,对了解GNU C的内存分配机制有很大的帮助!
该文档详细分析了c库中的内存管理细节,可广大程序员做参考。
简介133.2.2内存管理的设计假设 143.2.3内存管理数据结构概述 143.2.4内存分配概述 193.2.5内存回收概述 213.2.6配置选项概述 2
清晰版,对内存分析,程序设计非常有帮助。适合进阶的。
5. 源代码分析 5.1 边界标记法 5.2 分箱式内存管理 5.2.1 Small bins 5.2.2 Large bins 5.2.3 Unsorted bin 5.2.4 Fast bins 5.3 核心结构体分析 5.3.1 malloc_state 5.3.2 Malloc_par 5.3.3 分配区的初始...
glibc内存管理ptmalloc源代码分析.pdf
源代码分析 glibc的ptmalloc 分析
glibc内存管理ptmalloc源代码分析4.pdf
学习自《glibc内存管理ptmalloc源代码分析》庄明强 著 部分资料参考自互联网 chunk 描述: 当用户通过malloc等函数申请空间时,实际上是从堆中分配内存 目前 Linux 标准发行版中使用的是 glibc 中的堆分配器:...