第4章:Binder通信数据流:Transaction发起、数据序列化、驱动转发、目标端反序列化与回调
模块边界与系统分层
Binder一次完整的事务(Transaction)跨越三个层级:用户空间客户端、Binder内核驱动、用户空间服务端。数据在每一层发生形态与地址空间的转换,架构上保持了严格的边界隔离。
| 层级 | 角色 | 模块边界描述 |
|---|---|---|
| Client 端(用户空间) | 发起者 | 持有 Binder 代理对象(BpBinder),调用代理方法 -> 构造 Parcel 数据 -> 进入 ioctl |
| Binder 驱动(内核空间) | 转发/调度者 | 接收 ioctl 命令 BINDER_WRITE_READ,管理进程间内存映射与线程池,完成节点与事务匹配 |
| Server 端(用户空间) | 目标执行者 | 驱动唤醒等待的Binder线程 -> 从内核空间拷贝数据到本地 Parcel -> 执行反序列化并回调业务方法 |
组件职责与数据流全景
数据流从Client到Server经历 6 个核心阶段,每个阶段均有明确的组件负责。
- Transaction 发起:客户端通过 BpBinder::transact() 将调用意图写入 Parcel 对象。
- 数据序列化:Parcel 将结构化参数( int、String、IBinder 引用、文件描述符等)编码为扁平字节流。
- 内核入口:调用 ioctl(fd, BINDER_WRITE_READ, &bwr) 将 Parcel 数据从用户空间拷贝到内核空间缓存。
- 驱动转发:Binder 驱动根据目标 handle 查询服务端节点,拷贝数据到目标进程的 mmap 缓冲区,发布
BC_TRANSACTION。 - 反序列化:Server 端 Binder 线程收到数据,从内核缓冲区拷贝到本地 Parcel 对象,解码出原始参数。
- 回调:调用 BnInterface::onTransact() 方法,分发到具体业务函数执行。
关键设计要点:序列化/反序列化由同一个 Parcel 规范保证,驱动不做任何业务解析,只负责“搬运”与“路由”。这是 Binder 达到高效与安全解耦的基础。
控制流与信任链路
控制流由 Client 发起,驱动维护同步/异步标志。信任链路建立在以下几个环节:
- 身份校验:驱动会检查 Client 的 UID/PID,并附在 transaction 的数据头中传递给 Server。
- 文件描述符安全:驱动在转发
Parcel::writeFileDescriptor时,会进行权限与跨进程 fd 映射,防止提权。 - 对象引用计数:驱动维护内核端 Binder 节点引用,防止对象在传输中被非法释放。
选型权衡
| 决策点 | 选项 | 利弊分析 |
|---|---|---|
| 数据拷贝次数 | 一次拷贝(mmap) vs 两次拷贝 | Binder 驱动使用内核 mmap 映射只做一次物理拷贝,优于传统 socket 或共享内存的两次/三次拷贝 |
| 序列化格式 | 自描述 Parcel vs Protocol Buffers | Parcel 无 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 数据,会导致内核读脏数据。严格遵循驱动约定的同步等待模式可以避免此问题。
落地步骤
- 定义 AIDL 接口,编译生成 Stub/Proxy 骨架。
- 在 Proxy 中实现 transact(),注入 Parcel 写入逻辑。
- 在 Stub 中实现 onTransact(),读取 Parcel 并调用纯业务方法。
- 配置 Android.mk / Android.bp 包含 libbinder 依赖。
- 在 Service 主线程或 Binder 线程池中注册 service。
- 验证数据流:抓取 binder transaction 日志(
adb root && echo 1 > /d/binder/stats)。
验收标准
- 客户端发起 1000 次简单事务(传递 int+String),全部成功返回,无崩溃。
- 服务器端反序列化后取出的 UID 与客户端实际 UID 一致。
- 传递 4MB 数据时(需使用 Ashmem 方案),耗时不超过 50ms 且无 OOM。
- 文件描述符传递:Server 端能读出正确 fd 且可操作原始文件节点。
- 同步阻塞场景:客户端收到返回值的时间误差 < 5ms(排除调度抖动)。
数据流核心流程(SVG)
核心接口示意(非完整代码)
下面展示 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 宏来校验接口标识,防止错误调用被转发到错误的服务方法。