4. Binder通信协议格式
4.1 边界定义
本章聚焦于Binder驱动与用户空间之间的协议层,即"BC/B"事务协议。该层位于Binder IPC的中间层:
- 上边界:Java/Native服务端与客户端的IBinder接口调用(如transact()、onTransact())。
- 下边界:Binder驱动的内核处理逻辑(如binder_thread_read/write)。
- 核心问题:如何将方法调用、参数、结果序列化为扁平字节流,并通过驱动完成跨进程传递。
4.2 分层职责
| 层级 | 组件 | 职责 |
|---|---|---|
| 用户态调用层 | IBinder.transact() / Binder.execTransact() | 发起IPC请求,构造BBinder或BpBinder的调用。 |
| Parcel层 | Parcel :: write* / read* 方法 | 将基本类型、Parcelable、Binder对象序列化为堆叠字节。 |
| BC/B协议层 | 驱动中binder_transaction_data结构体 | 定义事务的数据帧格式,包括命令码、数据偏移、对象列表。 |
| Binder驱动 | binder_ioctl / binder_thread_write / binder_thread_read | 解析BC命令(BINDER_COMMAND_PROTOCOL),执行事务路由,生成BR回复。 |
4.3 BC/B transactional数据结构
Binder事务在驱动层使用struct binder_transaction_data表征。每个事务包含固定头部和可变载荷。
struct binder_transaction_data {
binder_uintptr_t target; // 目标Handle或本地Binder指针
binder_uintptr_t cookie; // 附加上下文(常用于BBinder)
uint32_t code; // 方法码(如0x1 = FIRST_CALL_TRANSACTION)
uint32_t flags; // TF_ONE_WAY, TF_ACCEPT_FDS 等
pid_t sender_pid;
uid_t sender_euid;
binder_size_t data_size; // 数据区字节数
binder_size_t offsets_size; // Binder对象偏移数组字节数
union {
struct {
binder_uintptr_t buffer;
binder_uintptr_t offsets;
} ptr;
uint8_t buf[8];
} data;
};
4.4 命令码编码(BC / BR dichotomy)
Binder定义了多组命令码,前缀BC_代表"从用户空间发送到驱动",BR_代表"从驱动发送到用户空间"。核心事务命令为:
| 方向 | 命令码 | 值 | 含义 |
|---|---|---|---|
| User → Driver | BC_TRANSACTION | 0x0C | 发起一次同步/异步RPC事务 |
| User → Driver | BC_REPLY | 0x0D | 回复BR_TRANSACTION |
| Driver → User | BR_TRANSACTION | 0x02 | 驱动通知用户有传入事务 |
| Driver → User | BR_REPLY | 0x03 | 驱动返回远程调用的结果 |
| Driver → User | BR_DEAD_REPLY | 0x05 | 目标进程已死 |
设计权衡:命令码设计为纯数字而非位掩码,目的是在驱动中能通过快速跳转表(switch-case)处理,减少分支预测失败。同时BC/BR分离保证了严格的双向协议对称性,简化驱动状态机。
4.5 Parcel序列化与反序列化设计
Parcel是一个可增长的缓冲区,支持线性写入和读取。核心设计原则:
- 位置不可逆:只允许向后写入/读取,不支持随机访问。
- 数据对齐:所有写入数据按4字节对齐(某些类型按1/2字节对齐)。
- 对象列表分离:Parcel中嵌入的Binder对象(flat_binder_object)不直接内联在数据流中,而是将偏移量存储在offsets数组中,驱动解析时统一提取。
- 段机制:支持restart_write、setDataPosition等回退操作(仅用于序列化错误处理)。
串行化示意(精简API签名):
// Parcel.h (伪接口)
class Parcel {
status_t writeInt32(int32_t val);
status_t writeString16(const String16& str);
status_t writeStrongBinder(const sp<IBinder>& binder);
status_t write(const void* data, size_t len);
int32_t readInt32();
String16 readString16();
sp<IBinder> readStrongBinder();
const void* readInplace(size_t len);
};
4.6 数据流与控制流
一次同步调用流程:
1. Client线程调用transact(code, data, reply, flags)。
2. Parcel序列化:将参数对象依次写入data Parcel,Binder对象写入对象列表。
3. 构造BC_TRANSACTION命令 + binder_transaction_data(含data_size/offsets_size)。
4. 通过ioctl(BC_TRANSACTION)写入驱动binder_thread_write。
5. 驱动解析命令码,查找目标节点(handle→binder_ref→binder_node)。
6. 驱动根据data.offsets解析flat_binder_object,完成Binder结点传递或入队server线程等待队列。
7. Server线程被唤醒,收到BR_TRANSACTION,读取Parcel内容。
8. Server执行onTransact,构造reply Parcel。
9. Server通过ioctl(BC_REPLY)写入回复。
10. Client线程从ioctl返回,收到BR_REPLY,读取reply Parcel获得结果。
1. Client线程调用transact(code, data, reply, flags)。
2. Parcel序列化:将参数对象依次写入data Parcel,Binder对象写入对象列表。
3. 构造BC_TRANSACTION命令 + binder_transaction_data(含data_size/offsets_size)。
4. 通过ioctl(BC_TRANSACTION)写入驱动binder_thread_write。
5. 驱动解析命令码,查找目标节点(handle→binder_ref→binder_node)。
6. 驱动根据data.offsets解析flat_binder_object,完成Binder结点传递或入队server线程等待队列。
7. Server线程被唤醒,收到BR_TRANSACTION,读取Parcel内容。
8. Server执行onTransact,构造reply Parcel。
9. Server通过ioctl(BC_REPLY)写入回复。
10. Client线程从ioctl返回,收到BR_REPLY,读取reply Parcel获得结果。
4.7 选型权衡与风险点
- 线性读写 vs 随机访问:Parcel 只有顺序读写,无法修改已写入数据。权衡:实现简单高效,但不利于需要复杂结构修改的场景。解决方案是提前构建好数据顺序。
- 对齐开销:每写入非4字节类型后自动填充。风险:如果写入大量小类型(char/short)会导致空间浪费。优化:使用writeByteArray批量写入。
- Binder对象传递风险:若接收方不读取或误读offsets数组可能导致引用泄漏。驱动只负责传递,不当的序列化逻辑会使Binder引用计数失衡。
- 大数据量传输:单个事务限制为4MB(BINDER_VM_SIZE/2)。超出会导致BINDER_FAIL_SYSTEM_ERROR。需拆分为多次调用或使用共享内存。
- 命令码扩展风险:BC/BR命令码是固定枚举,新增命令需同步驱动版本与用户态库版本,否则导致兼容性崩溃。厂商定制需注意。
4.8 落地步骤
- 定义接口:确定RPC方法签名、返回值类型。
- 实现Stub端:继承BBinder,重写onTransact,从data Parcel读取参数,调用本地实现。
- 实现Proxy端:继承BpBinder,在transact中构建data Parcel,调用远程transact。
- 序列化合约:在AIDL中声明顺序,确保两端读写顺序一致。
- 处理对象传递:使用writeStrongBinder传递Binder对象,驱动会自动处理handle映射。
- 错误处理:检查transact返回码,处理死亡通知(BR_DEAD_REPLY, BR_FAILED_REPLY)。
4.9 验收标准
| 验收项 | 方法 | 通过条件 |
|---|---|---|
| 基本类型传递正确 | 读写int/float/double/String | 序列化后反序列化值与原始值一致 |
| Binder对象传递 | 传递Binder句柄到另一进程,执行回调 | 回调成功执行且无异常 |
| 大数据传递 | 发送4MB - 1字节数据 | 事务成功完成,无TRANSACTION_FAIL |
| 超大数据拒绝 | 发送4MB + 1字节 | 返回STATUS_FAILED_TRANSACTION |
| OneWay调用 | 设置TF_ONE_WAY标志,连续发出10次 | 无阻塞且回复BR_TRANSACTION_COMPLETE |
| Binder节点传递引用 | 发送Binder对象后接收方写入新Binder | 引用计数正确,无泄漏(dumpsys检查) |
4.10 关键提示
常见陷阱:
- 忘记调用Parcel::setDataPosition(0)导致读取顺序错乱。
- 在writeStrongBinder之后忘记写offsets(AIDL内部已处理,手动构造时容易忽略)。
- 在接收端误用readInt64读取原本写入的int32导致栈读取越界。
- 未处理BR_DEAD_REPLY时进程长时间阻塞。
- 忘记调用Parcel::setDataPosition(0)导致读取顺序错乱。
- 在writeStrongBinder之后忘记写offsets(AIDL内部已处理,手动构造时容易忽略)。
- 在接收端误用readInt64读取原本写入的int32导致栈读取越界。
- 未处理BR_DEAD_REPLY时进程长时间阻塞。