目录
1、什么是堆
2、使用动态分配的内存
3、系统调用brk和mmap
3.1 brk()
3.2 mmap()
4、多线程与Arena
5、维护多个堆
1、什么是堆
堆是每个程序被分配到的一块内存区域,和栈的区别主要在于堆内存是动态分配的。也就是说,程序可以从“heap“段请求一块内存,或者释放一块内存。是ELF文件当中的一个段。
另外,堆内存是全局的,即在程序的任意位置都可以访问到堆,并不一定要在调用“malloc”的那个函数里访问。这是因为 C 语言使用指针指向动态分配的内存。但相比访问栈上的静态局部变量,使用指针也带来了一定的开销。
2、使用动态分配的内存
GLibc 采用 ptmalloc2 内存分配器管理堆内存,相比前身的 dlmalloc,它增加了对多线程的支持。
借助 stdlib.h 我们可以使用 malloc 和 free 函数来操作堆内存:
#include<stdio.h> #include<assert.h> #include<string.h> #include<stdlib.h> int main(){ char* buffer = (char*)malloc(10); assert(buffer); strcpy(buffer, "hello"); printf(buffer); }
malloc函数介绍:
malloc void* malloc (size_t size); Allocate memory block Allocates a block of size bytes of memory, returning a pointer to the beginning of the block. The content of the newly allocated block of memory is not initialized, remaining with indeterminate values. If size is zero, the return value depends on the particular library implementation (it may or may not be a null pointer), but the returned pointer shall not be dereferenced. Parameters size Size of the memory block, in bytes. size_t is an unsigned integral type. Return Value On success, a pointer to the memory block allocated by the function. The type of this pointer is always void*, which can be cast to the desired type of data pointer in order to be dereferenceable. If the function failed to allocate the requested block of memory, a null pointer is returned.
注意:
向堆区申请空间时,并不是真实的申请所需要的字节空间,而会有标准库进行多申请字节空间,用于记录申请空间的属性信息(申请时间,申请大小,访问权限等信息)(cookie数据),free在释放空间时,便可根据起始位置获取属性信息,得到需要释放空间的大小。
即使是申请0个字节空间,malloc依旧是会分配一个最小的chunk
free函数介绍:
free void free (void* ptr); Deallocate memory block A block of memory previously allocated by a call to malloc, calloc or realloc is deallocated, making it available again for further allocations. If ptr does not point to a block of memory allocated with the above functions, it causes undefined behavior. If ptr is a null pointer, the function does nothing. Notice that this function does not change the value of ptr itself, hence it still points to the same (now invalid) location. Parameters ptr Pointer to a memory block previously allocated with malloc, calloc or realloc. Return Value none
注意:
free函数,如果传递的是空指针,将不会进行任何操作,如果传递的之前已经被free过了,再次free就会造成一个double free,其后果是不可预测的。
3、系统调用brk和mmap
注意,申请内存时,Linux内核会先分配一段虚拟内存,真正使用时才会映射到物理内存。
参考文章:进程地址空间
3.1 brk()
brk()通过增加 break location 来获取内存,一开始 heap 段的起点 start_brk 和 heap 段的终点 brk 执行同一位置。
地址空间随机化(Address Space Layout Randomization)(ASLR)是一种操作系统用来抵御缓冲区溢出攻击的内存保护机制。这种技术使得系统上运行的进程的内存地址无法被预测,使得与这些进程有关的漏洞变得更加难以利用。
ASLR 关闭时,两者指向 data/bss 段的末尾,也就是 end_data
ASLR 开启时,两者指向 data/bss 段的末尾加上一段随机 brk 便宜
注意:
与 ' sbrk() ' 的区别,后者是 C 语言库函数, ’ malloc ‘ 源码中的 ’ MORECORE ‘ 就是调用的 ' sbrk() '。
当一直使用brk()往上移动,可能会导致与其他区域重叠,此时可以使用mmap()
3.2 mmap()
用于创建私有匿名映射段,主要是为了分配一块新的内存,且这块内存只有调用 ' mmap() ' 的进程可以使用,所以称之为私有的,与之进行相反操作的是 ' munmap() ',删除一块内存区域上的映射。
brk相当与你真的拿到了这块内存,而mmap相当于对于不同进程在MemoryMappingSegment创建映射段,并进行了标记。
4、多线程与Arena
前面提到,ptmalloc2的一大改进就在于多线程,那么他是如何做到的呢?不难猜到,每个线程必定要维护一些独立的数据结构,并且对这些数据结构的访问是需要加锁的。的确,在ptmalloc2中,每个线程拥有自己的freelist,也就是维护空闲内存的一个链表;以及自己的arena,一段连续的堆内存区域。特别地,主线程的arena 叫做main_arena,非主线程arena叫做non_main_arena
注意:只有main_arena 可以访问 heap段和mmap 映射区域,non_main_arena只能访问mmap 映射区域。
注:线程较多时,互斥锁机制会导致性能下降。
当我们在程序中第一次申请内存时还没有heap段,因此132KB的heap段,也就是我们的main_arena,会被创建(通过brk()),无论我们申请的内存是多大。对于接T来的内存申请,malloc 都会从 main_arena 中尝试取出一块内存进行分配。如果空间不够,main_arena 可以通过brk()扩张;如果空闲空间太多,也可以缩小。
那么对于non_main_arena 呢?前面提到它只能访问mmap 映射区域,因为在创建时它就是由mmap()创建的—1MB的内存空间会被映射到进程地址空间,不过实际上只有132KB是可读写的,这132KB就是该线程的heap结构,或者叫 non_main_arena。
注:当然了,当申请的空间大于128KB且arena 中没有足够空间时,无论在哪个arena里都只能通过mmap()分配内存。
arena也不是和线程一对一的,实际上有数量限制:
For 32 bit systems: Number of arena =2* number of cores. For 64 bit systems: Number of arena =8* number of cores.
而当我们free一小块内存时,内存也不会直接归还给内核,而是给ptmalloc2让他去维护,后者会将空闲内存丢入bin中,或者说 freelist 中也可以。如果过了一会我们的程序又要申请内存,那么 ptmalloc2 就会从bin 中找一块空闲的内存进行分配,找不到的话才会去问内核要内存。
5、维护多个堆
前面提到, main_arena只有一个堆,并且可以灵活地放缩; non_main_arena则只能通过mmap()获得一个堆。那么如果 non_main_arena 里分配的堆内存不够了怎么办?很简单,再mmap()一次,创建一个新的堆。
所以,在 non_main_arena里,我们必须考虑如何维护多个堆的问题。这里我们会涉及三个头部:
heap_info:每个堆的头部,main_arena 是没有的
malloc_state : arena的头部, main_arena的这个部分是全局变量而不属于堆段
malloc_chunk :每个 chunk 的头部
具体一点,heap_info完整定义如下:
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;
而malloc_state的完整定义如下:
/** * 全局malloc状态管理 */ struct malloc_state { /* Serialize access. 同步访问互斥锁 */ __libc_lock_define (, mutex); /* Flags (formerly in max_fast). * 用于标记当前主分配区的状态 * */ int flags; /* Set if the fastbin chunks contain recently inserted free blocks. */ /* Note this is a bool but not all targets support atomics on booleans. */ /* 用于标记是否有fastchunk */ int have_fastchunks; /* Fastbins fast bins。 * fast bins是bins的高速缓冲区,大约有10个定长队列。 * 当用户释放一块不大于max_fast(默认值64)的chunk(一般小内存)的时候,会默认会被放到fast bins上。 * */ mfastbinptr fastbinsY[NFASTBINS]; /* Base of the topmost chunk -- not otherwise kept in a bin */ /* Top chunk :并不是所有的chunk都会被放到bins上。 * top chunk相当于分配区的顶部空闲内存,当bins上都不能满足内存分配要求的时候,就会来top chunk上分配。 */ mchunkptr top; /* The remainder from the most recent split of a small request */ mchunkptr last_remainder; /* Normal bins packed as described above * 常规 bins chunk的链表数组 * 1. unsorted bin:是bins的一个缓冲区。当用户释放的内存大于max_fast或者fast bins合并后的chunk都会进入unsorted bin上 * 2. small bins和large bins。small bins和large bins是真正用来放置chunk双向链表的。每个bin之间相差8个字节,并且通过上面的这个列表, * 可以快速定位到合适大小的空闲chunk。 * 3. 下标1是unsorted bin,2到63是small bin,64到126是large bin,共126个bin * */ mchunkptr bins[NBINS * 2 - 2]; /* Bitmap of bins * 表示bin数组当中某一个下标的bin是否为空,用来在分配的时候加速 * */ unsigned int binmap[BINMAPSIZE]; /* 分配区全局链表:分配区链表,主分配区放头部,新加入的分配区放main_arean.next 位置 Linked list */ struct malloc_state *next; /* 分配区空闲链表 Linked list for free arenas. Access to this field is serialized by free_list_lock in arena.c. */ struct malloc_state *next_free; /* Number of threads attached to this arena. 0 if the arena is on the free list. Access to this field is serialized by free_list_lock in arena.c. */ INTERNAL_SIZE_T attached_threads; /* Memory allocated from the system in this arena. */ INTERNAL_SIZE_T system_mem; INTERNAL_SIZE_T max_system_mem; };
ptmalloc的空闲chunk都是通过在malloc_state上的bins数组来管理的。