5.6
多分配区支持
由于只有一个主分配区从堆中分配小内存块,而稍大的内存块都必须从
mmap
映射区域分配,如果有多个线程都要分配小内存块,但多个线程是不能同时调用
sbrk()
函数的,因为只有一个函数调用
sbrk()
时才能保证分配的虚拟地址空间是连续的。如果多个线程都从主分配区中分配小内存块,效率很低效。为了解决这个问题,
ptmalloc
使用非主分配区来模拟主分配区的功能,非主分配区同样可以分配小内存块,并且可以创建多个非主分配区,从而在线程分配内存竞争比较激烈的情况下,可以创建更多的非主分配区来完成分配任务,减少分配区的锁竞争,提高分配效率。
Ptmalloc
怎么用非主分配区来模拟主分配区的行为呢?首先创建一个新的非主分配区,非主分配区使用
mmap()
函数分配一大块内存来模拟堆(
sub-heap
),所有的从该非主分配区总分配的小内存块都从
sub-heap
中切分出来,如果一个
sub-heap
的内存用光了,或是
sub-heap
中的内存不够用时,使用
mmap()
分配一块新的内存块作为
sub-heap
,并将新的
sub-heap
链接在非主分配区中
sub-heap
的单向链表中。
分主分配区中的
sub-heap
所占用的内存不会无限的增长下去,同样会像主分配区那样进行进行
sub-heap
收缩,将
sub-heap
中
top chunk
的一部分返回给操作系统,如果
top chunk
为整个
sub-heap
,会把整个
sub-heap
还回给操作系统。收缩堆的条件是当前
free
的
chunk
大小加上前后能合并
chunk
的大小大于
64KB
,并且
top chunk
的大小达到
mmap
收缩阈值,才有可能收缩堆。
一般情况下,进程中有多个线程,也有多个分配区,线程的数据一般会比分配区数量多,所以必能保证没有线程独享一个分配区,每个分配区都有可能被多个线程使用,为了保证分配区的线程安全,对分配区的访问需要锁保护,当线程获得分配区的锁时,可以使用该分配区分配内存,并将该分配区的指针保存在线程的私有实例中。
当某一线程需要调用
malloc
分配内存空间时,该线程先查看线程私有变量中是否已经存在一个
分配区
,如果存在,尝试对该
分配区
加锁,如果加锁成功,使用该
分配区
分配内存,如果失败,该线程搜分配区索循环链表试图获得一个空闲的
分配区
。如果所有的
分配区
都已经加锁,那么
malloc
会开辟一个新的
分配区
,把该
分配区
加入到分配区的全局
分配区
循环链表并加锁,然后使用该
分配区
进行分配操作。在回收操作中,线程同样试图获得待回收块所在
分配区
的锁,如果该
分配区
正在被别的线程使用,则需要等待直到其他线程释放该
分配区
的互斥锁之后才可以进行回收操作。
5.6.1 Heap_info
Struct heap_info
定义如下:
/* A heap is a single contiguous memory region holding (coalesceable)
malloc_chunks. It is allocated with mmap() and always starts at an
address aligned to HEAP_MAX_SIZE. Not used unless compiling with
USE_ARENAS. */
typedef struct _heap_info {
mstate ar_ptr; /* Arena for this heap. */
struct _heap_info *prev; /* Previous heap. */
size_t size; /* Current size in bytes. */
size_t mprotect_size; /* Size in bytes that has been mprotected
PROT_READ|PROT_WRITE. */
/* Make sure the following data is properly aligned, particularly
that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
MALLOC_ALIGNMENT. */
char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK];
} heap_info;
ar_ptr
是指向所属分配区的指针,
mstate
的定义为:
typedef
struct malloc_state *mstate;
prev
字段用于将同一个分配区中的
sub_heap
用单向链表链接起来。
prev
指向链表中的前一个
sub_heap
。
size
字段表示当前
sub_heap
中的内存大小,以
page
对齐。
mprotect_size
字段表示当前
sub_heap
中被读写保护的内存大小,也就是说还没有被分配的内存大小。
Pad
字段用于保证
sizeof
(heap_info) + 2 * SIZE_SZ
是按
MALLOC_ALIGNMENT
对齐的。
MALLOC_ALIGNMENT_MASK
为
2 * SIZE_SZ - 1
,无论
SIZE_SZ
为
4
或
8
,
-6 * SIZE_SZ &
MALLOC_ALIGN_MASK
的值为
0
,如果
sizeof
(heap_info) + 2 * SIZE_SZ
不是按
MALLOC_ALIGNMENT
对齐,编译的时候就会报错,编译时会执行下面的宏。
/* Get a compile-time error if the heap_info padding is not correct
to make alignment work as expected in sYSMALLOc. */
extern int sanity_check_heap_info_alignment[(sizeof (heap_info)
+ 2 * SIZE_SZ) % MALLOC_ALIGNMENT
? -1 : 1];
为什么一定要保证对齐呢?作为分主分配区的第一个
sub_heap
,
heap_info
存放在
sub_heap
的头部,紧跟
heap_info
之后是该非主分配区的
malloc_state
实例,紧跟
malloc_state
实例后,是
sub_heap
中的第一个
chunk
,但
chunk
的首地址必须按照
MALLOC_ALIGNMENT
对齐,所以在
malloc_state
实例和第一个
chunk
之间可能有几个字节的
pad
,但如果
sub_heap
不是非主分配区的第一个
sub_heap
,则紧跟
heap_info
后是第一个
chunk
,但
sysmalloc()
函数默认
heap_info
是按照
MALLOC_ALIGNMENT
对齐的,没有再做对齐的工作,直接将
heap_info
后的内存强制转换成一个
chunk
。所以这里在编译时保证
sizeof (heap_info) + 2 * SIZE_SZ
是按
MALLOC_ALIGNMENT
对齐的,在运行时就不用再做检查了,也不必再做对齐。
5.6.2 获取分配区
为了支持多线程,
ptmalloc
定义了如下的全局变量:
static tsd_key_t arena_key;
static mutex_t list_lock;
#ifdef PER_THREAD
static size_t narenas;
static mstate free_list;
#endif
/* Mapped memory in non-main arenas (reliable only for NO_THREADS). */
static unsigned long arena_mem;
/* Already initialized? */
int __malloc_initialized = -1;
arena_key
存放的是线程的私用实例,该私有实例保存的是分配区(
arena
)的
malloc_state
实例的指针。
arena_key
指向的可能是主分配区的指针,也可能是非主分配区的指针。
list_lock
用于同步分配区的单向环形链表。
如果定义了
PRE_THREAD
,
narenas
全局变量表示当前分配区的数量,
free_list
全局变量是空闲分配区的单向链表,这些空闲的分配区可能是从父进程那里继承来的。全局变量
narenas
和
free_list
都用锁
list_lock
同步。
arena_mem
只用于单线程的
ptmalloc
版本,记录了非主分配区所分配的内存大小。
__malloc_initializd
全局变量用来标识是否
ptmalloc
已经初始化了,其值大于
0
时表示已经初始化。
Ptmalloc
使用如下的宏来获得分配区:
/* arena_get() acquires an arena and locks the corresponding mutex.
First, try the one last locked successfully by this thread. (This
is the common case and handled with a macro for speed.) Then, loop
once over the circularly linked list of arenas. If no arena is
readily available, create a new one. In this latter case, `size'
is just a hint as to how much memory will be required immediately
in the new arena. */
#define arena_get(ptr, size) do { \
arena_lookup(ptr); \
arena_lock(ptr, size); \
} while(0)
#define arena_lookup(ptr) do { \
Void_t *vptr = NULL; \
ptr = (mstate)tsd_getspecific(arena_key, vptr); \
} while(0)
#ifdef PER_THREAD
#define arena_lock(ptr, size) do { \
if(ptr) \
(void)mutex_lock(&ptr->mutex); \
else \
ptr = arena_get2(ptr, (size)); \
} while(0)
#else
#define arena_lock(ptr, size) do { \
if(ptr && !mutex_trylock(&ptr->mutex)) { \
THREAD_STAT(++(ptr->stat_lock_direct)); \
} else \
ptr = arena_get2(ptr, (size)); \
} while(0)
#endif
/* find the heap and corresponding arena for a given ptr */
#define heap_for_ptr(ptr) \
((heap_info *)((unsigned long)(ptr) & ~(HEAP_MAX_SIZE-1)))
#define arena_for_chunk(ptr) \
(chunk_non_main_arena(ptr) ? heap_for_ptr(ptr)->ar_ptr : &main_arena)
arena_get
首先调用
arena_lookup
查找本线程的私用实例中是否包含一个分配区的指针,返回该指针,调用
arena_lock
尝试对该分配区加锁,如果加锁成功,使用该分配区分配内存,如果对该分配区加锁失败,调用
arena_get2
获得一个分配区指针。如果定义了
PRE_THREAD
,
arena_lock
的处理有些不同,如果本线程拥有的私用实例中包含分配区的指针,则直接对该分配区加锁,否则,调用
arena_get2
获得分配区指针,
PRE_THREAD
的优化保证了每个线程尽量从自己所属的分配区中分配内存,减少与其它线程因共享分配区带来的锁开销,但
PRE_THREAD
的优化并不能保证每个线程都有一个不同的分配区,当系统中的分配区数量达到配置的最大值时,不能再增加新的分配区,如果再增加新的线程,就会有多个线程共享同一个分配区。所以
ptmalloc
的
PRE_THREAD
优化,对线程少时可能会提升一些性能,但线程多时,提升性能并不明显。即使没有线程共享分配区的情况下,任然需要加锁,这是不必要的开销,每次加锁操作会消耗
100ns
左右的时间。
每个
sub_heap
的内存块使用
mmap()
函数分配,并以
HEAP_MAX_SIZE
对齐,所以可以根据
chunk
的指针地址,获得这个
chunk
所属的
sub_heap
的地址。
heap_for_ptr
根据
chunk
的地址获得
sub_heap
的地址。由于
sub_heap
的头部存放的是
heap_info
的实例,
heap_info
中保存了分配区的指针,所以可以通过
chunk
的地址获得分配区的地址,前提是这个
chunk
属于非主分配区,
arena_for_chunk
用来做这样的转换。
#define HEAP_MIN_SIZE (32*1024)
#ifndef HEAP_MAX_SIZE
# ifdef DEFAULT_MMAP_THRESHOLD_MAX
# define HEAP_MAX_SIZE (2 * DEFAULT_MMAP_THRESHOLD_MAX)
# else
# define HEAP_MAX_SIZE (1024*1024) /* must be a power of two */
# endif
#endif
HEAP_MIN_SIZE
定义了
sub_heap
内存块的最小值,
32KB
。
HEAP_MAX_SIZE
定义了
sub_heap
内存块的最大值,在
32
位系统上,
HEAP_MAX_SIZE
默认值为
1MB
,
64
为系统上,
HEAP_MAX_SIZE
的默认值为
64MB
。
分享到:
相关推荐
《Glibc内存管理--ptmalloc2源代码分析》 Glibc是GNU项目提供的C语言标准库,它在Linux系统中扮演着至关重要的角色。其中,内存管理是Glibc中的核心部分,它负责程序运行时的内存分配与释放,对系统的性能有着深远...
在分析glibc内存管理的ptmalloc源代码之前,我们需要先了解一些基础知识,包括操作系统对内存的分配和管理方法,以及glibc内存分配机制。 内存管理是操作系统的一个核心功能,它负责维护和管理计算机系统中的物理和...
《glibc内存管理ptmalloc源代码分析》是一份深入探讨Linux系统中glibc库内存管理机制的专业资料。glibc,全称GNU C Library,是Linux操作系统下广泛使用的C语言标准库,其中ptmalloc是glibc中负责动态内存分配的核心...
glibc内存管理ptmalloc源代码分析
在深入探讨Glibc内存管理的Ptmalloc源代码之前,我们先来了解一下内存管理的基本概念。内存管理是操作系统和编程语言库中的核心组件,它负责有效地分配和回收内存,以确保程序的高效运行和资源的有效利用。 2.1 X86...
标题与描述中的关键词“Glibc内存管理Ptmalloc2源代码分析”指向了对Glibc中Ptmalloc2这一内存管理器的深入探讨。本文将围绕这一主题,详细解析Ptmalloc2的核心概念、设计原则、数据结构以及关键功能,同时结合给定...
通过对ptmalloc源代码的深入分析,可以找到潜在的优化点,如调整分配策略、改进数据结构设计等,以解决这些问题。 #### 13. 使用注意事项 虽然ptmalloc提供了强大的内存管理功能,但在使用过程中也需要注意一些事项...
在深入探讨Glibc内存管理的Ptmalloc源代码之前,我们先来了解一下内存管理的基本概念和Glibc中的Ptmalloc2。内存管理是操作系统和应用程序中的核心部分,它负责为程序分配和释放内存,以确保资源的有效利用和避免...
### glibc内存管理ptmalloc源代码分析 #### 1. 问题 在开发一款NoSQL系统的过程中,我们遇到一个令人头疼的问题:系统中使用的内存管理模块,在高并发、高负载的场景下,虽然已将内存释放给C运行时库(即glibc),...
### glibc内存管理ptmalloc源代码分析-清晰版 #### 一、背景介绍与文档概览 本文档针对glibc中的ptmalloc2内存管理模块进行了深入的源代码分析,旨在帮助开发者更好地理解ptmalloc的工作原理及其内部机制。文档...
`glibc`是GNU项目提供的C标准库,而`ptmalloc`是它的一部分,负责程序的内存分配与管理。`ptmalloc`优化了内存分配效率,但在某些情况下也可能成为安全漏洞的来源。 `GDB`(GNU调试器)是一款强大的调试工具,它...
本文将深入探讨内存管理的基本原理及其调试方法,特别关注于Linux环境下的Glibc内存管理库以及Ptmalloc2的具体实现。 #### 二、基础知识 ##### 2.1 X86平台Linux进程内存布局 **2.1.1 32位模式下进程内存经典布局...
通过对glibc库中的ptmalloc源代码进行深入分析,我们不仅了解了其内部实现机制,还能够针对实际应用中的内存管理问题提出有效的解决方案。这对于提升程序性能、减少内存碎片以及提高系统的整体稳定性具有重要意义。...
学习自《glibc内存管理ptmalloc源代码分析》庄明强 著 部分资料参考自互联网 chunk 描述: 当用户通过malloc等函数申请空间时,实际上是从堆中分配内存 目前 Linux 标准发行版中使用的是 glibc 中的堆分配器:...
《堆漏洞的利用技巧》 堆溢出是计算机安全领域中的一个重要...同时,阅读glibc内存管理的源代码分析对于提高理解和实践能力非常有帮助。通过这些知识的学习,不仅可以增强安全意识,也能提升在CTF竞赛中的攻防能力。