4. Binder 协议基础:BC_TRANSACTION、BC_REPLY、BR_TRANSACTION、BR_REPLY 等关键协议码
Binder 驱动作为 Android 跨进程通信的“中枢神经”,其上层交互完全依赖一套标准化的协议码。这些协议码分为两类:BC_*(Binder Command,从用户空间发往驱动)和 BR_*(Binder Return,从驱动发回用户空间)。理解这四组核心协议码,是掌握 Binder 数据流与控制流的关键。
核心认知: BC_ 前缀代表“客户端/服务端 → 驱动”的命令;BR_ 前缀代表“驱动 → 客户端/服务端”的响应或通知。两者成对出现,构成一次完整的 Binder 事务。
4.1 协议码总览与分类
Binder 协议码定义在 kernel/drivers/android/binder.h 中,总数约 20 余个。本章聚焦于事务处理最核心的四个协议码,其余协议码(如 BC_FREE_BUFFER、BC_INCREFS 等)将在后续章节涉及。
| 协议码 | 方向 | 功能描述 | 触发场景 |
|---|---|---|---|
| BC_TRANSACTION | 用户 → 驱动 | 发起一次跨进程事务请求(含数据) | 客户端调用 transact() |
| BC_REPLY | 用户 → 驱动 | 服务端回复一次事务结果(含数据) | 服务端 onTransact() 返回 |
| BR_TRANSACTION | 驱动 → 用户 | 驱动通知服务端有新的请求到达 | 驱动解析 BC_TRANSACTION 后 |
| BR_REPLY | 驱动 → 用户 | 驱动通知客户端请求结果已就绪 | 驱动解析 BC_REPLY 后 |
关键规律: BC_TRANSACTION 与 BR_TRANSACTION 配对,BC_REPLY 与 BR_REPLY 配对。驱动在其中扮演“转发+校验+内存映射”的角色,不会修改事务数据本身。
4.2 协议码数据结构:binder_transaction_data
所有 BC_TRANSACTION 和 BC_REPLY 命令都携带一个统一的数据结构 struct binder_transaction_data,该结构体是 Binder 协议的数据载体。其核心字段如下:
struct binder_transaction_data {
/* 通用字段 */
union {
__u32 handle; // 目标服务句柄(客户端使用)
void *ptr; // 目标 Binder 对象指针(内核使用)
} target;
void *cookie; // 服务端附加数据
__u32 code; // 事务代码(如 FIRST_CALL_TRANSACTION)
__u32 flags; // 标志位(如 TF_ONE_WAY)
/* 数据缓冲区描述 */
__u32 data_size; // 主数据大小(字节)
__u32 offsets_size;// 对象偏移数组大小(字节)
union {
struct {
const void __user *buffer; // 用户空间数据指针
const __u32 *offsets; // 对象偏移数组指针
} ptr;
__u8 buf[8]; // 小数据内联(8字节以内)
} data;
};
注意:
data.ptr.buffer 指向的是用户空间虚拟地址,驱动在收到 BC_TRANSACTION 后,会通过 copy_from_user 将数据拷贝到内核缓冲区,并建立与接收进程的内存映射关系。这就是 Binder 一次拷贝的核心机制。
4.3 完整事务流程:协议码的时序配合
一次典型的同步 Binder 调用,协议码的交互时序如下:
- 客户端 调用
IPCThreadState::transact(),构造binder_transaction_data,通过ioctl(fd, BINDER_WRITE_READ, &bwr)发送 BC_TRANSACTION 命令。 - Binder 驱动 接收 BC_TRANSACTION,解析目标 handle,查找服务端进程,将数据拷贝到内核缓冲区,然后向服务端线程写入 BR_TRANSACTION 命令。
- 服务端 线程从
ioctl返回,读取到 BR_TRANSACTION,调用onTransact()处理业务逻辑。 - 服务端 处理完成后,构造回复数据,通过
ioctl发送 BC_REPLY 命令。 - Binder 驱动 接收 BC_REPLY,将回复数据拷贝到内核缓冲区,然后向客户端线程写入 BR_REPLY 命令。
- 客户端 线程从
ioctl返回,读取到 BR_REPLY,获取回复数据,一次事务完成。
4.4 协议码与 Binder 驱动交互机制
协议码的发送与接收,通过 binder_write_read 结构体与 ioctl 系统调用完成。用户空间将命令写入 bwr.write_buffer,驱动处理后,将结果写入 bwr.read_buffer。
// 用户空间发送 BC_TRANSACTION 的典型代码路径
binder_transaction_data tr;
tr.target.handle = service_handle;
tr.code = TRANSACTION_CODE;
tr.data_size = data_len;
tr.data.ptr.buffer = (void *)data_ptr;
tr.data.ptr.offsets = offsets_ptr;
binder_write_read bwr;
bwr.write_size = sizeof(BC_TRANSACTION) + sizeof(tr);
bwr.write_buffer = (void *)cmd_buf;
// 将 BC_TRANSACTION 命令码写入缓冲区头部
*(__u32 *)cmd_buf = BC_TRANSACTION;
memcpy(cmd_buf + 4, &tr, sizeof(tr));
// 通过 ioctl 发送给驱动
ioctl(binder_fd, BINDER_WRITE_READ, &bwr);
性能要点: BC_TRANSACTION 和 BC_REPLY 的数据缓冲区通过
data.ptr.buffer 传递用户空间指针。驱动内部会使用 copy_from_user 将数据拷贝到内核缓冲区,然后通过内存映射(mmap)让接收进程直接访问,避免二次拷贝。这是 Binder 相比传统 Socket 通信的核心优势。
4.5 协议码的扩展:BC_TRANSACTION_SG 与 BR_SPAWN_LOOPER
除了上述四个核心协议码,还有两个与事务处理密切相关的扩展协议码:
| 协议码 | 类型 | 用途 |
|---|---|---|
| BC_TRANSACTION_SG | BC_* | 支持 scatter-gather 的增强版事务命令,允许一次事务携带多个不连续的内存块 |
| BR_SPAWN_LOOPER | BR_* | 驱动通知服务端线程池需要创建新的工作线程,以应对高并发请求 |
避坑指南: BC_TRANSACTION_SG 在 Android 10+ 中引入,主要用于大块数据(如 > 1MB)的高效传输。如果直接使用 BC_TRANSACTION 传输超大缓冲区,驱动会因内存分配失败返回 -ENOMEM。此时应改用 BC_TRANSACTION_SG 配合
struct binder_fd_array_object 实现文件描述符传递。
4.6 协议码错误处理与调试
当协议码交互出现异常时,驱动会返回特定的错误码。常见错误场景如下:
- BC_TRANSACTION 返回 -EINVAL:
binder_transaction_data结构体校验失败,如 data_size 与 offsets_size 不匹配。 - BC_REPLY 返回 -ENOENT:目标服务端已死亡(binder died),驱动无法找到接收进程。
- BR_TRANSACTION 返回 -EAGAIN:服务端线程池已满,驱动无法立即投递事务,需要等待空闲线程。
- BR_REPLY 返回 -ETIMEDOUT:同步调用超时,服务端在指定时间内未回复。
调试技巧: 使用
adb shell cat /sys/kernel/debug/binder/proc/* 可以查看当前 Binder 事务的协议码状态。重点关注 BC_TRANSACTION 和 BR_REPLY 的计数是否匹配,若长期不匹配,说明存在事务泄漏。