Skip to content

BINDER_WRITE_READ命令

追踪 BINDER_WRITE_READ 的双向信封、写入消费、读取等待、BR 返回和失败收束。

基于android-17.0.0_r1
AndroidBinderBINDER_WRITE_READ驱动源码阅读

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

c
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()

cpp
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()

c
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()

c
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

c
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

c
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.c
  • frameworks/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()

c
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()

c
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()

c
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()

c
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

c
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()

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;
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()

c
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()

c
*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()

c
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()

cpp
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

cpp
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

cpp
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

cpp
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 写用户空间失败-EFAULTtransaction/work 需要清理
envelope 回写失败-EFAULT用户态无法可信更新 consumed

失败路径有不同 owner:写失败反映用户提供的 BC/参数,读失败可能发生在驱动已经取走 work 后的用户地址访问。不能用同一个“重试 ioctl”策略覆盖全部分支。

10. 源码复现 ​

双向Envelope ​

write 已消费不等于 read 已返回;在阻塞模式中 binder_thread_read 可能等待 work,非阻塞模式则可返回 EAGAIN。这也是排查“命令写成功但服务未收到”时必须分别看 write/read 两个游标的原因。

本文主线:

text
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 游标

源码搜索:

bash
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 的真实边界。