Skip to content

BR事务返回

追踪 binder_thread_read 生成 BR_TRANSACTION/BR_REPLY、用户态消费、FD 修复和 buffer 回收。

基于android-17.0.0_r1
AndroidBinderBR_TRANSACTIONBR_REPLY源码阅读

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_TRANSACTIONt->buffer->target_node != NULLexecuteCommand()接收服务进程
BR_TRANSACTION_SEC_CTXtransaction 带 security contextexecuteCommand()接收服务进程
BR_REPLYtarget_node == NULLwaitForResponse()原调用方进程

2. 读取入口 ​

2.1 初始占位 ​

源码文件: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 的第一个命令是 BR_NOOP。它不是事务,也不是“驱动已经有工作”的证明。之后驱动会检查当前线程能否消费 proc work,并根据 fd 是否非阻塞决定 -EAGAIN 或进入 wait queue。

2.2 队列选择 ​

源码文件: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);
    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()

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

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

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

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

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

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

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

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

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

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

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

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 已经从队列摘下后,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()

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

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

c
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

cpp
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 fixupreply 显式 BR_FAILED_REPLY事务 buffer 清理
descriptor 写入put_user/copy_to_user 失败ioctl -EFAULT,transaction cleanup
BR errortarget dead/failed/frozenlibbinder status 映射
buffer 回收BC_FREE_BUFFER 无匹配用户错误,buffer 不释放

BR 命令、ioctl errno 和 Parcel status code 是三层结果:事务状态、内核到用户地址访问、用户态 payload 错误,定位时不能互换。

10. 源码复现 ​

本文主线:

text
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

源码搜索:

bash
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 才能进入释放/合并路径。