Binder协议状态机
Binder 用户态与驱动之间不是“一次 ioctl 对应一次 RPC”的固定关系。一个 BINDER_WRITE_READ 可以同时提交多条 BC 命令并接收多条 BR 命令;一次同步事务还可能经历 BR_TRANSACTION_COMPLETE、BR_TRANSACTION、BC_REPLY 和 BR_REPLY 等多个阶段。
本文面向已经读过 Binder调用源码地图 的读者,专门解释 协议层:外层信封如何描述两个缓冲区,命令码如何携带参数尺寸,驱动怎样增量消费,用户态怎样 解释返回命令,以及事务、引用、线程池、buffer、死亡和冻结为何都需要自己的握手。
本文不逐项复制全部枚举,也不展开 binder_transaction 内部路由和引用计数算法。读完后,读者 应能从一段 BC/BR 命令流恢复当前状态,并区分“驱动接受命令”“服务收到事务”和“客户端得到 业务 reply”。
1. 两层协议
ioctl信封
源码文件:kernel/common/include/uapi/linux/android/binder.h
struct binder_write_read {
binder_size_t write_size;
binder_size_t write_consumed;
binder_uintptr_t write_buffer;
binder_size_t read_size;
binder_size_t read_consumed;
binder_uintptr_t read_buffer;
};
enum {
BINDER_WRITE_READ =
_IOWR('b', 1, struct binder_write_read),
BINDER_SET_MAX_THREADS = _IOW('b', 5, __u32),
BINDER_VERSION = _IOWR('b', 9, struct binder_version),
};BINDER_WRITE_READ 是外层 ioctl;write_buffer 和 read_buffer 才承载 BC/BR 命令。两个 consumed 字段允许驱动报告实际消费量。协议调用方不能假定传入的每个字节都已被处理。
命令序列
write_buffer 与 read_buffer 都采用“uint32 命令码 + 命令参数”的连续布局。命令值使用 _IO、_IOR、_IOW 编码,因此命令号同时携带方向和参数大小信息;驱动 switch 仍按完整 cmd 值分派。
2. 用户态信封
mOut与mIn
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:talkWithDriver()
binder_write_read bwr;
const bool needRead =
mIn.dataPosition() >= mIn.dataSize();
const size_t outAvail =
(!doReceive || needRead) ? mOut.dataSize() : 0;
bwr.write_size = outAvail;
bwr.write_buffer =
reinterpret_cast<uintptr_t>(mOut.data());
if (doReceive && needRead) {
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer =
reinterpret_cast<uintptr_t>(mIn.data());
} else {
bwr.read_size = 0;
bwr.read_buffer = 0;
}mOut 可以积累多条命令;mIn 也可能还有未消费的返回命令。若 mIn 尚有数据,talkWithDriver 不会覆盖它。由此可见,业务调用次数与 ioctl 次数并非一一对应。
ioctl重试
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
bwr.write_consumed = 0;
bwr.read_consumed = 0;
status_t err;
do {
if (ioctl(mProcess->mDriverFD,
BINDER_WRITE_READ, &bwr) >= 0) {
err = NO_ERROR;
} else {
err = -errno;
}
} while (err == -EINTR);EINTR 在用户态重试;UAPI 注释还要求 ECONNREFUSED 后把驱动连接视为不可继续使用。错误处理 属于 ioctl 信封层,不等于 BR_FAILED_REPLY 这种事务命令。
消费收束
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
if (err >= NO_ERROR) {
if (bwr.write_consumed > 0) {
if (bwr.write_consumed < mOut.dataSize()) {
LOG_ALWAYS_FATAL(
"Driver did not consume write buffer");
} else {
mOut.setDataSize(0);
processPostWriteDerefs();
}
}
if (bwr.read_consumed > 0) {
mIn.setDataSize(bwr.read_consumed);
mIn.setDataPosition(0);
}
return NO_ERROR;
}libbinder 当前把“驱动成功但只消费部分 mOut”视为致命不变量破坏;read_consumed 则决定 mIn 的有效长度。协议分析时必须同时查看 ioctl 返回值和两个 consumed 字段。
3. 驱动写侧
增量解析
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_write()
void __user *buffer =
(void __user *)(uintptr_t)binder_buffer;
void __user *ptr = buffer + *consumed;
void __user *end = buffer + size;
while (ptr < end &&
thread->return_error.cmd == BR_OK) {
uint32_t cmd;
if (get_user(cmd, (uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
trace_binder_command(cmd);
switch (cmd) {
/* 每个 case 继续读取自己的参数 */
}
}
*consumed = ptr - buffer;解析起点是上次 consumed,而不是 buffer 起始地址。每个 case 必须按命令格式移动 ptr;参数 地址无效时返回 EFAULT。thread->return_error 不为 BR_OK 时,驱动停止继续消费新的 BC 命令。
事务命令
源码文件:kernel/common/drivers/android/binder.c
case BC_TRANSACTION_SG:
case BC_REPLY_SG: {
struct binder_transaction_data_sg tr;
if (copy_from_user(&tr, ptr, sizeof(tr)))
return -EFAULT;
ptr += sizeof(tr);
binder_transaction(proc, thread,
&tr.transaction_data,
cmd == BC_REPLY_SG,
tr.buffers_size);
break;
}
case BC_TRANSACTION:
case BC_REPLY: {
struct binder_transaction_data tr;
if (copy_from_user(&tr, ptr, sizeof(tr)))
return -EFAULT;
ptr += sizeof(tr);
binder_transaction(proc, thread, &tr,
cmd == BC_REPLY, 0);
break;
}普通事务与 scatter-gather 事务参数大小不同。BC_REPLY 没有目标 handle,而是依赖当前服务 线程的 transaction_stack;协议值相似不代表状态前置条件相同。
其他命令
驱动写侧还处理四组控制状态:
| 组 | 典型 BC 命令 | 驱动 owner |
|---|---|---|
| 引用 | INCREFS/ACQUIRE/RELEASE/DECREFS | binder_ref/node |
| buffer | FREE_BUFFER | binder_alloc |
| looper | REGISTER/ENTER/EXIT_LOOPER | binder_thread/proc |
| 通知 | REQUEST/CLEAR_DEATH、FREEZE | ref death/freeze work |
这些命令不是事务 payload,却与代理和服务线程能否继续工作直接相关。
4. 驱动读侧
空读等待
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()
if (*consumed == 0) {
if (put_user(BR_NOOP,
(uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
}
if (non_block) {
if (!binder_has_work(thread,
wait_for_proc_work))
ret = -EAGAIN;
} else {
ret = binder_wait_for_work(
thread, wait_for_proc_work);
}read buffer 首次会预留 BR_NOOP;无 work 时,阻塞 fd 等待,非阻塞 fd 返回 EAGAIN。BR_NOOP 可在需要时被 BR_SPAWN_LOOPER 替换,所以它不是业务事件。
work转命令
源码文件:kernel/common/drivers/android/binder.c
switch (w->type) {
case BINDER_WORK_RETURN_ERROR:
cmd = error->cmd;
put_user(cmd, (uint32_t __user *)ptr);
ptr += sizeof(uint32_t);
break;
case BINDER_WORK_TRANSACTION_COMPLETE:
cmd = BR_TRANSACTION_COMPLETE;
put_user(cmd, (uint32_t __user *)ptr);
ptr += sizeof(uint32_t);
break;
case BINDER_WORK_TRANSACTION:
t = container_of(w,
struct binder_transaction, work);
break;
}驱动内部 work 类型和公开 BR 命令不是同一枚举;binder_thread_read 负责把内部状态编码成 UAPI 命令。
事务返回
源码文件:kernel/common/drivers/android/binder.c
if (t->buffer->target_node) {
trd->target.ptr =
t->buffer->target_node->ptr;
trd->cookie =
t->buffer->target_node->cookie;
cmd = BR_TRANSACTION;
} else {
trd->target.ptr = 0;
trd->cookie = 0;
cmd = BR_REPLY;
}
trd->code = t->code;
trd->flags = t->flags;
trd->data_size = t->buffer->data_size;
trd->offsets_size =
t->buffer->offsets_size;有 target_node 表示服务端收到新事务;没有 target_node 表示客户端收到 reply。随后驱动写入 cmd 和 transaction_data,并以 read_consumed 告诉用户态有效字节数。
5. 事务状态
同步序列
同步事务的核心序列是:
BR_TRANSACTION_COMPLETE 只确认上一条 transaction/reply 命令被驱动处理。客户端有 reply 对象时仍必须等待 BR_REPLY,不能把 complete 当成服务完成。
waitForResponse
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:waitForResponse()
switch (cmd) {
case BR_TRANSACTION_COMPLETE:
if (!reply && !acquireResult)
goto finish;
break;
case BR_DEAD_REPLY:
err = DEAD_OBJECT;
goto finish;
case BR_FAILED_REPLY:
err = FAILED_TRANSACTION;
goto finish;
case BR_FROZEN_REPLY:
err = enableFrozenObjectErrorCode()
? FROZEN_OBJECT : FAILED_TRANSACTION;
goto finish;
case BR_REPLY:
binder_transaction_data tr;
err = mIn.read(&tr, sizeof(tr));
if ((tr.flags & TF_STATUS_CODE) == 0) {
reply->ipcSetDataReference(
reinterpret_cast<const uint8_t*>(
tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(
tr.data.ptr.offsets),
tr.offsets_size /
sizeof(binder_size_t),
freeBuffer);
}
goto finish;
default:
err = executeCommand(cmd);
break;
}等待 reply 的线程也会遇到引用、死亡或嵌套 transaction 等其他 BR 命令;default 交给 executeCommand。因此同步等待并非只读取一个 reply 包。
oneway序列
oneway 仍收到 BR_TRANSACTION_COMPLETE,但没有业务 BR_REPLY。服务端 onTransact 的错误和 reply 数据通常不会返回原调用者。冻结目标还可能产生 BR_TRANSACTION_PENDING_FROZEN, oneway 洪泛检测可能产生 BR_ONEWAY_SPAM_SUSPECT。
6. 引用握手
驱动通知
BR_ACQUIRE/BR_INCREFS 携带 ptr/cookie,通知本地进程增加对象强/弱引用。用户态不能只修改 RefBase 后结束,还要回写确认命令。
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
case BR_ACQUIRE:
refs = (RefBase::weakref_type*)
mIn.readPointer();
obj = (BBinder*)mIn.readPointer();
obj->incStrong(mProcess.get());
mOut.writeInt32(BC_ACQUIRE_DONE);
mOut.writePointer((uintptr_t)refs);
mOut.writePointer((uintptr_t)obj);
break;
case BR_INCREFS:
refs = (RefBase::weakref_type*)
mIn.readPointer();
obj = (BBinder*)mIn.readPointer();
refs->incWeak(mProcess.get());
mOut.writeInt32(BC_INCREFS_DONE);
mOut.writePointer((uintptr_t)refs);
mOut.writePointer((uintptr_t)obj);
break;引用通知与 DONE 命令构成握手。驱动会检查是否存在 pending acquire/increfs;重复或 cookie 不匹配会记录协议错误。
延迟释放
BR_RELEASE/BR_DECREFS 不一定立即在当前 switch 中删除对象;libbinder 把 strong/weak deref 放入 pending 容器,等写命令安全提交后处理。这避免在解析返回缓冲区期间提前销毁仍被使用的 对象。
7. 线程握手
进入线程池
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:joinThreadPool()
mOut.writeInt32(
isMain ? BC_ENTER_LOOPER
: BC_REGISTER_LOOPER);
do {
processPendingDerefs();
result = getAndExecuteCommand();
if (result < NO_ERROR &&
result != TIMED_OUT &&
result != -ECONNREFUSED &&
result != -EBADF) {
abort();
}
} while (result != -ECONNREFUSED &&
result != -EBADF);主线程主动 ENTER,驱动请求创建的线程 REGISTER。两个命令更新 binder_thread looper 状态, 并影响驱动是否可把 proc todo 分配给该线程。
生成线程
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
case BR_SPAWN_LOOPER:
mProcess->spawnPooledThread(false);
break;驱动只有在等待线程不足且未超过 max_threads 等条件下才产生 BR_SPAWN_LOOPER。用户态收到后 创建线程,线程再发送 BC_REGISTER_LOOPER;这不是单向通知,而是请求—注册闭环。
8. Buffer握手
读取buffer
BR_TRANSACTION/BR_REPLY 的 data.ptr.buffer 指向目标进程映射中的 Binder buffer。libbinder 用 ipcSetDataReference 建立 Parcel 视图,并注册 freeBuffer 回调。
释放命令
源码文件:kernel/common/include/uapi/linux/android/binder.h
BC_FREE_BUFFER =
_IOW('c', 3, binder_uintptr_t);源码文件:kernel/common/drivers/android/binder.c
case BC_FREE_BUFFER: {
binder_uintptr_t data_ptr;
get_user(data_ptr,
(binder_uintptr_t __user *)ptr);
ptr += sizeof(binder_uintptr_t);
buffer = binder_alloc_prepare_to_free(
&proc->alloc, data_ptr);
if (IS_ERR_OR_NULL(buffer))
break;
binder_free_buf(proc, thread,
buffer, false);
break;
}收到 BR_TRANSACTION 不意味着 buffer 已释放;Parcel 消费结束后 freeBuffer 才生成 BC_FREE_BUFFER。地址无匹配、buffer 尚未允许用户释放或正在释放都会被驱动拒绝。
9. 通知握手
死亡通知
典型闭环为:
- BC_REQUEST_DEATH_NOTIFICATION 提交 handle/cookie;
- node 死亡后驱动返回 BR_DEAD_BINDER(cookie);
- 用户态执行 DeathRecipient;
- 回写 BC_DEAD_BINDER_DONE(cookie)。
清除通知则由 BC_CLEAR_DEATH_NOTIFICATION 和 BR_CLEAR_DEATH_NOTIFICATION_DONE 配对。
冻结通知
Android 17 UAPI 还包含 REQUEST/CLEAR_FREEZE_NOTIFICATION、BR_FROZEN_BINDER 和 BR_CLEAR_FREEZE_NOTIFICATION_DONE。它们与死亡通知使用不同状态,不能把 frozen 等同于 dead。
10. 错误边界
ioctl错误
EFAULT 表示外层结构或用户指针不可访问;EAGAIN 常见于非阻塞读且无 work;EINTR 由 libbinder 重试;ECONNREFUSED 表示该进程的驱动连接不再接受操作。
BR错误
| 命令 | 含义 | 用户态结果 |
|---|---|---|
| BR_DEAD_REPLY | 目标死亡 | DEAD_OBJECT |
| BR_FAILED_REPLY | 事务失败 | FAILED_TRANSACTION |
| BR_FROZEN_REPLY | 同步目标冻结 | FROZEN_OBJECT 或失败 |
| BR_TRANSACTION_PENDING_FROZEN | oneway 发往冻结目标 | 告警后结束等待 |
| BR_ERROR | 驱动携带 errno | 读取 int32 error |
这些错误发生在不同层,不能全部归类为 RemoteException 根因。
未知命令
驱动写侧对未支持的 BC 命令通常返回 EINVAL;libbinder 返回侧的 default 会调用 executeCommand,不认识的 BR 命令也会进入错误路径。协议版本必须在 BINDER_VERSION 上先协商, 不能靠忽略未知命令维持兼容。
11. 测试输入
版本与空读
源码文件:frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp
相关测试:Version、WriteReadNull、WriteReadEmpty、Read
Version 调用 BINDER_VERSION 并断言 BINDER_CURRENT_PROTOCOL_VERSION;WriteReadNull 传空指针 断言 EFAULT;空信封断言成功;非阻塞空读断言 EAGAIN 和 read_consumed 为 0。这些测试分别覆盖 版本、外层指针、空命令和无 work,不证明事务业务结果。
事务序列
源码文件:frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp
相关测试:Transaction
bwr.write_buffer = (uintptr_t)&bc1;
bwr.write_size = sizeof(bc1);
bwr.read_buffer = (uintptr_t)&br;
bwr.read_size = sizeof(br);
binderTestIoctlSuccessOrError(
BINDER_WRITE_READ, &bwr, EAGAIN);
EXPECT_EQ(sizeof(bc1), bwr.write_consumed);
if (bwr.read_consumed > offsetof(typeof(br), cmd0))
EXPECT_EQ(BR_NOOP, br.cmd0);
if (bwr.read_consumed > offsetof(typeof(br), cmd1))
EXPECT_EQ(BR_TRANSACTION_COMPLETE, br.cmd1);
if (bwr.read_consumed > offsetof(typeof(br), cmd2))
EXPECT_EQ(BR_REPLY, br.cmd2);输入是发往 handle 0 的 PING_TRANSACTION;测试允许第一次 ioctl 因非阻塞读返回 EAGAIN,再 poll 并继续读取,最终检查 NOOP、TRANSACTION_COMPLETE 和 REPLY 顺序。随后它发送 BC_FREE_BUFFER 并断言 write_consumed,覆盖 buffer 释放闭环。
引用与通知
同一测试文件的 IncRefsAcquireReleaseDecRefs 把四条引用命令连续放进 write_buffer,并断言全部 消费;RequestDeathNotification 组合引用、请求、清除和 decrefs,检查对应 BR 命令。它们证明 命令可批量编码和握手,不证明完整引用计数算法。
12. 阅读边界
状态转移图
前文代码分别展示 write/read 的实现;下面把命令消费者和线程状态放在同一图中,便于沿 BINDER_WRITE_READ 复核一条事务。
BR_TRANSACTION_COMPLETE 位于 DriverConsume→UserRead 之间,只表示写命令被驱动接收;真正的同步终点是 BR_REPLY 或失败命令。图中的 BufferFree 是 Parcel 消费后的释放动作,不是服务方法返回的同义词。
本文证明了 ioctl 信封、命令流、增量 consumed、事务阶段、引用/线程/buffer/通知握手和错误层次。 没有逐项解释所有命令的数据结构,也没有展开 binder_transaction、node/ref、allocator、线程池 策略和死亡通知内部算法。
下一步可进入 BD006 数据传输模型,继续分析 transaction_data、offsets 和对象转换;也可进入 BD021/BD022 分别深挖 BC_TRANSACTION/BC_REPLY 与 BR_TRANSACTION/BR_REPLY。复查协议时不要只 列命令名,而应写出“谁发送、前置状态、参数长度、谁消费、需要何种确认、失败后状态”。
