BINDER_WRITE_READ命令
BINDER_WRITE_READ 是 Binder 用户态和驱动之间的双向调用。用户态把尚未处理的 BC 命令放在 write buffer,把能够接收 BR 命令的区域放在 read buffer;驱动先消费写入,再按当前线程和进程的 work 状态填充读取结果,最后把 write_consumed 与 read_consumed 写回同一个 envelope。
本文面向已经读过 Binder ioctl入口、Binder线程状态模型 和 Binder内存映射 的读者,回答一次 BINDER_WRITE_READ 从参数复制到用户态游标更新的完整问题。
文章不展开每个 BC 命令对应的 node/ref/transaction 算法;它展示写循环和读循环的控制边界,并 把事务创建、对象翻译和 reply 细节留给后续专题。UAPI 用 consumed 表达实际进度;当前 libbinder 则要求一次成功调用完整消费本轮 mOut,部分成功会触发 fatal。read buffer 仍可能只容纳部分 BR。
1. 双向信封
源码文件:kernel/common/include/uapi/linux/android/binder.h
相关定义:struct binder_write_read
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;
};六个字段分成两组:write 组描述用户态已经准备好的 BC 字节,read 组描述用户态愿意接收的 BR 字节。write_consumed 和 read_consumed 是出参,调用后才表示驱动实际推进了多少游标。
2. 用户准备
2.1 命令缓冲
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:IPCThreadState::talkWithDriver()
status_t IPCThreadState::talkWithDriver(
bool doReceive) {
if (mProcess->mDriverFD < 0)
return -EBADF;
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<binder_uintptr_t>(
mOut.data());
if (doReceive && needRead) {
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer =
reinterpret_cast<binder_uintptr_t>(
mIn.data());
} else {
bwr.read_size = 0;
bwr.read_buffer = 0;
}
if (bwr.write_size == 0 &&
bwr.read_size == 0)
return NO_ERROR;
bwr.write_consumed = 0;
bwr.read_consumed = 0;
status_t err;
do {
if (ioctl(mProcess->getDriverFD(),
BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
else
err = -errno;
} while (err == -EINTR);
if (err >= NO_ERROR) {
if (bwr.write_consumed > 0) {
if (bwr.write_consumed < mOut.dataSize())
LOG_ALWAYS_FATAL(
"Driver did not consume write buffer");
mOut.setDataSize(0);
processPostWriteDerefs();
}
if (bwr.read_consumed > 0) {
mIn.setDataSize(bwr.read_consumed);
mIn.setDataPosition(0);
}
return NO_ERROR;
}
return err;
}mOut 保存待发送 BC,mIn 保存驱动返回的 BR。mIn 还有未解析数据时,needRead 为 false, 用户态会暂缓继续写入,先消费现有 BR;doReceive == false 则明确只发送。两种条件避免把同一 线程的输入游标和新的 BC 命令交错处理。
talkWithDriver() 还会把 EINTR 当作可重试的系统调用中断。其他 errno 直接返回;只有成功返回 后才依据 consumed 移动 mOut/mIn 游标。
2.2 参数复制
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_ioctl_write_read()
static int binder_ioctl_write_read(
struct file *filp, unsigned long arg,
struct binder_thread *thread) {
int ret = 0;
struct binder_proc *proc = filp->private_data;
void __user *ubuf = (void __user *)arg;
struct binder_write_read bwr;
bool has_special_work = false;
if (copy_from_user(&bwr, ubuf, sizeof(bwr)))
return -EFAULT;
if (bwr.write_size > 0) {
ret = binder_thread_write(proc, thread,
bwr.write_buffer, bwr.write_size,
&bwr.write_consumed);
trace_binder_write_done(ret);
if (ret < 0) {
bwr.read_consumed = 0;
goto out;
}
}
if (bwr.read_size > 0) {
ret = binder_thread_read(proc, thread,
bwr.read_buffer, bwr.read_size,
&bwr.read_consumed,
filp->f_flags & O_NONBLOCK);
trace_binder_read_done(ret);
if (ret < 0)
goto out;
}
out:
if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
ret = -EFAULT;
return ret;
}入口先复制 envelope,再固定执行“先写后读”。写失败不回滚已经成功处理的前置 BC,只清零 read_consumed 并跳过读阶段。最后的 copy_to_user 无论成功、写失败还是读失败都会尝试执行,回写失败会覆盖原返回值。
3. 写入消费
3.1 指针推进
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_write()
static int binder_thread_write(
struct binder_proc *proc,
struct binder_thread *thread,
binder_uintptr_t binder_buffer,
size_t size,
binder_size_t *consumed) {
uint32_t cmd;
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) {
if (get_user(cmd,
(uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(uint32_t);
trace_binder_command(cmd);
/* 根据 cmd 读取参数并调用专属 helper */
}
*consumed = ptr - buffer;
return 0;
}写循环从 *consumed 偏移开始。命令号先占用 4 字节,参数长度由具体 case 决定;因此 consumed 是字节游标,不是命令计数。已有 thread return error 时循环停止,避免在错误状态下继续解析 BC。
3.2 引用命令
源码文件:kernel/common/drivers/android/binder.c
相关命令:BC_INCREFS、BC_ACQUIRE、BC_RELEASE、BC_DECREFS
case BC_INCREFS:
case BC_ACQUIRE:
case BC_RELEASE:
case BC_DECREFS: {
uint32_t target;
bool strong = cmd == BC_ACQUIRE ||
cmd == BC_RELEASE;
bool increment = cmd == BC_INCREFS ||
cmd == BC_ACQUIRE;
struct binder_ref_data rdata;
if (get_user(target,
(uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(target);
ret = binder_update_ref_for_handle(
proc, target, increment, strong,
&rdata);
if (ret)
binder_user_error(
"%d:%d invalid ref %d\n",
proc->pid, thread->pid, target);
break;
}该 case 消费 handle 并把状态交给 proc 的引用 owner。无效 handle 会记录用户错误,但不能泛化为所有 BC 错误都会使 ioctl 返回 errno;每个 case 的错误处理都需要单独阅读。
3.3 事务边界
源码文件:kernel/common/drivers/android/binder.c
相关命令:BC_TRANSACTION、BC_REPLY
case BC_TRANSACTION:
case BC_REPLY: {
struct binder_transaction_data tr;
int reply = cmd == BC_REPLY;
if (copy_from_user(&tr, ptr, sizeof(tr)))
return -EFAULT;
ptr += sizeof(tr);
binder_transaction(
proc, thread, &tr, reply, 0);
break;
}写入口只把 descriptor 交给 binder_transaction()。目标查找、allocator buffer、对象翻译、排队和失败 reply 都在后续 helper 中完成。本文的边界是“BC 字节如何到达事务 helper”,不是事务算法本身。
3.4 消费发布
相关源码:
kernel/common/drivers/android/binder.cframeworks/native/libs/binder/IPCThreadState.cpp
成功处理后驱动把 ptr - buffer 写入 write_consumed。如果第三个 BC 参数复制失败,前两个 BC 可能已经改变内核状态;但当前 libbinder 对非 EINTR 错误不会根据 consumed 继续发送剩余字节, 而是把这条线程连接视为异常。consumed 在这里首先是诊断和协议进度,不是自动重放承诺。
4. 读取等待
4.1 初始BR_NOOP
源码文件: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 从零 consumed 开始时先写 BR_NOOP。它只是读协议的起点,不表示有事务;之后驱动仍要根据 work 状态选择阻塞、EAGAIN 或其他 BR。
4.2 阻塞与非阻塞
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()、binder_wait_for_work()
thread->looper |= BINDER_LOOPER_STATE_WAITING;
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);
}
thread->looper &= ~BINDER_LOOPER_STATE_WAITING;
if (ret)
return ret;非阻塞 fd 没有工作时返回 -EAGAIN;阻塞 fd 进入线程 wait queue。信号打断等待返回 -EINTR。两者都不是 BR 错误,也不代表 transaction 失败。
4.3 等待队列
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_wait_for_work()
for (;;) {
prepare_to_wait(&thread->wait, &wait,
TASK_INTERRUPTIBLE |
TASK_FREEZABLE);
if (binder_has_work_ilocked(
thread, do_proc_work))
break;
if (do_proc_work)
list_add(&thread->waiting_thread_node,
&proc->waiting_threads);
binder_inner_proc_unlock(proc);
schedule();
binder_inner_proc_lock(proc);
list_del_init(&thread->waiting_thread_node);
if (signal_pending(current)) {
ret = -EINTR;
break;
}
}只有可消费 proc work 的线程进入 proc->waiting_threads。唤醒后重新检查 work 条件,再从等待列表移除;这保证事务选择者不会唤醒已经不再等待的线程。
5. 读取工作
5.1 队列选择
源码文件: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);
break;
}
w = binder_dequeue_work_head_ilocked(list);
if (binder_worklist_empty_ilocked(
&thread->todo))
thread->process_todo = false;读取优先消费 thread todo;只有线程没有 transaction stack 和 thread todo 时才消费 proc todo。work 摘链后释放 inner lock,再向用户空间写入 BR,避免用户地址访问阻塞 Binder 内部锁。
5.2 完成命令
源码文件:kernel/common/drivers/android/binder.c
相关 work:BINDER_WORK_TRANSACTION_COMPLETE、BINDER_WORK_TRANSACTION_PENDING、BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT
case BINDER_WORK_TRANSACTION_COMPLETE:
case BINDER_WORK_TRANSACTION_PENDING:
case BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT: {
uint32_t cmd;
if (w->type == BINDER_WORK_TRANSACTION_PENDING)
cmd = BR_TRANSACTION_PENDING_FROZEN;
else if (w->type ==
BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT)
cmd = BR_ONEWAY_SPAM_SUSPECT;
else
cmd = BR_TRANSACTION_COMPLETE;
binder_inner_proc_unlock(proc);
kfree(w);
if (put_user(cmd,
(uint32_t __user *)ptr))
return -EFAULT;
ptr += sizeof(cmd);
}内核 work 类型在 read 阶段转换成用户可见的 BR 命令。完成 work 没有 transaction descriptor;冻结 pending 和 oneway spam 是不同状态,不能统称普通完成。
5.3 事务描述符
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()
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;
trd->data.ptr.buffer = t->buffer->user_data;
trd->data.ptr.offsets =
trd->data.ptr.buffer +
ALIGN(t->buffer->data_size,
sizeof(void *));目标 node 存在时返回 BR_TRANSACTION,否则返回 BR_REPLY。descriptor 中的 buffer/offsets 指针指向 BD014 建立的目标 VMA,而不是 read envelope 中的 payload。带 security context 时命令还会变成 BR_TRANSACTION_SEC_CTX。
5.4 用户回写失败
源码文件: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 已经从队列取出后,用户 read buffer 仍可能无效。驱动清理 transaction 和目标 buffer,避免对象停留在“已取出但未交付”状态;用户态看到的是 ioctl -EFAULT,不是一条完整的 BR 错误消息。
6. 线程扩容
6.1 Spawn条件
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()
*consumed = ptr - buffer;
binder_inner_proc_lock(proc);
if (proc->requested_threads == 0 &&
list_empty(&proc->waiting_threads) &&
proc->requested_threads_started <
proc->max_threads &&
(thread->looper &
(BINDER_LOOPER_STATE_REGISTERED |
BINDER_LOOPER_STATE_ENTERED))) {
proc->requested_threads++;
binder_inner_proc_unlock(proc);
if (put_user(BR_SPAWN_LOOPER,
(uint32_t __user *)buffer))
return -EFAULT;
binder_stat_br(proc, thread,
BR_SPAWN_LOOPER);
} else {
binder_inner_proc_unlock(proc);
}BR_SPAWN_LOOPER 不是内核创建 pthread,而是驱动增加 requested_threads 后要求用户态创建并注册线程。条件同时要求没有未完成请求、没有 waiting thread、线程数低于 max,且当前 looper 已注册或进入。
6.2 读取完成
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_thread_read()
done:
*consumed = ptr - buffer;
binder_inner_proc_lock(proc);
trace_android_vh_binder_spawn_new_thread(
thread, proc, &force_spawn);
binder_inner_proc_unlock(proc);
return 0;read buffer 空间不足时循环会提前停止,剩余 work 留在队列;下一次 BINDER_WRITE_READ 继续读取。read_consumed 表示本次填入 read buffer 的字节数,不是 proc work 总量。
7. 用户游标
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:IPCThreadState::talkWithDriver()
if (ioctl(mProcess->getDriverFD(),
BINDER_WRITE_READ, &bwr) >= 0) {
if (bwr.write_consumed > 0) {
LOG_ALWAYS_FATAL_IF(
bwr.write_consumed < mOut.dataSize(),
"Driver did not consume write buffer");
mOut.setDataSize(0);
processPostWriteDerefs();
}
if (bwr.read_consumed > 0) {
mIn.setDataSize(bwr.read_consumed);
mIn.setDataPosition(0);
}
return NO_ERROR;
}
return -errno;用户态只在系统调用成功时更新 mOut/mIn。mOut.setDataSize(0) 表示本轮 BC 已由驱动完整消费, 随后 processPostWriteDerefs() 处理写入完成后的延迟引用;mIn.setDataSize() 加上 position 重置让后续 IPCThreadState 解析 BR。它们都不代表 payload 被再次复制。
8. 测试输入
8.1 空读写
源码文件:frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp
相关测试:WriteReadNull、WriteReadEmpty、Read
TEST_F(BinderDriverInterfaceTest, WriteReadNull) {
binderTestIoctlErr1(
BINDER_WRITE_READ, nullptr, EFAULT);
}
TEST_F(BinderDriverInterfaceTest, WriteReadEmpty) {
struct binder_write_read bwr = {};
binderTestIoctl(BINDER_WRITE_READ, &bwr);
}
TEST_F(BinderDriverInterfaceTest, Read) {
binderTestReadEmpty();
}NULL envelope 验证第一层参数复制失败;空 envelope 验证无读写时入口可返回;Read 在非阻塞 fd 上期待 EAGAIN 并检查 read_consumed 不会错误增加。
8.2 写入消费
源码文件:frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp
相关测试:IncRefsAcquireReleaseDecRefs
const uint32_t bc[] = {
BC_INCREFS, 0,
BC_ACQUIRE, 0,
BC_RELEASE, 0,
BC_DECREFS, 0,
};
struct binder_write_read bwr = {};
bwr.write_buffer = (uintptr_t)bc;
bwr.write_size = sizeof(bc);
binderTestIoctl(BINDER_WRITE_READ, &bwr);
EXPECT_EQ(sizeof(bc), bwr.write_consumed);测试输入是四组引用命令且没有 read buffer。断言要求整个 BC 数组被消费,随后空读确认没有遗留 BR 错误;它不证明 transaction 或长期引用生命周期。
8.3 事务输入
源码文件:frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp
相关测试:Transaction、Reply
TEST_F(BinderDriverInterfaceTest, Transaction) {
binder_transaction_data tr = {};
tr.target.handle = 0;
tr.flags = TF_ACCEPT_FDS;
binderTestTransaction(BC_TRANSACTION, &tr);
}空 payload 事务只证明 BC_TRANSACTION 能到达写入口,不能证明真实服务目标、非空 Parcel、fd 翻译或 BR_TRANSACTION 交付。
9. 失败边界
| 位置 | 结果 | 含义 |
|---|---|---|
| envelope 复制 | -EFAULT | 没有进入读写循环 |
| BC 参数复制 | -EFAULT | 前置 BC 可能已经消费 |
| 写 helper 失败 | errno | 跳过读,read_consumed 清零 |
| 非阻塞无工作 | -EAGAIN | 没有进入等待 |
| 阻塞被信号打断 | -EINTR | 从等待队列返回 |
| BR 写用户空间失败 | -EFAULT | transaction/work 需要清理 |
| envelope 回写失败 | -EFAULT | 用户态无法可信更新 consumed |
失败路径有不同 owner:写失败反映用户提供的 BC/参数,读失败可能发生在驱动已经取走 work 后的用户地址访问。不能用同一个“重试 ioctl”策略覆盖全部分支。
10. 源码复现
双向Envelope
write 已消费不等于 read 已返回;在阻塞模式中 binder_thread_read 可能等待 work,非阻塞模式则可返回 EAGAIN。这也是排查“命令写成功但服务未收到”时必须分别看 write/read 两个游标的原因。
本文主线:
IPCThreadState buffers
→ binder_write_read envelope
→ binder_ioctl_write_read
→ binder_thread_write
→ write_consumed
→ binder_thread_read
→ wait queue 或 thread/proc work
→ BR 命令与 transaction descriptor
→ read_consumed
→ 用户态 mOut/mIn 游标源码搜索:
rg -n "talkWithDriver|BINDER_WRITE_READ|write_consumed|read_consumed" \
frameworks/native/libs/binder/IPCThreadState.cpp \
frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp
rg -n "binder_ioctl_write_read|binder_thread_write|binder_thread_read" \
kernel/common/drivers/android/binder.c
rg -n "binder_wait_for_work|BR_NOOP|BR_SPAWN_LOOPER|BR_TRANSACTION" \
kernel/common/drivers/android/binder.c
rg -n "WriteReadNull|WriteReadEmpty|IncRefsAcquireReleaseDecRefs|Transaction" \
frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp如果能够说明为什么写失败仍可能保留已经消费的 BC、为什么非阻塞读返回 -EAGAIN 不等于 BR 错误、为什么 transaction descriptor 指向 BD014 的接收 VMA,以及为什么 read buffer 空间不足会让 work 留到下一次调用,就已经掌握了 BINDER_WRITE_READ 的真实边界。
