准备用一个系列来总结一下内存管理涉及到的相关知识,范围从底层的数据结构和算法,到上层的API的使用,这里的内存管理,目前打算主要是侧重在堆的管理,本文作为一个引子,先粗略讲一下虚拟地址空间、堆管理、arena,接下来会陆续讲一下堆管理算法、malloc实现、new/delete等C++内容的相关实现等,但也希望如果以后有时间的话,也可以把栈和甚至和cache相关的内容也加到这个系列里,现在大概是这么规划的,不知道能写几篇,先努力写着看吧。
(免费订阅,永久学习)学习地址: Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂
一、虚拟地址空间
说内存管理,就必须要先说虚拟地址空间,这是现代程序运行的根本,基本概念大家都知道就不在这里赘述了,尽量把这部分的精华部分尽量简单的总结出来,先看一下进程虚拟地址空间的总体布局,以32位Linux系统为例:
图1
基于上图的虚拟地址空间布局来简单说下ELF文件是怎样映射到进程虚拟地址空间的,ELF文件被组织成如下图左列出的一系列section,其中具有相同属性(R/W/E)的section再组成一个segment,以segment为单位映射到进程的虚拟地址空间,其中虚拟地址空间中的segment要做到页大小对齐,下图也一同简要展示了虚拟地址空间到物理地址空间的映射,通过MMU完成,具体的细节请大家网上或者书上查资料,这都是比较基本的概念,这里就不赘述了:
图2
其它系统原理类似,都是将可执行程序组织成若干segment连同用到的动态库和kernel映射到进程的虚拟地址空间,主要差别在于不同segment映射的起始地址、大小不同等,比如32位Linux系统的Text segment起址是0x08048000,64位Linux系统的Text segment起址是0x00400000,再比如相对32位Linux系统的kernel space是1G,32位Windows的kernel space是2G等等,后面相关内容全部以32位Linux系统为例说明。
二、堆
从图1中可以看到堆所处的位置,它位于Data segment的上面,这部分堆空间是程序默认创建的,主要是给程序的主线程用的,在特定的情况下(比如malloc申请大内存的时候、多线程的时候等),堆的内容还会扩展到图1中的Memory mapping segment区域,假如一共存在三个堆区域,它们的分布如下图所示:
图3
应用申请堆空间,主要是通过三个系统函数完成的:brk、sbrk、mmap,在前面图3中,主线程的堆空间是用brk或者sbrk申请的,heap1和heap2这两个Memory mapping segment是用mmap申请的。其中brk和sbrk使用的是同一个系统调用,根本原理是改变上面图3中的brk ptr这个指针的位置,mmap的功能很强大,在申请堆空间的时候主要使用它的Anonymous mapping的功能,下面是一个从上到下的简略调用关系:
图4
在堆中,内存被管理的基本单位是块,glibc中的名字叫chunk,为了不引起歧义,这个系列以后全部用chunk这个词来表示堆中的内存块,如下图所示(其中的free chunk会用后续文章介绍的某种算法维护,这里没有画出):
图5
三、arena
很多人可能没听说过这个概念,这是Linux堆管理中常用的一个术语,这个词的意思是竞技场,感觉这个词用的非常传神,堆这块地方可不就是一个竞技场么。下面从glibc实现的角度简单说下arena,在glibc中,arena的个数是有限制的,限制条件如下:
system | arena number |
32bit | 2 x cpu_core_number + 1 |
64bit | 8 x cpu_core_number + 1 |
每个线程一定对应一个arena,但是一个arena可以给多个线程使用,同时一个arena可以由一个或者多个图3中的堆区组成,它们之间的关系如下图:
图6
在glibc中,每一个heap的开始有一个heap_info的描述头(注意main thread所用到的那个arena对应的heap没有这个描述头,也就是紧挨着data segment的那个使用brk/sbrk来动态扩展的heap),定义如下:
// malloc/arena.c
typedef struct _heap_info {
// Arena for this heap
struct malloc_state *ar_ptr;
// Previous heap
struct _heap_info *prev;
// Current size in bytes
size_t size;
// Size in bytes that has been mprotected PROT_READ|PROT_WRITE
size_t mprotect_size;
// padding
char pad[];
} heap_info;
从上面代码可以看出,如果一个arena用到多个heap,那么这些heap通过prev这个指针连接起来,并且通过ar_ptr这个指针指向所属的arena,arena在glibc中对应的数据结构是malloc_state,定义如下:
// malloc/malloc.c
struct malloc_state {
// 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
int have_fastchunks;
// Fastbins
mfastbinptr fastbinsY[NFASTBINS];
// Base of the topmost chunk -- not otherwise kept in a bin
mchunkptr top;
// The remainder from the most recent split of a small request
mchunkptr last_remainder;
// Normal bins packed as described above
mchunkptr bins[NBINS * 2 - 2];
// Bitmap of bins
unsigned int binmap[BINMAPSIZE];
// Linked list
struct malloc_state *next;
// Linked list for free arenas.
struct malloc_state *next_free;
// Number of threads attached to this arena
INTERNAL_SIZE_T attached_threads;
// Memory allocated from the system in this arena
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
这里只简单列一下和arena相关的两个数据结构,其中具体的字段含义等以后将malloc实现的时候再细说,这里先贴出来大家好有个基本概念。
四、reference
这个系列主要参考的资料有下面这些,后续的文章没有特殊情况的话就不再提及了,大家有兴趣的话也可以直接去翻看下面所述的章节:
- 深入理解计算机系统:虚拟存储器这一章
- 程序员的自我修养:可执行文件的装载与进程这一章
- Effective C++:定制new和delete这一章
- Modern Effective C++:Smart Pointers这一章
- C++并发编程实战:C++内存模型和原子类型操作这一章
- GCC的官方文档
- cppreference的内存管理相关函数的manual
- 网上的一些博文:
- https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/
- https://sploitfun.wordpress.com/2015/02/11/syscalls-used-by-malloc
- https://fantiq.github.io/2019/05/13/malloc%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E5%88%9D%E5%A7%8B%E5%8C%96%E4%B8%8Earena%E7%9A%84%E5%88%9B%E5%BB%BA/
- https://introspelliam.github.io/archives/
- Safe-Linking - Eliminating a 20 year-old malloc() exploit primitive - Check Point Research
- tcache 源码分析及利用思路
原文链接:https://zhuanlan.zhihu.com/p/374431199 原文作者:月踏