Libc Musl 简析
Musl 是一个轻量级的C标准库,设计作为 GNU C library (glibc)、 uClibc 或 Android Bionic 的替代用于嵌入式操作系统和移动设备
它遵循 POSIX 2008 规格和 C99 标准,采用 MIT 许可证授权,使用 Musl 的 Linux 发行版和项目包括 sabotage
, bootstrap-linux
, LightCube OS
等
Libc Musl 堆管理器 - 1.2.x
1.2.x 和 1.1.x 堆管理结构几乎完全不同:
- 1.2.x 新版本使用 mallocng
- 1.1.x 旧版本使用 oidmalloc
1.2.x Musl 关键结构体
在 Musl 中有几大重要的结构体:
malloc_context:musl libc 的全局管理结构指针,存放在 libc.so
的 .bss
段
1 | struct malloc_context { |
- 数组
active
中的每个条目都是一个 meta 链表,其含义为 “有可用 chunk 的 meta 链表”(当一个 meta 被填满时,它将不会出现在active
中) - musl 把 chunk 大小分为48类,用 size_to_class 进行计算(与
*active[48]
对应)
1 | const uint16_t size_classes[] = { |
meta_area:mallocng 在分配 meta 时,总是先分配一页的内存,然后划分为多个 meta 区域,而该页的最开始存放的就是 meta_area
1 | struct meta_area { |
- slots 结构体数组可以看作是一个 meta 的集合
meta:组成一个 meta 队列
1 | struct meta { |
- meta 可以是 brk 分配的,可以是 mmap 映射的
group:由多个相同大小的 chunk 以及一些控制信息组成的(并且物理相邻)
1 | struct group { |
- group 只能是 mmap 映射的
- 这里的 storage 就是我们习惯中理解的 chunk
chunk 没有专门在代码中定义,但总体结构如下:
1 | struct chunk { |
- group 中第一个 chunk 的 pre_user_data 为一个指针,指向这个 group 的 meta 元数据(其实就是
group->meta
指针) - 其余 chunk 使用 offset 表示与所属 group 中第一个 chunk 的偏移,而 pre_user_data 用于存储上一个 chunk 的数据
这些结构体的关系如下图:
- 相同类型的 meta 之间通过双向链表进行连接,头节点地址存储于
malloc_context->active
中 - 而 meta_area 会形成一条单向链表,其头节点和尾节点指针都存储于
malloc_context
中
1.2.x Musl 关键函数
malloc:核心分配函数
1 | void *malloc(size_t n) |
- 判断是否需要调用 mmap
- 先计算 chunk size 的范围,判断
ctx.active[sc]
中对应的 meta,此时有3种情况:- meta 为 NULL:
- 查看是否可以获取更大的 meta,进入下一步(判断 meta 是否装满)
- meta 存在但是装满:
- 调用
alloc_slot
尝试分配新的meta
,进入下一步(返回目标 chunk)
- 调用
- meta 存在并且有空闲:
- 计算对应 chunk 的 idx,返回目标 chunk
- meta 为 NULL:
其中有一些重要的函数:alloc_meta
,alloc_slot
alloc_meta:分配一个 meta
1 |
1 | struct meta *alloc_meta(void) |
alloc_slot:申请新的 chunk(重新分配 meta)
1 | static int alloc_slot(int sc, size_t req) |
alloc_group:给 sc 分配一个 meta 再分配一个新的 group 结构体
1 | static struct meta *alloc_group(int sc, size_t req) |
free:释放 chunk 的核心函数
1 | void free(void *p) |
nontrivial_free:设置关于属于该 group 的 meta
1 | static struct mapinfo nontrivial_free(struct meta *g, int i) |