BC事务写入
BC_TRANSACTION 和 BC_REPLY 是写入方向的两个事务命令:用户态把命令号与 binder_transaction_data 写入 IPCThreadState::mOut,驱动在 binder_thread_write() 中复制 descriptor,再调用 binder_transaction()。
本文面向已经读过 BINDER_WRITE_READ命令、Binder事务发送 和 Binder远程引用 的读者,专门回答写入命令本身如何选择 request/reply、如何表达失败状态,以及标准结构和 SG 结构如何进入同一事务函数。
本文不重新讲目标 node/ref、buffer 分配和对象翻译的全部算法;这些是 BD017 的主题。这里的重点是“用户态写入什么,驱动先复制什么,哪些字段由驱动忽略或覆盖,以及错误如何回到 waitForResponse()”。
1. 命令对照
| 命令 | 用户态入口 | reply | 目标字段 | 典型结果 |
|---|---|---|---|---|
BC_TRANSACTION | transact() | false | target.handle | 同步请求或 oneway 请求 |
BC_REPLY | sendReply() | true | 驱动从 transaction stack 找 | 回复原发送线程 |
BC_TRANSACTION_SG | UAPI/驱动入口 | false | target.handle | 额外 buffers_size |
BC_REPLY_SG | UAPI/驱动入口 | true | 驱动从 transaction stack 找 | 额外 buffers_size |
普通 libbinder 调用使用前两种命令;SG 变体由驱动支持,但当前 frameworks/native 的普通生产路径没有直接写入它们。不要因为 UAPI 有 SG 命令,就把每个 Parcel 都描述成 SG 事务。
2. 用户态构造
2.1 普通调用
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:IPCThreadState::transact()、`writeTransactionData()``
status_t IPCThreadState::transact(
int32_t handle, uint32_t code,
const Parcel& data, Parcel* reply,
uint32_t flags) {
flags |= TF_ACCEPT_FDS;
status_t err = writeTransactionData(
BC_TRANSACTION, flags, handle,
code, data, nullptr);
if (err != NO_ERROR)
return err;
if ((flags & TF_ONE_WAY) == 0)
return waitForResponse(reply);
return waitForResponse(nullptr, nullptr);
}TF_ONE_WAY 不会把命令号换成其他 BC 命令;它仍是 BC_TRANSACTION,只是让驱动把事务标成 async,并改变发送方是否等待 reply。TF_ACCEPT_FDS 由 libbinder 默认加入,驱动仍会按目标 node 的 fd 策略检查对象。
2.2 Descriptor构造
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:writeTransactionData()
status_t IPCThreadState::writeTransactionData(
int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code,
const Parcel& data,
status_t* statusBuffer) {
binder_transaction_data tr;
tr.target.ptr = 0;
tr.target.handle = handle;
tr.code = code;
tr.flags = binderFlags;
tr.cookie = 0;
tr.sender_pid = 0;
tr.sender_euid = 0;
const status_t err = data.errorCheck();
if (err == NO_ERROR) {
tr.data_size = data.ipcDataSize();
tr.data.ptr.buffer = data.ipcData();
tr.offsets_size =
data.ipcObjectsCount() *
sizeof(binder_size_t);
tr.data.ptr.offsets = data.ipcObjects();
} else if (statusBuffer) {
tr.flags |= TF_STATUS_CODE;
*statusBuffer = err;
tr.data_size = sizeof(status_t);
tr.data.ptr.buffer =
reinterpret_cast<uintptr_t>(
statusBuffer);
tr.offsets_size = 0;
tr.data.ptr.offsets = 0;
} else {
return (mLastError = err);
}
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}用户态明确把 sender_pid、sender_euid 初始化为 0;这些字段不是调用者可信输入,驱动在接收 transaction 时填充目标可见身份。data.ptr.buffer 和 offsets 仍指向发送方 Parcel 的用户地址,驱动后续复制并翻译。
2.3 状态码回复
sendReply() 使用 statusBuffer 接收 reply Parcel 的序列化错误:
status_t IPCThreadState::sendReply(
const Parcel& reply, uint32_t flags) {
status_t statusBuffer;
status_t err = writeTransactionData(
BC_REPLY, flags, -1, 0,
reply, &statusBuffer);
if (err < NO_ERROR)
return err;
return waitForResponse(nullptr, nullptr);
}reply 的 handle 参数固定为 -1,因为驱动不会从 reply descriptor 的 target.handle 找目标;binder_transaction(reply=1) 依赖当前线程 transaction stack。若 reply Parcel 自身有错误,TF_STATUS_CODE 把状态写成目标可读的 32 位结果。
3. 驱动解析
3.1 写循环
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_write()
while (ptr < end &&
thread->return_error.cmd == BR_OK) {
uint32_t cmd;
int ret;
if (get_user(cmd,
(uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
trace_binder_command(cmd);
if (_IOC_NR(cmd) <
ARRAY_SIZE(binder_stats.bc)) {
atomic_inc(&binder_stats.bc[_IOC_NR(cmd)]);
atomic_inc(&proc->stats.bc[_IOC_NR(cmd)]);
atomic_inc(&thread->stats.bc[_IOC_NR(cmd)]);
}
switch (cmd) {
/* BC_TRANSACTION/BC_REPLY 在下方分支 */
}
}循环先读取 4 字节 command,再进入对应 case;write_consumed 以字节为单位推进。线程已经有 return error 时停止解析后续 BC,避免错误状态继续扩大。
3.2 标准结构
源码文件:kernel/common/drivers/android/binder.c
相关命令:BC_TRANSACTION、BC_REPLY
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;
}标准命令的实际分派只有两个动作:复制 descriptor,依据命令号生成 reply 布尔值,额外空间为 0。事务目标查找、buffer 申请、对象翻译和 work 入队由 binder_transaction() 接管。
3.3 SG结构
源码文件:kernel/common/drivers/android/binder.c
相关命令:BC_TRANSACTION_SG、BC_REPLY_SG
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;
}SG 结构在标准 descriptor 后增加 buffers_size,驱动只把它作为 extra_buffers_size 传给统一事务函数。它改变目标 buffer 的额外空间计算,不改变 reply 栈、目标解析或对象翻译的主入口。
4. 请求路径
4.1 Target handle
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_transaction()
if (!reply && tr->target.handle) {
struct binder_ref *ref;
binder_proc_lock(proc);
ref = binder_get_ref_olocked(
proc, tr->target.handle, true);
if (ref)
target_node =
binder_get_node_refs_for_txn(
ref->node, &target_proc,
&return_error);
else
return_error = BR_FAILED_REPLY;
binder_proc_unlock(proc);
}请求的 handle 属于发送方 proc。驱动要求强 ref,找到后增加 node/proc 临时引用;handle 无效或 node 已死时,事务函数设置 BR_FAILED_REPLY 或 BR_DEAD_REPLY,写循环本身仍完成 descriptor 消费。
4.2 Context manager
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_transaction()
if (!reply && !tr->target.handle) {
mutex_lock(
&context->context_mgr_node_lock);
target_node =
context->binder_context_mgr_node;
if (target_node)
target_node =
binder_get_node_refs_for_txn(
target_node, &target_proc,
&return_error);
else
return_error = BR_DEAD_REPLY;
mutex_unlock(
&context->context_mgr_node_lock);
}handle 0 指向当前 Binder context 的 manager node。context manager 未注册时请求失败;它不是普通 descriptor 0 的偶然值,而是由 context manager node 关系决定的特殊入口。
5. 回复路径
5.1 栈校验
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_transaction()
if (reply) {
binder_inner_proc_lock(proc);
in_reply_to = thread->transaction_stack;
if (!in_reply_to) {
binder_inner_proc_unlock(proc);
return_error = BR_FAILED_REPLY;
return_error_param = -EPROTO;
goto err_empty_call_stack;
}
if (in_reply_to->to_thread != thread) {
binder_inner_proc_unlock(proc);
return_error = BR_FAILED_REPLY;
return_error_param = -EPROTO;
goto err_bad_call_stack;
}
thread->transaction_stack =
in_reply_to->to_parent;
binder_inner_proc_unlock(proc);
}BC_REPLY 不携带一个可供驱动信任的目标 handle。当前 thread 必须正在处理栈顶请求,驱动先校验 to_thread,再弹出接收栈项;发送线程和目标 proc 稍后从 in_reply_to->from 取得。
5.2 发送结果
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:waitForResponse()
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 (err != NO_ERROR)
goto finish;
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;驱动事务错误以 BR 命令返回,libbinder 再映射为 DEAD_OBJECT、FAILED_TRANSACTION 或 frozen error。成功 BR_REPLY 不复制 payload 到另一个 Parcel,而是把目标 Binder buffer 交给 reply Parcel,并注册 freeBuffer 回调,最终通过 BC_FREE_BUFFER 归还。
6. 事务标志
6.1 Oneway
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp、kernel/common/include/uapi/linux/android/binder.h
相关符号:TF_ONE_WAY
enum transaction_flags {
TF_ONE_WAY = 0x01,
TF_ROOT_OBJECT = 0x04,
TF_STATUS_CODE = 0x08,
TF_ACCEPT_FDS = 0x10,
TF_CLEAR_BUF = 0x20,
TF_UPDATE_TXN = 0x40,
};TF_ONE_WAY 让驱动设置 t->is_async 并进入 node/proc async 路径;用户态 transact() 不等待业务 reply,只等待写入完成或 pending 状态。服务端如果错误地构造 BC_REPLY,驱动仍按 transaction stack 校验,而不是因为原请求是 oneway 就生成一个隐式 reply。
6.2 Status code
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:writeTransactionData()、waitForResponse()
if (err != NO_ERROR && statusBuffer) {
tr.flags |= TF_STATUS_CODE;
*statusBuffer = err;
tr.data_size = sizeof(status_t);
tr.data.ptr.buffer =
reinterpret_cast<uintptr_t>(
statusBuffer);
tr.offsets_size = 0;
tr.data.ptr.offsets = 0;
}当 writeTransactionData() 收到 statusBuffer 时,status code 是事务 payload 的一部分;它与驱动返回的 BR_FAILED_REPLY 不同。前者是用户态把已知错误编码进 reply data,后者表示驱动在目标解析、分配或入队阶段拒绝事务。
6.3 Clear buffer
源码文件:kernel/common/include/uapi/linux/android/binder.h、kernel/common/drivers/android/binder.c
相关符号:TF_CLEAR_BUF、binder_transaction_buffer_release()
TF_CLEAR_BUF 让目标 buffer 在事务结束时清理数据;它不改变 BC 命令格式,也不代表发送方 payload 在发送前会被清零。实际释放仍由接收方处理 BR_TRANSACTION/BR_REPLY 后提交 BC_FREE_BUFFER 触发。
7. 消费与清理
7.1 写入消费
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_write()
ptr += sizeof(uint32_t);
if (copy_from_user(&tr, ptr, sizeof(tr)))
return -EFAULT;
ptr += sizeof(tr);
binder_transaction(proc, thread, &tr,
cmd == BC_REPLY, 0);
*consumed = ptr - buffer;descriptor 复制成功后,ptr 已越过整个结构;事务函数内部失败不会把 BC 消费位置倒退。用户态 libbinder 对成功 ioctl 要求完整消费本轮 mOut,事务失败通过 BR/error work 反馈,而不是通过重放同一 BC。
7.2 事务完成
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_transaction() 错误尾部
if (in_reply_to) {
binder_set_txn_from_error(
in_reply_to, t_debug_id,
return_error, return_error_param);
thread->return_error.cmd =
BR_TRANSACTION_COMPLETE;
binder_enqueue_thread_work(
thread, &thread->return_error.work);
binder_send_failed_reply(
in_reply_to, return_error);
} else {
binder_set_extended_error(
&thread->ee, t_debug_id,
return_error, return_error_param);
thread->return_error.cmd = return_error;
binder_enqueue_thread_work(
thread, &thread->return_error.work);
}reply 失败和普通请求失败的返回 owner 不同:reply 失败要把错误送回原请求链;普通请求把错误放进当前发送 thread 的 return_error 和 extended error。两者最终都由后续读路径变成 BR 命令。
8. 测试输入
8.1 空请求
源码文件:frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp
相关测试:Transaction
struct {
uint32_t cmd1;
binder_transaction_data arg1;
} __attribute__((packed)) bc1 = {
.cmd1 = BC_TRANSACTION,
.arg1 = {
.target = {0},
.code = android::IBinder::PING_TRANSACTION,
.flags = 0,
.data_size = 0,
.offsets_size = 0,
.data = { .ptr = {0, 0} },
},
};
bwr.write_buffer = (uintptr_t)&bc1;
bwr.write_size = sizeof(bc1);
bwr.read_buffer = (uintptr_t)&br;
bwr.read_size = sizeof(br);测试输入是空 payload 的 BC_TRANSACTION,read buffer 接收多轮 BR。关键断言包括 write_consumed == sizeof(bc1)、BR_NOOP、BR_TRANSACTION_COMPLETE、BR_REPLY 和 reply descriptor 的零字段;它证明写入、基础 reply 形状和 BC_FREE_BUFFER 路径,不证明非空对象翻译。
8.2 Oneway
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
相关测试:NopTransactionOneway、OnewayQueueing
TEST_F(BinderLibTest, NopTransactionOneway) {
Parcel data, reply;
EXPECT_THAT(m_server->transact(
BINDER_LIB_TEST_NOP_TRANSACTION,
data, &reply, TF_ONE_WAY),
StatusEq(NO_ERROR));
}oneway 测试断言用户态返回成功,queueing 测试使用延迟 callback 与立即 callback 检查同一 node 的 async 顺序。它不证明 BC_REPLY 会在 oneway 请求后自动生成。
8.3 Freeze错误
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
相关测试:FreezeTxn
冻结目标后同步 transact 期望 frozen error,解冻后同一 transact 成功。它覆盖 BR_FROZEN_REPLY 从 driver 到用户态的映射,不覆盖 BC_REPLY 栈错误或 SG 事务。
9. 失败边界
| 阶段 | 失败 | 对用户态的结果 |
|---|---|---|
| descriptor copy | 用户地址无效 | ioctl -EFAULT,BC 未进入事务函数 |
| target lookup | handle/node/context 无效 | BR_FAILED_REPLY 或 BR_DEAD_REPLY |
| reply stack | 无栈或 to_thread 不符 | BR_FAILED_REPLY/-EPROTO |
| allocator/translation | buffer、offset、object 或 fd 失败 | failed reply、buffer/fixup 回滚 |
| frozen target | 同步/异步状态不同 | BR_FROZEN_REPLY 或 pending |
| read result | BR 写用户空间失败 | ioctl -EFAULT,事务清理 |
| Parcel error | statusBuffer 分支 | TF_STATUS_CODE payload,不等于 driver errno |
BC 命令被消费与事务成功是两个时刻。写入口可以已经推进 consumed,随后事务在 target lookup、buffer、translation 或 enqueue 阶段失败;排查时必须同时查看 BR、extended error 和 transaction log。
10. 源码复现
本文主线:
transact/sendReply
→ writeTransactionData
→ mOut: BC命令+descriptor
→ binder_thread_write
→ 标准或SG结构
→ binder_transaction(reply)
→ target栈/handle/context
→ buffer与flags
→ BR结果或error work源码搜索:
rg -n "writeTransactionData|BC_TRANSACTION|BC_REPLY|sendReply" \
frameworks/native/libs/binder/IPCThreadState.cpp
rg -n "case BC_TRANSACTION|case BC_REPLY|binder_transaction\(" \
kernel/common/drivers/android/binder.c
rg -n "TF_ONE_WAY|TF_STATUS_CODE|TF_CLEAR_BUF|binder_transaction_data_sg" \
kernel/common/include/uapi/linux/android/binder.h
rg -n "TEST_F.*Transaction|NopTransactionOneway|OnewayQueueing|FreezeTxn" \
frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp \
frameworks/native/libs/binder/tests/binderLibTest.cpp如果能够解释为什么 TF_ONE_WAY 不改变 BC 命令号、为什么 BC_REPLY 不信任 target.handle、为什么 SG 只增加 extra_buffers_size、以及为什么 descriptor 消费成功仍可能得到 BR_FAILED_REPLY,就已经掌握了这两个写入命令的真实源码边界。
