`
mqzhuang
  • 浏览: 187580 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

Glibc内存管理--ptmalloc2源代码分析(二十一)

阅读更多

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内存管理--ptmalloc2源代码分析》 Glibc是GNU项目提供的C语言标准库,它在Linux系统中扮演着至关重要的角色。其中,内存管理是Glibc中的核心部分,它负责程序运行时的内存分配与释放,对系统的性能有着深远...

    glibc内存管理ptmalloc源代码分析PDF

    在分析glibc内存管理的ptmalloc源代码之前,我们需要先了解一些基础知识,包括操作系统对内存的分配和管理方法,以及glibc内存分配机制。 内存管理是操作系统的一个核心功能,它负责维护和管理计算机系统中的物理和...

    glibc内存管理ptmalloc源代码分析-高清PDF-pdf版

    《glibc内存管理ptmalloc源代码分析》是一份深入探讨Linux系统中glibc库内存管理机制的专业资料。glibc,全称GNU C Library,是Linux操作系统下广泛使用的C语言标准库,其中ptmalloc是glibc中负责动态内存分配的核心...

    glibc内存管理ptmalloc源代码分析.pdf

    glibc内存管理ptmalloc源代码分析

    glibc内存管理ptmalloc源代码分析@华庭1

    在深入探讨Glibc内存管理的Ptmalloc源代码之前,我们先来了解一下内存管理的基本概念。内存管理是操作系统和编程语言库中的核心组件,它负责有效地分配和回收内存,以确保程序的高效运行和资源的有效利用。 2.1 X86...

    Glibc内存管理Ptmalloc2源代码分析

    标题与描述中的关键词“Glibc内存管理Ptmalloc2源代码分析”指向了对Glibc中Ptmalloc2这一内存管理器的深入探讨。本文将围绕这一主题,详细解析Ptmalloc2的核心概念、设计原则、数据结构以及关键功能,同时结合给定...

    glibc内存管理ptmalloc源代码分析4.pdf

    通过对ptmalloc源代码的深入分析,可以找到潜在的优化点,如调整分配策略、改进数据结构设计等,以解决这些问题。 #### 13. 使用注意事项 虽然ptmalloc提供了强大的内存管理功能,但在使用过程中也需要注意一些事项...

    glibc内存管理ptmalloc源代码分析1

    在深入探讨Glibc内存管理的Ptmalloc源代码之前,我们先来了解一下内存管理的基本概念和Glibc中的Ptmalloc2。内存管理是操作系统和应用程序中的核心部分,它负责为程序分配和释放内存,以确保资源的有效利用和避免...

    glibc内存管理ptmalloc源代码分析

    ### glibc内存管理ptmalloc源代码分析 #### 1. 问题 在开发一款NoSQL系统的过程中,我们遇到一个令人头疼的问题:系统中使用的内存管理模块,在高并发、高负载的场景下,虽然已将内存释放给C运行时库(即glibc),...

    glibc内存管理ptmalloc源代码分析-清晰版.pdf

    ### glibc内存管理ptmalloc源代码分析-清晰版 #### 一、背景介绍与文档概览 本文档针对glibc中的ptmalloc2内存管理模块进行了深入的源代码分析,旨在帮助开发者更好地理解ptmalloc的工作原理及其内部机制。文档...

    Python-用于检测glibc堆ptmalloc的gdbpython库

    `glibc`是GNU项目提供的C标准库,而`ptmalloc`是它的一部分,负责程序的内存分配与管理。`ptmalloc`优化了内存分配效率,但在某些情况下也可能成为安全漏洞的来源。 `GDB`(GNU调试器)是一款强大的调试工具,它...

    内存管理与调试详细剖析

    本文将深入探讨内存管理的基本原理及其调试方法,特别关注于Linux环境下的Glibc内存管理库以及Ptmalloc2的具体实现。 #### 二、基础知识 ##### 2.1 X86平台Linux进程内存布局 **2.1.1 32位模式下进程内存经典布局...

    malloc源码分析glibc库

    通过对glibc库中的ptmalloc源代码进行深入分析,我们不仅了解了其内部实现机制,还能够针对实际应用中的内存管理问题提出有效的解决方案。这对于提升程序性能、减少内存碎片以及提高系统的整体稳定性具有重要意义。...

    pwn学习总结(八)—— 堆(持续更新)

    学习自《glibc内存管理ptmalloc源代码分析》庄明强 著 部分资料参考自互联网 chunk 描述: 当用户通过malloc等函数申请空间时,实际上是从堆中分配内存 目前 Linux 标准发行版中使用的是 glibc 中的堆分配器:...

    堆漏洞的利用技巧1

    《堆漏洞的利用技巧》 堆溢出是计算机安全领域中的一个重要...同时,阅读glibc内存管理的源代码分析对于提高理解和实践能力非常有帮助。通过这些知识的学习,不仅可以增强安全意识,也能提升在CTF竞赛中的攻防能力。

Global site tag (gtag.js) - Google Analytics