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 → DriverBC_TRANSACTION0x0C发起一次同步/异步RPC事务
User → DriverBC_REPLY0x0D回复BR_TRANSACTION
Driver → UserBR_TRANSACTION0x02驱动通知用户有传入事务
Driver → UserBR_REPLY0x03驱动返回远程调用的结果
Driver → UserBR_DEAD_REPLY0x05目标进程已死
设计权衡:命令码设计为纯数字而非位掩码,目的是在驱动中能通过快速跳转表(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获得结果。
BC/B 事务协议数据流与控制流 Client 线程 transact(code, data, reply) Binder Driver binder_thread_write/read Server 线程 onTransact(code, data, reply) BC_TRANSACTION BR_TRANSACTION BC_REPLY (来自服务端) BR_REPLY (到达客户端) Parcel 序列化(发送端) writeInt32/64 writeString16 → UTF16 + length writeStrongBinder → flat_binder_object 内存对齐至4字节边界 offsets数组记录对象位置 Parcel 反序列化(接收端) readInt32/64 readString16 → 偏移+长度 readStrongBinder → 驱动解析 stl矢量数据用readInplace 只读方式,不可修改 驱动事务处理 解析 binder_transaction_data 提取 flat_binder_object 列表 执行 Binder 结点传递/引用计数

4.7 选型权衡与风险点

  • 线性读写 vs 随机访问:Parcel 只有顺序读写,无法修改已写入数据。权衡:实现简单高效,但不利于需要复杂结构修改的场景。解决方案是提前构建好数据顺序。
  • 对齐开销:每写入非4字节类型后自动填充。风险:如果写入大量小类型(char/short)会导致空间浪费。优化:使用writeByteArray批量写入。
  • Binder对象传递风险:若接收方不读取或误读offsets数组可能导致引用泄漏。驱动只负责传递,不当的序列化逻辑会使Binder引用计数失衡。
  • 大数据量传输:单个事务限制为4MB(BINDER_VM_SIZE/2)。超出会导致BINDER_FAIL_SYSTEM_ERROR。需拆分为多次调用或使用共享内存。
  • 命令码扩展风险:BC/BR命令码是固定枚举,新增命令需同步驱动版本与用户态库版本,否则导致兼容性崩溃。厂商定制需注意。

4.8 落地步骤

  1. 定义接口:确定RPC方法签名、返回值类型。
  2. 实现Stub端:继承BBinder,重写onTransact,从data Parcel读取参数,调用本地实现。
  3. 实现Proxy端:继承BpBinder,在transact中构建data Parcel,调用远程transact。
  4. 序列化合约:在AIDL中声明顺序,确保两端读写顺序一致。
  5. 处理对象传递:使用writeStrongBinder传递Binder对象,驱动会自动处理handle映射。
  6. 错误处理:检查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时进程长时间阻塞。