Skip to content

binder_alloc_buf分配

解析 Binder allocator 的尺寸归一化、红黑树 best-fit、buffer 分裂、页安装、异步额度、合并与 ENOSPC 诊断。

基于android-17.0.0_r1
AndroidBinder内存

binder_alloc_buf分配 ​

分配器同时维护地址排序的 buffer 区间和按大小索引的空闲树;理解 best-fit、分裂、装页、quota 和相邻合并,需要把一次分配的状态变化串起来。

本文承接 Binder one-copy 和 Binder锁竞争。binder_alloc_new_buf 不只是从一块大数组切内存:它要同时维护按地址排列的 buffer 链、按大小索引的红黑树、按页的 LRU、async 配额和用户映射。本文解释一次分配如何经过这些 owner,以及为什么“总 free 足够”仍可能 -ENOSPC。

1. 尺寸归一化 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

c
tmp = ALIGN(data_size, sizeof(void *)) + ALIGN(offsets_size, sizeof(void *));
total = tmp + ALIGN(extra_buffers_size, sizeof(void *));
total = max(total, sizeof(void *));

数据、对象偏移和额外安全上下文分别按指针宽度对齐,并检查整数溢出;零长度请求也至少占一个指针大小以获得唯一地址。分配器按归一化总 size 切 buffer,但 buffer 的 data_size/offsets_size/extra_buffers_size 仍保存原始逻辑尺寸。

2. 空间索引 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

空闲 buffer 同时存在地址链和 free_buffers 红黑树。binder_alloc_new_buf_locked 从红黑树按 size 搜索最小可容纳块(best fit);找不到单个连续块时即使 total_free_size 足够也返回 -ENOSPC。debug_no_space_locked 会记录 free buffer 数量、总量和最大连续块,诊断碎片必须看最后一个值。

3. Buffer分裂 ​

找到 oversized free buffer 后,分配器把 new_buffer->user_data = buffer->user_data + size,把尾部插回 free tree,前部变为 allocated。分裂只改变元数据和红黑树节点,页不会立刻全部安装;后续 binder_install_buffer_pages 按实际范围处理页。

4. 页安装 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

分配器先从 page freelist 删除目标页范围,再在 binder_install_buffer_pages 中为缺页安装物理页。若页安装失败,外层 binder_alloc_new_buf 调用 binder_alloc_free_buf 回滚 buffer。分配成功不代表所有页都已驻留,页生命周期还受 LRU 回收影响。

5. Async额度 ​

初始化时 free_async_space = buffer_size / 2。异步分配在 best-fit 前先检查额度:不足直接 -ENOSPC;成功后按归一化 size 扣减。释放 async buffer 时按实际 buffer_size 返还额度,而不是只返还原始 data_size,避免分裂/对齐造成账目不一致。

6. 分配锁 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

binder_alloc_new_buf 预分配下一个 binder_buffer 结构,再持有 alloc->mutex 修改 free/allocated tree;解锁后安装页面。这样把可能较慢的页操作放到 mutex 外,降低同一进程并发事务的分配竞争。

7. 释放合并 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

c
if (next->free) {
    rb_erase(&next->rb_node, &alloc->free_buffers);
    binder_delete_free_buffer(alloc, next);
}
if (prev->free) {
    binder_delete_free_buffer(alloc, buffer);
    rb_erase(&prev->rb_node, &alloc->free_buffers);
    buffer = prev;
}
binder_insert_free_buffer(alloc, buffer);

释放先从 allocated tree 删除,再与相邻 free buffer 合并,最后重新插入 free tree。合并按地址相邻关系进行;两个远隔的小洞不会自动拼成连续大块。

8. 失败类型 ​

  • 未完成 mmap:-ESRCH,通常表示目标进程死亡或 allocator 正在清理。
  • 尺寸溢出/非法:-EINVAL。
  • struct/page 分配失败:-ENOMEM。
  • 连续地址空间不足或 async 配额不足:-ENOSPC。
  • trace hook 主动拒绝 async 空间:-EPERM。

驱动将这些错误映射为不同 Binder reply;排查时必须保留原 errno,不能只记录“Transaction failed”。

9. KUnit验证 ​

源码文件:kernel/common/drivers/android/tests/binder_alloc_kunit.c

KUnit 反复调用 binder_alloc_new_buf,检查每个 buffer 覆盖页已安装,再按不同顺序释放并验证页面进入 LRU。它验证的是分配/释放和页状态,不等价于真实跨进程 Parcel 的对象 fixup 测试。

10. 诊断输出 ​

debug_no_space_locked 输出 allocated/free 总量、buffer 数量和最大 free size;BINDER_DEBUG_BUFFER_ALLOC、BINDER_DEBUG_BUFFER_ALLOC_ASYNC 记录 size、async 余额和分配结果。遇到 ENOSPC,应同时读取目标 proc 的 mmap 状态、最大连续 free block 和 free async space。

11. 阅读检查 ​

给定三个请求:普通 4K、带大量对象偏移的 4K、oneway 200K,分别指出归一化 size、tree 选择、async 扣账和页安装步骤。再构造“总 free 1MB、最大连续块 64K、请求 128K”的结果,说明为何仍是 -ENOSPC。