macos 内存分配学习笔记
字数 2218 2025-08-24 07:48:09

macOS 内存分配学习笔记

基础概念

在 macOS 中利用的是 libcmalloc 内存分配器,堆块分为三种类型:

  1. tiny (Q = 16)

    • 大小范围:tiny < 1009B
    • 最小单位为 quantum,大小为 0x10 字节
    • 记录堆块大小的信息为以 quantum 为单位的 msize
  2. small (Q = 512)

    • 大小范围:1008B < small < 127KB
    • block 在 region 中的大小为 0x200
  3. large

    • 大小范围:127KB < large

每个 magazine 有个 cache 区域可以用来快速分配释放堆。

内存结构

整体结构

每个进程包含 3 个区域:

  • tiny rack (1MB)
  • small rack (8MB)
  • large allocations

每个区域里面包含多个 magazine 区域(活动可变),magazine 区域中又包含多个 Region。

Region 结构

Region 包含三部分:

  1. 以 Q 为单位的内存 block
  2. 负责将各个 Region 关联起来的 trailer
  3. 记录 chunk 信息的 metadata

tiny Region 结构

tiny Region {
    Q (1 Q = 16) * 64520 
    region_trailer_t trailer
    metadata [64520 / sizeof(uint32_t)] {
        bitmaps[0]: uint32_t header = 描述哪个 block 是起始 chunk
        bitmaps[1]: uint32_t inuse = 描述 chunk 状态 (busy/free)
    }
}

small Region 结构

Small Region {
    Q (1 Q = 512) * 16320 
    region_trailer_t trailer
    metadata [16320] {
        bitmaps[0]: uint16_t msize = 最高一位描述 chunk 状态 (busy/free), 其余位描述 chunk  Q 
    }
}

tiny 堆块管理

tiny chunk 特性

  • 类似于 Linux 下的堆块
  • Free 后会被放在对应的 freelist 上
  • Free 后的 chunk 会写入数据:
    • checksum(prev_pointer)
    • checksum(next_pointer)
    • msize

指针校验机制

  • 使用 checksum 保护指针:
    • Checksum = SumofEverybytes(ptr ^ cookie) & 0xf
    • 随机生成一个 cookie
    • (checksum << 56) | (pointer >> 4) 保存指针

tiny magazine 结构

  • map_last_free: 保存最后一次 free 的 chunk
  • map_last_free_msize: 保存最后一次 free 的 chunk 大小
  • map_last_free_rgn: 保存最后一次 free 的 chunk region
  • free_list: 以 0x10 为单位的 64 个链表,类似于 Linux 的 smallbins

tiny 分配机制

  1. 检查 tiny magazine 中的 mag_last_free_msize 是否匹配
  2. 如果不匹配,从 free_list 中查找
    • 对找到的 chunk 的 next 指针进行解密
  3. 如果没有合适大小的 chunk,从能容纳的 list 中取出并分割
  4. 如果 free_list 中没有可用 chunk,从 tiny region end 中申请
  5. 更新 metadata 并返回 chunk

tiny 释放机制

  1. 如果 msize < 0x10,与 cache 中的 freed 堆块交换
  2. 执行 tiny_free_no_lock 进行合并操作:
    • 利用上一个 freed chunk 末尾的 msize 定位上一个 freed chunk
    • 利用当前 chunk 的 msize 定位下一个 freed chunk
  3. 合并前执行 unlink 操作

small 堆块管理

small chunk 结构

  • 与 tiny 不同,pre 和 next 指针使用原始指针
  • pre 和 next 指针后有 1 字节存储 checksum 值(填充为 8 字节)
  • 存在 oob free chunk 用于页面对齐,没有元数据

small magazine 结构

  • 前面的 map_last_xxx 作为 cache
  • free_list 以 0x200 为单位,保存 oob_free_entry
  • oob_free_entry 保存在 small Region 末尾

small Region 详细结构

  • small_region_end
  • small_meta_words
  • smal_oob_free_entries
  • block 大小为 0x200
  • region 默认大小为 0x800000
  • block 个数为 16319

small 分配机制

  1. 检查 magazine 中的 cache 是否匹配
  2. 从 free_list 中查找
    • 对普通 chunk 执行 unchecksum next 指针
    • oob chunk 直接使用原始数据
  3. 执行 unlink 操作,验证 next->prev == ptr
  4. 修改 small Region 末尾的 metadata

small 释放机制

  1. 把 chunk 放入 cache
  2. 根据 prev 指针和 metadata 定位上一个堆块
  3. 定位下一个堆块
  4. 合并相邻的 freed chunk
  5. 执行 unlink 操作前验证 prev->next == next->prev == ptr

安全机制

  • 使用 checksum 保护指针,防止堆元数据被溢出破坏
  • 随机生成 cookie 用于 checksum 计算
  • 合并前严格验证链表完整性

性能优化

  • 使用 cache 机制提高分配速度
  • 采用类似 slab 的分级管理
  • 通过 magazine 实现多线程高效管理
macOS 内存分配学习笔记 基础概念 在 macOS 中利用的是 libcmalloc 内存分配器,堆块分为三种类型: tiny (Q = 16) 大小范围:tiny < 1009B 最小单位为 quantum ,大小为 0x10 字节 记录堆块大小的信息为以 quantum 为单位的 msize small (Q = 512) 大小范围:1008B < small < 127KB block 在 region 中的大小为 0x200 large 大小范围:127KB < large 每个 magazine 有个 cache 区域可以用来快速分配释放堆。 内存结构 整体结构 每个进程包含 3 个区域: tiny rack (1MB) small rack (8MB) large allocations 每个区域里面包含多个 magazine 区域(活动可变),magazine 区域中又包含多个 Region。 Region 结构 Region 包含三部分: 以 Q 为单位的内存 block 负责将各个 Region 关联起来的 trailer 记录 chunk 信息的 metadata tiny Region 结构 small Region 结构 tiny 堆块管理 tiny chunk 特性 类似于 Linux 下的堆块 Free 后会被放在对应的 freelist 上 Free 后的 chunk 会写入数据: checksum(prev_ pointer) checksum(next_ pointer) msize 指针校验机制 使用 checksum 保护指针: Checksum = SumofEverybytes(ptr ^ cookie) & 0xf 随机生成一个 cookie 将 (checksum << 56) | (pointer >> 4) 保存指针 tiny magazine 结构 map_last_free : 保存最后一次 free 的 chunk map_last_free_msize : 保存最后一次 free 的 chunk 大小 map_last_free_rgn : 保存最后一次 free 的 chunk region free_list : 以 0x10 为单位的 64 个链表,类似于 Linux 的 smallbins tiny 分配机制 检查 tiny magazine 中的 mag_last_free_msize 是否匹配 如果不匹配,从 free_ list 中查找 对找到的 chunk 的 next 指针进行解密 如果没有合适大小的 chunk,从能容纳的 list 中取出并分割 如果 free_ list 中没有可用 chunk,从 tiny region end 中申请 更新 metadata 并返回 chunk tiny 释放机制 如果 msize < 0x10,与 cache 中的 freed 堆块交换 执行 tiny_free_no_lock 进行合并操作: 利用上一个 freed chunk 末尾的 msize 定位上一个 freed chunk 利用当前 chunk 的 msize 定位下一个 freed chunk 合并前执行 unlink 操作 small 堆块管理 small chunk 结构 与 tiny 不同,pre 和 next 指针使用原始指针 pre 和 next 指针后有 1 字节存储 checksum 值(填充为 8 字节) 存在 oob free chunk 用于页面对齐,没有元数据 small magazine 结构 前面的 map_last_xxx 作为 cache free_list 以 0x200 为单位,保存 oob_free_entry oob_free_entry 保存在 small Region 末尾 small Region 详细结构 small_region_end small_meta_words smal_oob_free_entries block 大小为 0x200 region 默认大小为 0x800000 block 个数为 16319 small 分配机制 检查 magazine 中的 cache 是否匹配 从 free_ list 中查找 对普通 chunk 执行 unchecksum next 指针 oob chunk 直接使用原始数据 执行 unlink 操作,验证 next->prev == ptr 修改 small Region 末尾的 metadata small 释放机制 把 chunk 放入 cache 根据 prev 指针和 metadata 定位上一个堆块 定位下一个堆块 合并相邻的 freed chunk 执行 unlink 操作前验证 prev->next == next->prev == ptr 安全机制 使用 checksum 保护指针,防止堆元数据被溢出破坏 随机生成 cookie 用于 checksum 计算 合并前严格验证链表完整性 性能优化 使用 cache 机制提高分配速度 采用类似 slab 的分级管理 通过 magazine 实现多线程高效管理