BR事务返回
BR_TRANSACTION 和 BR_REPLY 是驱动写入用户态 read buffer 的两种事务结果。驱动不是把一个完整 Parcel 复制到 read buffer,而是从 thread/proc work 取出 binder_transaction,把命令号和 binder_transaction_data 描述符写入用户地址;描述符中的 data.ptr.buffer 仍指向目标进程的 Binder VMA。
本文面向已经读过 BINDER_WRITE_READ命令、BC事务写入 和 Binder事务发送 的读者,回答“驱动如何决定返回 transaction 还是 reply,用户态如何消费 descriptor,以及什么时候通过 BC_FREE_BUFFER 归还目标 buffer”。
本文不重复讲 request/reply 的目标查找和对象翻译算法,只讲 read 阶段的 work 消费、BR 结构组装、用户态执行和释放边界。BR_TRANSACTION 已写入 read buffer 也不等于服务方法已经返回;BR_REPLY 已到达也不等于 Parcel 已经完成对象反序列化。
1. 两种返回
| 返回 | 驱动判据 | 用户态消费者 | buffer owner |
|---|---|---|---|
BR_TRANSACTION | t->buffer->target_node != NULL | executeCommand() | 接收服务进程 |
BR_TRANSACTION_SEC_CTX | transaction 带 security context | executeCommand() | 接收服务进程 |
BR_REPLY | target_node == NULL | waitForResponse() | 原调用方进程 |
2. 读取入口
2.1 初始占位
源码文件: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);
}新 read buffer 的第一个命令是 BR_NOOP。它不是事务,也不是“驱动已经有工作”的证明。之后驱动会检查当前线程能否消费 proc work,并根据 fd 是否非阻塞决定 -EAGAIN 或进入 wait queue。
2.2 队列选择
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()
binder_inner_proc_lock(proc);
if (!binder_worklist_empty_ilocked(
&thread->todo))
list = &thread->todo;
else if (!binder_worklist_empty_ilocked(
&proc->todo) && wait_for_proc_work)
list = &proc->todo;
else {
binder_inner_proc_unlock(proc);
if (ptr - buffer == 4 &&
!thread->looper_need_return)
goto retry;
break;
}thread todo 优先于 proc todo;如果只写入了 BR_NOOP 且没有特殊返回需求,驱动重新进入等待,而不是把只有 BR_NOOP 的 read 立即当作完成事务返回。read buffer 已有其他 BR 时,空间不足或没有更多 work 才返回已有内容。
2.3 空间门槛
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()
if (end - ptr < sizeof(tr) + 4) {
binder_inner_proc_unlock(proc);
break;
}
w = binder_dequeue_work_head_ilocked(list);
if (binder_worklist_empty_ilocked(
&thread->todo))
thread->process_todo = false;事务描述符最大按 binder_transaction_data_secctx 预留空间,命令号还需要 4 字节。read buffer 不够时 work 不会被摘走;空间检查发生在 dequeue 前,所以剩余 work 留到下一次 BINDER_WRITE_READ。
3. 事务描述符
3.1 方向判定
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()
BUG_ON(t->buffer == NULL);
if (t->buffer->target_node) {
struct binder_node *target_node =
t->buffer->target_node;
trd->target.ptr = target_node->ptr;
trd->cookie = target_node->cookie;
binder_transaction_priority(
thread, t, target_node);
cmd = BR_TRANSACTION;
} else {
trd->target.ptr = 0;
trd->cookie = 0;
cmd = BR_REPLY;
}驱动不看最初的 BC 命令号来决定 BR。事务对象到达 read 阶段时,target_node 是否存在才是判据:请求有目标 node,生成 BR_TRANSACTION;reply 没有目标 node,生成 BR_REPLY。
3.2 公共字段
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()
trd->code = t->code;
trd->flags = t->flags;
trd->sender_euid =
from_kuid(current_user_ns(),
t->sender_euid);
t_from = binder_get_txn_from(t);
if (t_from) {
struct task_struct *sender =
t_from->proc->tsk;
trd->sender_pid =
task_tgid_nr_ns(sender,
task_active_pid_ns(current));
} else {
trd->sender_pid = 0;
}sender_pid 和 sender_euid 是驱动根据 task/cred 填充的接收方可见身份,不是发送 descriptor 中可信字段。oneway 或发送线程已经消失时,t_from 可能为空,sender_pid 写 0。
3.3 Buffer指针
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()
trd->data_size = t->buffer->data_size;
trd->offsets_size = t->buffer->offsets_size;
trd->data.ptr.buffer =
t->buffer->user_data;
trd->data.ptr.offsets =
trd->data.ptr.buffer +
ALIGN(t->buffer->data_size,
sizeof(void *));read buffer 中只有 descriptor;payload 和 offsets 位于接收 proc 的 Binder VMA。服务端或客户端后续从 trd.data.ptr.buffer 读取,处理完成后通过 BC_FREE_BUFFER 把同一 buffer 交还驱动。
3.4 安全上下文
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()
tr.secctx = t->security_ctx;
if (t->security_ctx) {
cmd = BR_TRANSACTION_SEC_CTX;
trsize = sizeof(tr);
}
if (put_user(cmd,
(uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
if (copy_to_user(ptr, &tr, trsize))
return -EFAULT;
ptr += trsize;目标 node 请求 transaction security context 时,命令变成 BR_TRANSACTION_SEC_CTX,descriptor 尾部增加 secctx 指针。它与普通 BR_TRANSACTION 共用 data/offsets,但用户态必须按命令布局读取不同结构大小。
4. 用户态消费
4.1 等待回复
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:waitForResponse()
case BR_REPLY: {
binder_transaction_data tr;
err = mIn.read(&tr, sizeof(tr));
if (err != NO_ERROR)
goto finish;
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);
} else {
err = *reinterpret_cast<const status_t*>(
tr.data.ptr.buffer);
freeBuffer(
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));
}
goto finish;
}普通 reply 通过 ipcSetDataReference() 让 Parcel 借用目标 buffer;Parcel 的 freeBuffer 回调在读完对象后提交释放。TF_STATUS_CODE reply 不建立普通数据引用,而是从 buffer 中读取 status 并立即调用 freeBuffer。
4.2 收到请求
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:executeCommand()
case BR_TRANSACTION:
case BR_TRANSACTION_SEC_CTX: {
binder_transaction_data_secctx tr_secctx;
binder_transaction_data& tr =
tr_secctx.transaction_data;
if (cmd == BR_TRANSACTION_SEC_CTX)
err = mIn.read(&tr_secctx,
sizeof(tr_secctx));
else {
err = mIn.read(&tr,
sizeof(tr));
tr_secctx.secctx = 0;
}
if (err != NO_ERROR)
break;
Parcel buffer;
buffer.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);
mCallingPid = tr.sender_pid;
mCallingUid = tr.sender_euid;
mCallingSid = reinterpret_cast<const char*>(tr_secctx.secctx);
mLastTransactionBinderFlags =
tr.flags;
if (tr.target.ptr) {
if (reinterpret_cast<RefBase::weakref_type*>(
tr.target.ptr)
->attemptIncStrong(this)) {
BBinder* binder =
reinterpret_cast<BBinder*>(
tr.cookie);
error = doTransactBinder(
binder, tr.code,
buffer, &reply,
tr.flags);
binder->decStrong(this);
} else {
error = UNKNOWN_TRANSACTION;
}
} else {
BBinder* binder = the_context_object.get();
error = doTransactBinder(
binder, tr.code,
buffer, &reply,
tr.flags);
}
}服务端先从 descriptor 的地址建立借用目标 buffer 的 Parcel,再通过 target/cookie 找本地 BBinder,并由 doTransactBinder() 进入对象分发。这一步才进入服务对象代码;驱动生成 BR 只完成“把 work 交给用户态”。BR_TRANSACTION_SEC_CTX 还会把 secctx 写入当前调用身份。
4.3 完成回复
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:executeCommand()
if (!(tr.transaction_data.flags & TF_ONE_WAY)) {
if (error < NO_ERROR)
sendReply(reply, error);
else
sendReply(reply, 0);
} else {
if (reply.dataSize() != 0)
ALOGI("oneway reply data will be dropped");
}同步 BR_TRANSACTION 处理后用户态写 BC_REPLY;oneway 不写 reply,服务端即使填充了 reply Parcel 也会被丢弃或记录日志。驱动并不会为 oneway 自动生成 BC_REPLY。真实实现还会在同步分支发送前清空输入 buffer,避免服务端重新进入时客户端 buffer 尚未释放。
5. FD修复
5.1 目标fd安装
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_apply_fd_fixups()
ret = binder_apply_fd_fixups(proc, t);
if (ret) {
struct binder_buffer *buffer =
t->buffer;
buffer->transaction = NULL;
binder_cleanup_transaction(
t, "fd fixups failed",
BR_FAILED_REPLY);
binder_free_buf(
proc, thread, buffer, true);
if (cmd == BR_REPLY) {
cmd = BR_FAILED_REPLY;
put_user(cmd,
(uint32_t __user *)ptr);
ptr += sizeof(uint32_t);
break;
}
continue;
}FD fixup 发生在目标进程上下文,驱动先为目标 fd 分配槽位并把新 fd 写回 Binder buffer,再安装 file。失败时 BR_TRANSACTION 会清理并继续处理其他 work;BR_REPLY 失败要额外向原调用方写 BR_FAILED_REPLY。
5.2 成功交付
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()
trace_binder_transaction_received(t);
binder_stat_br(proc, thread, cmd);
if (t_from)
binder_thread_dec_tmpref(t_from);
t->buffer->allow_user_free = 1;
if (cmd != BR_REPLY &&
!(t->flags & TF_ONE_WAY)) {
binder_inner_proc_lock(thread->proc);
t->to_parent = thread->transaction_stack;
t->to_thread = thread;
thread->transaction_stack = t;
binder_inner_proc_unlock(thread->proc);
} else {
binder_free_transaction(t);
}同步请求交付后压入接收线程 transaction stack,保证后续 BC_REPLY 能找到它;BR_REPLY 和 oneway 请求不需要接收栈,事务对象可直接释放,但 buffer 仍等待用户 BC_FREE_BUFFER。
6. 错误返回
6.1 BR错误
源码文件: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;BR 错误是协议层返回,libbinder 映射成用户态 status。它们不等价于 binder_thread_read() 访问用户地址时的 ioctl -EFAULT;前者代表事务状态,后者代表返回 envelope/descriptor 写入失败。
6.2 Descriptor写失败
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()
if (put_user(cmd,
(uint32_t __user *)ptr)) {
binder_cleanup_transaction(
t, "put_user failed",
BR_FAILED_REPLY);
return -EFAULT;
}
if (copy_to_user(ptr, &tr, trsize)) {
binder_cleanup_transaction(
t, "copy_to_user failed",
BR_FAILED_REPLY);
return -EFAULT;
}work 已经从队列摘下后,descriptor 写入用户 read buffer 仍可能失败。驱动清理 transaction,防止它永远停留在“已取出但未交付”;用户态看到的是 ioctl -EFAULT,不是一条完整 BR_FAILED_REPLY。
6.3 FD失败
FD fixup 失败有特殊差异:BR_REPLY 会显式追加 BR_FAILED_REPLY,BR_TRANSACTION 则清理当前 transaction 并继续取下一个 work。这个差异保证 reply 调用方能结束等待,同时不让一个坏的服务请求阻塞同一目标线程的其他 work。
7. Buffer回收
7.1 回调入口
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:freeBuffer()、waitForResponse()
void IPCThreadState::freeBuffer(
const uint8_t* data, size_t dataSize,
const binder_size_t* objects,
size_t objectsSize) {
mOut.writeInt32(BC_FREE_BUFFER);
mOut.writePointer(
reinterpret_cast<uintptr_t>(data));
flushIfNeeded();
}用户态不直接 free 目标内核 buffer;freeBuffer 把目标地址写成 BC_FREE_BUFFER,随后由 driver binder_free_buf() 释放对象引用、fd fixup、node 引用和 allocator buffer。
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_write()、binder_free_buf()
case BC_FREE_BUFFER: {
binder_uintptr_t data_ptr;
struct binder_buffer *buffer;
if (get_user(data_ptr,
(binder_uintptr_t __user *)ptr))
return -EFAULT;
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;
}BC_FREE_BUFFER 只携带接收进程 VMA 中的地址。驱动先用 allocator 校验这个地址是否对应“已交付且尚未回收”的 buffer,再进入 binder_free_buf();因此 Parcel 释放回调是一次协议写入,不是用户态直接操作内核分配器。
7.2 事务栈
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()
t->buffer->allow_user_free = 1;
if (cmd != BR_REPLY &&
!(t->flags & TF_ONE_WAY)) {
t->to_parent =
thread->transaction_stack;
t->to_thread = thread;
thread->transaction_stack = t;
} else {
binder_free_transaction(t);
}BR_TRANSACTION 的同步请求把 transaction 栈绑定在服务线程;BC_REPLY 弹出它并把 reply work 送回原调用线程。BR_REPLY/oneway 不建立接收栈,但所有类型都要等用户 BC_FREE_BUFFER 后才释放目标 buffer。
8. 测试输入
8.1 多轮读写
源码文件:frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp
相关测试:Transaction
if (bwr.read_consumed <
offsetof(typeof(br), pad)) {
binderWaitForReadData(10000);
binderTestIoctl(
BINDER_WRITE_READ, &bwr);
}
EXPECT_EQ(BR_NOOP, br.cmd0);
EXPECT_EQ(BR_TRANSACTION_COMPLETE, br.cmd1);
EXPECT_EQ(BR_REPLY, br.cmd2);测试把写入和读取拆成多轮:第一轮提交 BC_TRANSACTION,第二轮等待并读取 BR_NOOP、BR_TRANSACTION_COMPLETE、BR_REPLY,第三轮通过 BC_FREE_BUFFER 归还 reply buffer。它证明返回命令顺序和 buffer 回收入口,不证明非空 Parcel 对象翻译。
8.2 Oneway
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
相关测试:NopTransactionOneway、OnewayQueueing
oneway 测试检查用户态调用成功和同 node 回调顺序;因为没有同步 reply,不能用它证明 BR_REPLY 会出现。
8.3 冻结
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
相关测试:FreezeTxn
冻结期间同步事务期望 frozen error,解冻后再次成功。测试覆盖 BR_FROZEN_REPLY 的用户态映射,不覆盖 descriptor 写失败或 FD fixup 失败。
9. 失败边界
| 阶段 | 失败 | 可观察结果 |
|---|---|---|
| 无 work | 非阻塞 | -EAGAIN,BR_NOOP 可能已写 |
| read 空间不足 | descriptor 不摘 work | 下一次继续读取 |
| FD fixup | reply 显式 BR_FAILED_REPLY | 事务 buffer 清理 |
| descriptor 写入 | put_user/copy_to_user 失败 | ioctl -EFAULT,transaction cleanup |
| BR error | target dead/failed/frozen | libbinder status 映射 |
| buffer 回收 | BC_FREE_BUFFER 无匹配 | 用户错误,buffer 不释放 |
BR 命令、ioctl errno 和 Parcel status code 是三层结果:事务状态、内核到用户地址访问、用户态 payload 错误,定位时不能互换。
10. 源码复现
本文主线:
thread/proc work
→ binder_thread_read
→ target_node判定
→ BR_TRANSACTION/BR_REPLY
→ descriptor与VMA地址
→ executeCommand/waitForResponse
→ BC_REPLY或oneway结束
→ freeBuffer→BC_FREE_BUFFER
→ binder_free_buf源码搜索:
rg -n "binder_thread_read|BR_TRANSACTION|BR_REPLY|BR_TRANSACTION_SEC_CTX" \
kernel/common/drivers/android/binder.c
rg -n "waitForResponse|executeCommand|freeBuffer|case BR_REPLY" \
frameworks/native/libs/binder/IPCThreadState.cpp
rg -n "binder_apply_fd_fixups|binder_free_buf|BC_FREE_BUFFER" \
kernel/common/drivers/android/binder.c
rg -n "TEST_F.*Transaction|NopTransactionOneway|OnewayQueueing|FreezeTxn" \
frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp \
frameworks/native/libs/binder/tests/binderLibTest.cpp如果能够说明为什么 target_node 决定 BR_TRANSACTION/BR_REPLY、为什么同步请求要压入服务线程 transaction stack、为什么 BR_REPLY 的 Parcel 可以借用目标 buffer、以及为什么 descriptor 写失败和 BR_FAILED_REPLY 不是同一层错误,就已经掌握了 Binder 返回命令的真实生命周期。
BR_REPLY 的数据引用和 BC_FREE_BUFFER 属于不同阶段;reply 被消费后,目标 buffer 才能进入释放/合并路径。
