Binder大数据传输
大数据传输先决定 wire format,再决定 owner 和异常边界;普通 Parcel、Blob、fd-backed heap 和分片协议不是同一条实现。下面的选择图与正文的 Parcel.cpp 分支对应。
本文承接 Ashmem共享内存 和 MemoryHeapBase。大数据策略不是一句“超过 1MB 用共享内存”:Android 17 Parcel::writeBlob 已实现内联与 Ashmem fd 两条协议,选择取决于长度和 fd 是否允许;更大或长期复用的数据还应由业务显式管理共享 fd 生命周期。本文只讨论真实 Blob 协议和决策边界。
1. 协议类型
源码文件:frameworks/native/libs/binder/Parcel.cpp
Parcel.cpp 定义三种 blob tag:BLOB_INPLACE、BLOB_ASHMEM_IMMUTABLE、BLOB_ASHMEM_MUTABLE,并把内联阈值设为 BLOB_INPLACE_LIMIT = 16 * 1024。tag 是 Parcel 协议的一部分,接收端必须按 tag 选择读取路径,不能仅凭长度猜测 fd 是否存在。
2. 分流条件
源码文件:frameworks/native/libs/binder/Parcel.cpp
if (!mAllowFds || len <= BLOB_INPLACE_LIMIT) {
status = writeInt32(BLOB_INPLACE);
void* ptr = writeInplace(len);
outBlob->init(-1, ptr, len, false);
return NO_ERROR;
}小于等于 16 KiB 或 Parcel 不允许 fd 时,payload 内联到 transaction buffer。即使数据很大,mAllowFds == false 也不会自动得到共享内存方案,而会继续尝试内联并承受 Binder buffer 上限;调用者必须在协议设计时决定 fd 是否可用。
3. Ashmem路径
源码文件:frameworks/native/libs/binder/Parcel.cpp
int fd = ashmem_create_region("Parcel Blob", len);
void* ptr = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (!mutableCopy)
ashmem_set_prot_region(fd, PROT_READ);
writeInt32(mutableCopy ? BLOB_ASHMEM_MUTABLE : BLOB_ASHMEM_IMMUTABLE);
writeFileDescriptor(fd, true);发送端先创建区域并本地映射写入,immutable blob 在写完前把 region 降为只读,再把 fd 作为 BINDER_TYPE_FD 放入 Parcel。transaction 中传的是 tag 和 fd,不是 len 字节 payload;内容页由共享 mapping 支撑。
4. 接收校验
源码文件:frameworks/native/libs/binder/Parcel.cpp
readBlob 先读 tag。内联 tag 使用 readInplace(len);Ashmem tag 读取 fd,调用 ashmem_valid 验证类型,再用 ashmem_get_size_region 确认 fd size 不小于请求 len,最后按 mutable 属性选择 PROT_READ 或 PROT_READ|PROT_WRITE mmap。
长度由 API 调用者提供,不是从 tag 自动获取;接收端必须在 mapping 前执行 size 校验,避免把短 fd 区域当作长 blob 使用。
5. Blob生命周期
源码文件:frameworks/native/libs/binder/Parcel.cpp
WritableBlob/ReadableBlob 继承 Parcel::Blob。Ashmem blob 的 mFd 和 mData 由 Blob owner 管理;析构调用 release,若有 mapping 则 munmap,再清空字段。内联 blob 的 fd 为 -1,因此 release 不会解除 Parcel 自己的 buffer。
应用不能在 Blob 析构后保留 data() 指针;若需要跨调用缓存,应显式保存/dup fd 并建立自己的 mapping 生命周期。
6. 不可变与可变
mutableCopy=false 不是 C++ const,而是发送端通过 Ashmem protection 把 region 调为只读,接收端也以只读 mmap 打开。mutableCopy=true 允许双方映射可写页,必须由协议自行解决并发写入、版本号和消费者读到半写数据的问题。Binder 不为共享页提供同步屏障。
7. 失败路径
len > INT32_MAX:BAD_VALUE,防止负 int 转 size_t 变成超大请求。- Ashmem 创建失败:
NO_MEMORY。 mmap/protection 失败:返回负 errno 或状态并关闭 fd。- fd 不允许:内联;若内联写入失败则
NO_MEMORY,不会静默降级为共享页。 - 接收 fd 非 Ashmem、size 太小或 mmap 失败:
BAD_VALUE/NO_MEMORY。 BINDER_DISABLE_BLOB:整个 API 返回INVALID_OPERATION。
8. 与交易上限
内联 blob 计入目标 binder_alloc transaction buffer,和其他 Parcel 数据、object offsets、security context 共同竞争空间;Ashmem blob 只让 transaction 带 fd 元数据,内容不占该 transaction payload。它能降低单次 Binder buffer 压力,但 fd 数量、映射地址空间、共享页内存和生命周期成为新的资源边界。
9. 选择策略
- 小且一次性的数据:内联,避免 fd/mmap 管理成本。
- 超过 Blob 阈值、允许 fd 且短期读取:
writeBlob的 Ashmem 协议。 - 大且反复使用、多区间访问或需要明确 ownership:直接设计
MemoryHeapBase/AIDLParcelFileDescriptor协议,不要把大量数据拆成多次同步 Parcel。 - 需回传结果或高频流:结合 FMQ/专用共享内存协议,另行设计同步与 backpressure。
10. 模糊测试
源码文件:frameworks/native/libs/binder/tests/parcel_fuzzer/binder.cpp
Parcel fuzzer 会读取随机 length 的 ReadableBlob 并记录 status,覆盖 malformed tag、fd、长度和 mapping 输入。它主要验证 read path 不崩溃;业务代码仍要对 blob 内容格式、大小上限和 mutable 协议做语义校验。
11. 阅读检查
分别代入 4 KiB、32 KiB 且允许 fd、32 KiB 且禁止 fd 三种输入,指出 tag、payload 位置、fd 是否传递和主要失败边界。再解释 immutable blob 如何在发送端和接收端共同限制写入,以及为何这仍不解决业务数据的版本同步。
