5.6.5 new_heap()
New_heap()
函数负责从
mmap
区域映射一块内存来作为
sub_heap
,在
32
位系统上,该函数每次映射
1M
内存,映射的内存块地址按
1M
对齐;在
64
为系统上,该函数映射
64M
内存,映射的内存块地址按
64M
对齐。
New_heap()
函数只是映射一块虚拟地址空间,该空间不可读写,不会被
swap
。
New_heap()
函数的实现源代码如下:
/* If consecutive mmap (0, HEAP_MAX_SIZE << 1, ...) calls return decreasing
addresses as opposed to increasing, new_heap would badly fragment the
address space. In that case remember the second HEAP_MAX_SIZE part
aligned to HEAP_MAX_SIZE from last mmap (0, HEAP_MAX_SIZE << 1, ...)
call (if it is already aligned) and try to reuse it next time. We need
no locking for it, as kernel ensures the atomicity for us - worst case
we'll call mmap (addr, HEAP_MAX_SIZE, ...) for some value of addr in
multiple threads, but only one will succeed. */
static char *aligned_heap_area;
/* Create a new heap. size is automatically rounded up to a multiple
of the page size. */
static heap_info *
internal_function
#if __STD_C
new_heap(size_t size, size_t top_pad)
#else
new_heap(size, top_pad) size_t size, top_pad;
#endif
{
size_t page_mask = malloc_getpagesize - 1;
char *p1, *p2;
unsigned long ul;
heap_info *h;
if(size+top_pad < HEAP_MIN_SIZE)
size = HEAP_MIN_SIZE;
else if(size+top_pad <= HEAP_MAX_SIZE)
size += top_pad;
else if(size > HEAP_MAX_SIZE)
return 0;
else
size = HEAP_MAX_SIZE;
size = (size + page_mask) & ~page_mask;
调整size
的大小,
size
的最小值为
32K,
最大值
HEAP_MAX_SIZE
在不同的系统上不同,在
32
位系统为
1M
,
64
位系统为
64M
,将
size
的大小调整到最小值与最大值之间,并以页对齐,如果
size
大于最大值,直接报错。
/* A memory region aligned to a multiple of HEAP_MAX_SIZE is needed.
No swap space needs to be reserved for the following large
mapping (on Linux, this is the case for all non-writable mappings
anyway). */
p2 = MAP_FAILED;
if(aligned_heap_area) {
p2 = (char *)MMAP(aligned_heap_area, HEAP_MAX_SIZE, PROT_NONE,
MAP_PRIVATE|MAP_NORESERVE);
aligned_heap_area = NULL;
if (p2 != MAP_FAILED && ((unsigned long)p2 & (HEAP_MAX_SIZE-1))) {
munmap(p2, HEAP_MAX_SIZE);
p2 = MAP_FAILED;
}
}
全局变量
aligned_heap_area
是上一次调用
mmap
分配内存的结束虚拟地址,并已经按照
HEAP_MAX_SIZE
大小对齐。如果
aligned_heap_area
不为空,尝试从上次映射结束地址开始映射大小为
HEAP_MAX_SIZE
的内存块,由于全局变量
aligned_heap_area
没有锁保护,可能存在多个线程同时
mmap()
函数从
aligned_heap_area
开始映射新的虚拟内存块,操作系统会保证只会有一个线程会成功,其它在同一地址映射新虚拟内存块都会失败。无论映射是否成功,都将全局变量
aligned_heap_area
设置为
NULL
。如果映射成功,但返回的虚拟地址不是按
HEAP_MAX_SIZE
大小对齐的,取消该区域的映射,映射失败。
if(p2 == MAP_FAILED) {
p1 = (char *)MMAP(0, HEAP_MAX_SIZE<<1, PROT_NONE,
MAP_PRIVATE|MAP_NORESERVE);
全局变量
aligned_heap_area
为
NULL
,或者从
aligned_heap_area
开始映射失败了,尝试映射
2
倍
HEAP_MAX_SIZE
大小的虚拟内存,便于地址对齐,因为在最坏可能情况下,需要映射
2
倍
HEAP_MAX_SIZE
大小的虚拟内存才能实现地址按照
HEAP_MAX_SIZE
大小对齐。
if(p1 != MAP_FAILED) {
p2 = (char *)(((unsigned long)p1 + (HEAP_MAX_SIZE-1))
& ~(HEAP_MAX_SIZE-1));
ul = p2 - p1;
if (ul)
munmap(p1, ul);
else
aligned_heap_area = p2 + HEAP_MAX_SIZE;
munmap(p2 + HEAP_MAX_SIZE, HEAP_MAX_SIZE - ul);
映射2
倍
HEAP_MAX_SIZE
大小的虚拟内存成功,将大于等于
p1
并按
HEAP_MAX_SIZE
大小对齐的第一个虚拟地址赋值给
p2
,
p2
作为
sub_heap
的起始虚拟地址,
p2+ HEAP_MAX_SIZE
作为
sub_heap
的结束地址,并将
sub_heap
的结束地址赋值给全局变量
aligned_heap_area
,最后还需要将多余的虚拟内存还回给操作系统。
} else {
/* Try to take the chance that an allocation of only HEAP_MAX_SIZE
is already aligned. */
p2 = (char *)MMAP(0, HEAP_MAX_SIZE, PROT_NONE, MAP_PRIVATE|MAP_NORESERVE);
if(p2 == MAP_FAILED)
return 0;
if((unsigned long)p2 & (HEAP_MAX_SIZE-1)) {
munmap(p2, HEAP_MAX_SIZE);
return 0;
}
映射2
倍
HEAP_MAX_SIZE
大小的虚拟内存失败了,再尝试映射
HEAP_MAX_SIZE
大小的虚拟内存,如果失败,返回;如果成功,但该虚拟地址不是按照
HEAP_MAX_SIZE
大小对齐的,返回。
}
}
if(mprotect(p2, size, PROT_READ|PROT_WRITE) != 0) {
munmap(p2, HEAP_MAX_SIZE);
return 0;
}
h = (heap_info *)p2;
h->size = size;
h->mprotect_size = size;
THREAD_STAT(stat_n_heaps++);
调用
mprotect()
函数将
size
大小的内存设置为可读可写,如果失败,解除整个
sub_heap
的映射。然后更新
heap_info
实例中的相关字段。
return h;
}
5.6.6 get_free_list()和reused_arena()
这两个函数在开启了
PER_THRAD
优化时用于获取分配区(
arena
),
arena_get2
首先调用
get_free_list()
尝试获得
arena
,如果失败在尝试调用
reused_arena()
获得
arena
,如果仍然没有获得分配区,调用
_int_new_arena()
创建一个新的分配区。
Get_free_list()
函数的源代码如下:
static mstate
get_free_list (void)
{
mstate result = free_list;
if (result != NULL)
{
(void)mutex_lock(&list_lock);
result = free_list;
if (result != NULL)
free_list = result->next_free;
(void)mutex_unlock(&list_lock);
if (result != NULL)
{
(void)mutex_lock(&result->mutex);
tsd_setspecific(arena_key, (Void_t *)result);
THREAD_STAT(++(result->stat_lock_loop));
}
}
return result;
}
这个函数实现很简单,首先查看
arena
的
free_list
中是否为
NULL
,如果不为
NULL
,获得全局锁
list_lock
,将
free_list
的第一个
arena
从单向链表中取出,解锁
list_lock
。如果从
free_list
中获得一个
arena
,对该
arena
加锁,并将该
arena
加入线程的私有实例中。
reused_arena()
函数的源代码实现如下:
static mstate
reused_arena (void)
{
if (narenas <= mp_.arena_test)
return NULL;
首先判断全局分配区的总数是否小于分配区的个数的限定值(
arena_test
),
在
32
位系统上
arena_test
默认值为
2
,
64
位系统上的默认值为
8
,如果当前进程的分配区数量没有达到限定值,直接返回
NULL
。
static int narenas_limit;
if (narenas_limit == 0)
{
if (mp_.arena_max != 0)
narenas_limit = mp_.arena_max;
else
{
int n = __get_nprocs ();
if (n >= 1)
narenas_limit = NARENAS_FROM_NCORES (n);
else
/* We have no information about the system. Assume two
cores. */
narenas_limit = NARENAS_FROM_NCORES (2);
}
}
if (narenas < narenas_limit)
return NULL;
设定全局变量
narenas_limit
,如果应用层设置了进程的最大分配区个数(
arena_max
),将
arena_max
赋值给
narenas_limit
,否则根据系统的
cpu
个数和系统的字大小设定
narenas_limit
的大小,
narenas_limit
的大小默认与
arena_test
大小相同。然后再次判断进程的当前分配区个数是否达到了分配区的限制个数,如果没有达到限定值,返回。
mstate result;
static mstate next_to_use;
if (next_to_use == NULL)
next_to_use = &main_arena;
result = next_to_use;
do
{
if (!mutex_trylock(&result->mutex))
goto out;
result = result->next;
}
while (result != next_to_use);
/* No arena available. Wait for the next in line. */
(void)mutex_lock(&result->mutex);
out:
tsd_setspecific(arena_key, (Void_t *)result);
THREAD_STAT(++(result->stat_lock_loop));
next_to_use = result->next;
全局变量
next_to_use
指向下一个可能可用的分配区,该全局变量没有锁保护,主要用于记录上次遍历分配区循环链表到达的位置,避免每次都从同一个分配区开始遍历,导致从某个分配区分配的内存过多。首先判断
next_to_use
是否为
NULL
,如果是,将主分配区赋值给
next_to_use
。然后从
next_to_use
开始遍历分配区链表,尝试对遍历的分配区加锁,如果加锁成功,退出循环,如果遍历分配区循环链表中的所有分配区,尝试加锁都失败了,等待获得
next_to_use
指向的分配区的锁。执行到
out
的代码,意味着已经获得一个分配区的锁,将该分配区加入线程私有实例,并将当前分配区的下一个分配区赋值给
next_to_use
。
return result;
}
分享到:
相关推荐
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
清晰版,对内存分析,程序设计非常有帮助。适合进阶的。
本文通过Glibc的内存暴增问题,主要介绍了系统的内存管理问题,具体如下: 目录 1. 问题 2. 基础知识 2.1 X86平台Linux进程内存布局 2.1.1 32位模式下进程内存经典布局 2.1.2 32位模式下进程默认内存布局 ...
glibc内存管理ptmalloc源代码分析.pdf
源代码分析 glibc的ptmalloc 分析
glibc内存管理ptmalloc源代码分析4.pdf
学习自《glibc内存管理ptmalloc源代码分析》庄明强 著 部分资料参考自互联网 chunk 描述: 当用户通过malloc等函数申请空间时,实际上是从堆中分配内存 目前 Linux 标准发行版中使用的是 glibc 中的堆分配器:...