Skip to content

Binder大数据传输

追踪 Parcel Blob 的内联/Ashmem 分流、fd 传递、只读映射、长度验证和 RAII 释放,建立大数据传输的真实选择边界。

基于android-17.0.0_r1
AndroidBinderParcelAshmem

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

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

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/AIDL ParcelFileDescriptor 协议,不要把大量数据拆成多次同步 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 如何在发送端和接收端共同限制写入,以及为何这仍不解决业务数据的版本同步。