MemoryDealer内存分配器
MemoryDealer 把一个共享堆拆成可复用的 MemoryBase 子块;分配、释放和 Binder 传递的 owner 不同,不能把子块析构误认为共享 fd 已关闭。
本文面向已读过 Ashmem共享内存 和 Binder one-copy 的读者。MemoryDealer 是用户态对一个共享 heap 的二级分配器:它先创建并映射整块 MemoryHeapBase,再把偏移/长度切成多个 MemoryBase 子对象通过 Binder 共享。它不管理 Binder transaction buffer,也不替代内核 allocator。
1. 堆与分配器
源码文件:frameworks/native/libs/binder/MemoryDealer.cpp
MemoryDealer::MemoryDealer(size_t size, const char* name, uint32_t flags)
: mHeap(sp<MemoryHeapBase>::make(size, flags, name)),
mAllocator(new SimpleBestFitAllocator(size)) {}mHeap 是真正的 Ashmem/memfd 映射,mAllocator 只记录该 heap 内的逻辑区间。两者 size 都按页对齐,但 allocator 额外以 32 字节 cache-line 边界分配子块。
2. 子块对象
源码文件:frameworks/native/libs/binder/MemoryBase.cpp
MemoryBase 保存 sp<IMemoryHeap>、mOffset、mSize;getMemory 只返回 heap 和区间,不复制数据。MemoryDealer::allocate 将 offset/size 包装为内部 Allocation,客户端拿到的是一个描述子区间的 Binder 对象。
3. Best-fit分配
源码文件:frameworks/native/libs/binder/MemoryDealer.cpp
SimpleBestFitAllocator::alloc 在双向 chunk 链表中寻找最小可容纳 free chunk。请求按 32 字节单位向上取整;PAGE_ALIGNED 会增加前置 padding,使返回 offset 对齐页边界。找到后可能分裂前置 padding 和尾部 free chunk。
if (cur->free && cur->size >= (size + extra)) {
if (!free_chunk || cur->size < free_chunk->size)
free_chunk = cur;
}best-fit 减少大洞被小请求消耗,但仍会产生碎片;没有单个连续 chunk 时返回 NO_MEMORY,即使链表 free 总量足够。
4. 分配锁
SimpleBestFitAllocator::allocate/deallocate/dump 都使用 mLock。锁只保护用户态 chunk 链表;heap 映射和 Binder 事务由其他对象/线程管理。把 MemoryDealer 锁与服务业务锁一起跨 IPC 持有,会形成用户态死锁风险。
5. 释放合并
源码文件:frameworks/native/libs/binder/MemoryDealer.cpp
Allocation 析构时读取自身 offset/size,调用 mDealer->deallocate(offset)。allocator 找到起始 chunk,标记 free,并把相邻 free chunk 合并。size 为 0 的 Allocation 特殊处理:它没有 allocator 记录,offset 可能为 0,不能进入 deallocate。
6. 生命周期
Allocation 持有 sp<MemoryDealer>,因此即使外部 dealer 引用释放,heap 和 allocator 仍保持到最后一个子块析构。反过来,如果客户端只保存裸 offset 而丢失 IMemory 强引用,子块可能提前释放并让该区间重新分配给其他使用者。
7. Binder共享
MemoryBase 通过生成的 IMemory Binder 接口让远端获取 heap、offset、size;Binder 传递的是 heap 的 fd/对象和区间元数据,不是子块 payload。接收端映射同一 heap 后加上 offset 访问数据,多个子块可以共享一个 fd 和页表 backing。
8. 对齐差异
MemoryDealer 的 32 字节对齐服务于 cache line;MemoryHeapBase 以 page 对齐创建映射;Binder Parcel/allocator 又有 pointer-size 对齐。不要用某一层的 offset 对齐假设替代其他层:传入 PAGE_ALIGNED 只影响 allocator 返回子块,不能改变 heap 起始地址或 Binder buffer 对齐。
9. 后端与保护
源码文件:frameworks/native/libs/binder/MemoryHeapBase.cpp
MemoryHeapBase 可根据 flags 使用 Ashmem 或强制 memfd;READ_ONLY 会设置 Ashmem protection 或 memfd sealing。MemoryDealer 只接收 heap,不重新定义 fd 权限;子块读写能力受底层映射和 fd seal 共同限制。
10. 测试与模糊输入
源码文件:frameworks/native/libs/binder/tests/unit_fuzzers/MemoryDealerFuzz.cpp
fuzzer 反复生成 allocate/deallocate/dump 操作,覆盖随机 size、offset 和释放顺序,目标是检查链表合并、重复释放和边界输入不会破坏 allocator。它验证的是用户态分配器健壮性,不证明跨进程客户端能安全使用悬空 IMemory。
11. 对照内核
MemoryDealer:用户态、一个共享 heap 内的逻辑子块、mLock、32 字节对齐;Binder allocator:内核态、每个进程 transaction buffer、alloc->mutex、pointer 对齐和 async quota。两者都使用 best-fit/合并思想,但 owner、生命周期、错误码和传输目的完全不同。
12. 阅读检查
从 MemoryDealer::allocate 追到 MemoryBase::getMemory,说明数据为什么没有复制;再给出“free 总量足够但连续 chunk 不足”“Allocation 析构后仍使用裸指针”“READ_ONLY memfd”三种结果,分别定位 allocator、引用生命周期和 fd seal 的源码。
