iOS内存-堆(libmalloc)-简介

进程的虚拟内存是由多个内存区域组成的,不同的内存区域拥有不同的权限(r/w/e)
进程中每个内存区域的分配,释放都得经过内核系统调用(内核需要记录内存区域的信息, 内存区域由一个或多个页组成,进程以页的单位被内核记录在案,记录物理内存的映射关系,即页表)

全局变量 内存的分配发生在编译(决定大小)和装载期。编译器将全局变量放到数据区,在dyld装载时,通过系统调用(mmap/vm_map)给该全局变量所在的数据区(磁盘上)和当前进程的虚拟内存建立映射后,该变量也就分配了虚拟地址空间(没访问过该地址所在的页,该页不映射到具体的物理内存页,但页表对应的值的有效bit位被设置可访问)。除非通过系统调用将该数据区的内存注销,或者进程结束,否则该变量永远不会释放

栈变量 内存的分配发生在运行期,但是无法保存变量,在函数调用完后,出栈,变量也就“释放”了

堆变量 如果要变量内存分配发生在运行期,并且是持久,可以利用系统调用分配和堆内存管理

如果每次分配一个小内存都走系统调用分配,不仅效率慢(cpu执行状态切换以及内核代码过多的安全检查逻辑),而且会给内核内存带来压力(每向内核申请一块内存区域,内核会生成vm_objcet对象描述该区域信息)
并且内核申请的最小单位是页(4096B)

堆内存是通过系统调用分配许多大的内存区域,然后在用户态对这些内存区域进行管理

libmalloc

apple平台的堆内存libmalloc设计是空闲链表和位图结合

位图 libmalloc会将操作系统分配到的heap内存区域分为一个一个块(block)处理,每个块16字节(两个指针的大小),当请求malloc的size小于等于16字节时,分配1个block. 大于16小于等于32时,分配2个block. 以此类推(细节操作见后面几篇的堆内存,如描述块的medata bit设置等)

空闲链表 libmalloc会将free的内存块添加到free_list中,由于堆内存最小单位是block(16 字节),因此释放的内存能够储存pre和next指针信息 (细节操作见后面几篇的堆内存, 如空闲块的合并等)

Zone

libmalloc 通过Zone对象对堆内存区域通过位图和空闲链表进行集中管理
libmalloc中有两种类型的Zone, scalable _zonenano _zone (nano _zone有两个版本, nano _zone和nanov2 _zone)

nano_zone不常用,只有在进程环境变量MallocNanoZone为1情况下,才会开启
nano _zone只能分配小于256字节的内存,如果大于256, 会通过nano _zone的属性helper _zone分配,而helper _zone是一个scalable _zone

后面文章主要对这三种Zone进行分析,解析用户态堆设计, 即Zone是如何管理向内核申请的Heap Memory Region的

malloc_zones

malloc _zones是libmalloc的一个全局变量指针,指向一个zone指针数组,Zone对象通过系统调用(mach_ vm_ map)申请内存,申请后的Zone对象指针存放在zone指针数组中
zone指针数组通过系统调用mach_ vm _allocate申请内存空间

具体如下图

当添加一个Zone对象后,会通过mach_ vm _allocate重新申请一块内存区域,用来存放zone指针,将之前zone指针数组内存数据的copy到该新区域,并在后面加上新Zone对象的地址,之前的zone指针数组内存释放

变化如下

1
2
3
4
5
6
7
8
9
10
/* 
添加一个zone*到malloc_zones
*/
malloc_zone_register_while_locked

/*
第一个zone的通过懒加载初始化的
*/
_malloc_initialize_once
_malloc_initialize

malloc_zone _t

scalable _zone和nano _zone的第一个元素是malloc _zone _t,储存着malloc, free
calloc, valloc函数指针,即不同类型的zone, 在creat后设置不同的malloc函数处理。zone的malloc _zone _t设置后,会将当前页设置为只读

malloc _zone _t结构如下

zone结构如下

1
2
3
4
5
6
7
struct zone {
malloc_zone_t basic_zone;
uint8_t pad[PAGE_MAX_SIZE - sizeof(malloc_zone_t)];
·
·
·
}

default_zone

malloc 函数具体调用

1
malloc_zone_malloc(malloc_zone_t *zone, size_t size)

申请内存,参数zone是全局变量default _zone, 这个default _zone是scalable _zone类型,主要存放着函数指针信息。这个default _zone的malloc函数不会参与具体的内存分配,而是找到上面说到的全局变量malloc _zones的第一个zone,通过该zone属性malloc_zone _t中的malloc函数指针进行具体的malloc内存。free同理

过程如下图

后续…

以上是libmalloc简单的介绍以及最外层的大致结构,至于核心操作和实现都在scalable和nano的zone中

后面几篇就讲述scalable _zonenano _zone是怎么工作的