第4章:Binder通信数据流:Transaction发起、数据序列化、驱动转发、目标端反序列化与回调

模块边界与系统分层

Binder一次完整的事务(Transaction)跨越三个层级:用户空间客户端、Binder内核驱动、用户空间服务端。数据在每一层发生形态与地址空间的转换,架构上保持了严格的边界隔离。

层级角色模块边界描述
Client 端(用户空间)发起者持有 Binder 代理对象(BpBinder),调用代理方法 -> 构造 Parcel 数据 -> 进入 ioctl
Binder 驱动(内核空间)转发/调度者接收 ioctl 命令 BINDER_WRITE_READ,管理进程间内存映射与线程池,完成节点与事务匹配
Server 端(用户空间)目标执行者驱动唤醒等待的Binder线程 -> 从内核空间拷贝数据到本地 Parcel -> 执行反序列化并回调业务方法

组件职责与数据流全景

数据流从Client到Server经历 6 个核心阶段,每个阶段均有明确的组件负责。

  1. Transaction 发起:客户端通过 BpBinder::transact() 将调用意图写入 Parcel 对象。
  2. 数据序列化:Parcel 将结构化参数( int、String、IBinder 引用、文件描述符等)编码为扁平字节流。
  3. 内核入口:调用 ioctl(fd, BINDER_WRITE_READ, &bwr) 将 Parcel 数据从用户空间拷贝到内核空间缓存。
  4. 驱动转发:Binder 驱动根据目标 handle 查询服务端节点,拷贝数据到目标进程的 mmap 缓冲区,发布 BC_TRANSACTION。
  5. 反序列化:Server 端 Binder 线程收到数据,从内核缓冲区拷贝到本地 Parcel 对象,解码出原始参数。
  6. 回调:调用 BnInterface::onTransact() 方法,分发到具体业务函数执行。

关键设计要点:序列化/反序列化由同一个 Parcel 规范保证,驱动不做任何业务解析,只负责“搬运”与“路由”。这是 Binder 达到高效与安全解耦的基础。

控制流与信任链路

控制流由 Client 发起,驱动维护同步/异步标志。信任链路建立在以下几个环节:

  • 身份校验:驱动会检查 Client 的 UID/PID,并附在 transaction 的数据头中传递给 Server。
  • 文件描述符安全:驱动在转发 Parcel::writeFileDescriptor 时,会进行权限与跨进程 fd 映射,防止提权。
  • 对象引用计数:驱动维护内核端 Binder 节点引用,防止对象在传输中被非法释放。

选型权衡

决策点选项利弊分析
数据拷贝次数一次拷贝(mmap) vs 两次拷贝Binder 驱动使用内核 mmap 映射只做一次物理拷贝,优于传统 socket 或共享内存的两次/三次拷贝
序列化格式自描述 Parcel vs Protocol BuffersParcel 无 schema 校验,性能高但调试困难;ProtoBuf 可读性强但额外序列化开销
事务线程模型同步阻塞 vs 异步回调同步简单但可能阻塞调用线程;异步需管理回调注册与生命周期,复杂度上升

风险点

  • Parcel 越界:反序列化时如果提供了错误的数据类型(混淆 String 与 Int),可能导致 OOB 读写。应严格使用 write/read 配对。
  • 大块数据阻塞:驱动虽然采用 mmap,但一次传输超过 1MB 的数据仍会引发长时间内核拷贝和线程饥饿。建议拆分或改用 Ashmem + FileDescriptor。
  • 文件描述符泄露:Server 端未及时 close 从 Parcel 中读取的 fd,会导致进程 fd 耗尽。需在 onTransact 中明确管理 fd 生命周期。
  • 事务重入死锁:若 Server 回调中再次发起 Binder 调用且目标锁被 Client 持有,可产生循环等待。应在架构层使用线程池或异步通道避免。

⚠️ 内核转发与用户空间数据一致性问题:驱动在拷贝数据到目标进程 mmap 区后,会写回一个 BC_TRANSACTION_COMPLETE 状态给 Client。如果 Client 在收到完成前就释放 Parcel 数据,会导致内核读脏数据。严格遵循驱动约定的同步等待模式可以避免此问题。

落地步骤

  1. 定义 AIDL 接口,编译生成 Stub/Proxy 骨架。
  2. 在 Proxy 中实现 transact(),注入 Parcel 写入逻辑。
  3. 在 Stub 中实现 onTransact(),读取 Parcel 并调用纯业务方法。
  4. 配置 Android.mk / Android.bp 包含 libbinder 依赖。
  5. 在 Service 主线程或 Binder 线程池中注册 service。
  6. 验证数据流:抓取 binder transaction 日志(adb root && echo 1 > /d/binder/stats)。

验收标准

  • 客户端发起 1000 次简单事务(传递 int+String),全部成功返回,无崩溃。
  • 服务器端反序列化后取出的 UID 与客户端实际 UID 一致。
  • 传递 4MB 数据时(需使用 Ashmem 方案),耗时不超过 50ms 且无 OOM。
  • 文件描述符传递:Server 端能读出正确 fd 且可操作原始文件节点。
  • 同步阻塞场景:客户端收到返回值的时间误差 < 5ms(排除调度抖动)。

数据流核心流程(SVG)

Binder Transaction 数据流(六阶段) ① 客户端发起调用 ② Parcel 序列化 ③ ioctl 进入内核 ④ 驱动路由与拷贝 ⑤ Server 反序列化 ⑥ onTransact 回调 数据流详细说明: ① Client 端通过 BpBinder::transact() 将参数写入 Parcel,此时仍在用户空间。 ② Parcel 对原始数据类型(int/String/flat_binder_object)进行二进制打包。 ③ ioctl 触发 BINDER_WRITE_READ,驱动从用户空间拷贝数据到内核空间缓存。 ④ 驱动根据 target.handle 找到目标进程的 mmap 区,将数据拷贝至内核为 Server 预留的缓冲区。 ⑤ Server 端 Binder 线程被唤醒,从内核区拷贝数据到本地 Parcel(一次最终用户空间拷贝)。 ⑥ Parcel.readXxx() 还原原始参数,调用业务函数并构造返回 Parcel,反向流回 Client。 ⚠️ 整个链路中内核只做“拷贝+路由”,不对业务数据做任何解析。 同步模式:Client 在 ioctl 中阻塞等待 Server 返回;异步模式:驱动直接返回,Server 后续通过 callback 通知。

核心接口示意(非完整代码)

下面展示 Client 端发起事务的核心步骤,仅用于说明边界,不包含全部错误处理。

// Client 端:通过 BpBinder 发起 Transaction
Parcel data, reply;
data.writeInt32(param1);
data.writeString16(param2);
status_t ret = remote()->transact(CODE_HELLO, data, &reply,
                                  TF_ONESHOOT | TF_ACCEPT_FDS);
if (ret == NO_ERROR) {
    int32_t result = reply.readInt32();
}
// Server 端:onTransact 反序列化与回调
status_t BnHelloService::onTransact(uint32_t code,
                                    const Parcel& data,
                                    Parcel* reply,
                                    uint32_t flags) {
    switch (code) {
        case CODE_HELLO: {
            CHECK_INTERFACE(IGreetingService, data, reply);
            int32_t a = data.readInt32();
            String16 b = data.readString16();
            int32_t res = handleHello(a, b);
            reply->writeInt32(res);
            return NO_ERROR;
        }
    }
    return BBinder::onTransact(code, data, reply, flags);
}

设计建议:在编写自定义 Binder 服务时,始终确保 Parcel 的 write/read 顺序与类型严格对称。使用 Parcel::enforceInterface 或 CHECK_INTERFACE 宏来校验接口标识,防止错误调用被转发到错误的服务方法。