Skip to content

oneway异步事务队列

追踪 TF_ONE_WAY 事务从发送确认、节点队列、目标线程消费到 buffer 释放推进的真实调度路径。

基于android-17.0.0_r1
AndroidBinderoneway异步事务源码阅读

oneway异步事务队列 ​

TF_ONE_WAY 改变的不是 BC_TRANSACTION 命令名,而是 transaction 的等待和投递关系:发送方仍会收到一次完成状态,但不等待服务端 BC_REPLY;目标 node 同时只允许一笔异步事务处于“正在处理”状态,后续事务暂存在 node->async_todo,直到当前 buffer 释放才推进下一笔。

本文面向已经读过 Binder事务发送、Binder线程管理、binder_alloc内存分配器 和 binder_buffer管理 的读者。本文不重复 Parcel 编码、best-fit 分配和完整线程池启动,只回答:TF_ONE_WAY 如何进入驱动、第一笔事务为什么可能直接交给线程、后续事务如何进入 node 队列、冻结和 TF_UPDATE_TXN 何时改变行为,以及 buffer 回收如何解除串行化。

读完后,应该能从 IPCThreadState::transact() 找到 driver 的 async 分支,解释 has_async_transaction 与 async_todo 的区别,判断一个 oneway 调用得到 BR_TRANSACTION_COMPLETE、BR_TRANSACTION_PENDING_FROZEN 还是 BR_ONEWAY_SPAM_SUSPECT,并从 binder_free_buf() 定位下一笔异步 work 的生效时机。

1. 语义边界 ​

1.1 标志定义 ​

源码文件:kernel/common/include/uapi/linux/android/binder.h

相关类型:transaction_flags

c
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 的注释是 async、no return;TF_UPDATE_TXN 是“更新过时的 pending async transaction”。两个 flag 可以同时出现,但更新逻辑只在目标进程 frozen 且新旧事务满足严格匹配时生效。

1.2 用户态提交 ​

源码文件:frameworks/native/libs/binder/IPCThreadState.cpp

相关函数:IPCThreadState::transact()

cpp
status_t IPCThreadState::transact(
        int32_t handle, uint32_t code,
        const Parcel& data, Parcel* reply,
        uint32_t flags) {
    status_t err = writeTransactionData(
            BC_TRANSACTION, flags, handle,
            code, data, nullptr);
    if (err != NO_ERROR)
        return err;
    if ((flags & TF_ONE_WAY) == 0) {
        if (reply)
            err = waitForResponse(reply);
        else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
    } else {
        err = waitForResponse(nullptr, nullptr);
    }
    return err;
}

oneway 调用仍写入 BC_TRANSACTION,随后显式使用 waitForResponse(nullptr, nullptr) 等待 transaction completion 或错误命令;传入的 reply 指针不会参与 oneway 结果。因而“不等待服务端返回值”不等于“完全不经过 Binder 驱动确认”。

1.3 完成命令 ​

源码文件:frameworks/native/libs/binder/IPCThreadState.cpp

相关函数:IPCThreadState::waitForResponse()

cpp
case BR_TRANSACTION_COMPLETE:
    if (!reply && !acquireResult)
        goto finish;
    break;

case BR_TRANSACTION_PENDING_FROZEN:
    ALOGW("Sending oneway calls to frozen process.");
    goto finish;

case BR_ONEWAY_SPAM_SUSPECT:
    ALOGE("Process seems to be sending too many oneway calls.");
    [[fallthrough]];

普通 oneway 成功通常以 BR_TRANSACTION_COMPLETE 结束发送方等待;目标冻结时,已排队的 async transaction 可能返回 BR_TRANSACTION_PENDING_FROZEN;如果驱动标记了 oneway spam 且 proc 开启了检测,用户态会看到 BR_ONEWAY_SPAM_SUSPECT,随后按完成路径收束。这里没有 BR_REPLY Parcel。

2. 节点状态 ​

2.1 两个字段 ​

源码文件:kernel/common/drivers/android/binder_internal.h

相关类型:binder_node

c
struct binder_node {
    spinlock_t lock;
    struct binder_proc *proc;
    bool has_async_transaction;
    struct list_head async_todo;
};

has_async_transaction 是“当前 node 是否已有一笔异步事务占用串行化槽位”的状态位;async_todo 是等待队列。队列非空不代表当前事务已经完成,状态位也不等于队列中有多少个元素。

节点创建时初始化空队列:

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_init_node_ilocked()

c
node->proc = proc;
node->ptr = ptr;
node->cookie = cookie;
node->work.type = BINDER_WORK_NODE;
spin_lock_init(&node->lock);
INIT_LIST_HEAD(&node->async_todo);

2.2 锁分工 ​

has_async_transaction 受 node lock 保护;async_todo 作为 proc 相关 work list,入队、出队和与 proc todo 的转移需要 proc inner lock。驱动在 binder_proc_transaction() 中先持有 node lock 再取得 target proc inner lock,释放时反向进行;这保证“检查状态、决定队列、更新 outstanding count”处于同一决策窗口。

2.3 状态图 ​

状态从 Active 回到 Idle 的唯一正常 allocator 触发点是当前 async buffer 释放;服务方法返回、发送方收到 BR_TRANSACTION_COMPLETE 或用户态读完 Parcel,都不能单独推出 node 已经空闲。

3. 投递决策 ​

3.1 入口参数 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_transaction()、binder_proc_transaction()

c
BUG_ON(target_node == NULL);
BUG_ON(t->buffer->async_transaction != 1);

return_error = binder_proc_transaction(
        t, target_proc, NULL);
if (return_error == BR_TRANSACTION_PENDING_FROZEN) {
    tcomplete->type =
            BINDER_WORK_TRANSACTION_PENDING;
}
binder_enqueue_thread_work(
        thread, tcomplete);

oneway 传给 binder_proc_transaction() 的 thread 参数必须是 NULL。发送线程不负责处理目标请求,driver 可以从目标 proc 的 waiting threads 中选择消费者;发送方自己的 thread todo 只接收 tcomplete,而不是目标 transaction work。

3.2 首笔事务 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_proc_transaction()

c
bool oneway = !!(t->flags & TF_ONE_WAY);
bool pending_async = false;

binder_node_lock(node);
if (oneway) {
    BUG_ON(thread);
    if (node->has_async_transaction)
        pending_async = true;
    else
        node->has_async_transaction = true;
}

第一笔 oneway 把 has_async_transaction 从 false 改成 true,但此时还没有进入 async_todo。只要存在可用目标线程,它可以直接进入 thread->todo;没有可用线程时才进入 proc->todo。这就是“同 node 串行”与“固定 proc 队列”不同的地方。

3.3 队列选择 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_proc_transaction()

c
if (!thread && !pending_async)
    thread = binder_select_thread_ilocked(proc);

if (thread) {
    binder_transaction_priority(
            thread, t, node);
    binder_enqueue_thread_work_ilocked(
            thread, &t->work);
} else if (!pending_async) {
    binder_enqueue_work_ilocked(
            &t->work, &proc->todo);
} else {
    binder_enqueue_work_ilocked(
            &t->work, &node->async_todo);
}

决策可以压缩为:

条件目标队列是否立即唤醒
首笔且找到空闲 threadthread->todo是
首笔但没有空闲 threadproc->todo是
has_async_transaction 已为真node->async_todo否,等待当前 buffer
同步事务由目标 thread/proc 语义决定按同步规则

因此 async_todo 只保存“已有同 node async 事务在处理时到达”的后续 work;它不是所有 oneway 的总队列。

3.4 计数与唤醒 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_proc_transaction()

c
if (!pending_async)
    binder_wakeup_thread_ilocked(
            proc, thread, !oneway);

proc->outstanding_txns++;
binder_inner_proc_unlock(proc);
binder_node_unlock(node);

queued async work 不立即唤醒目标线程,因为当前 async buffer 尚未释放;首笔 work 和同步 work 则可以唤醒消费者。每一笔被接受的 transaction 都增加目标 proc 的 outstanding_txns,后续 transaction free 或被替换时减少它。

4. 消费推进 ​

4.1 用户态消费 ​

源码文件:frameworks/native/libs/binder/IPCThreadState.cpp

相关函数:IPCThreadState::executeCommand()

cpp
if ((tr.flags & TF_ONE_WAY) == 0) {
    if (error < NO_ERROR)
        reply.setError(error);
    buffer.setDataSize(0);
    sendReply(reply,
            tr.flags & TF_CLEAR_BUF);
} else {
    if (error != OK || reply.dataSize() != 0)
        ALOGI("oneway results will be dropped");
    LOG_ONEWAY("NOT sending reply");
}

服务端执行 oneway 方法后不发送 BC_REPLY;reply Parcel 内容被丢弃或只用于日志。输入 buffer 的释放仍由 Parcel 的 freeBuffer 回调触发,随后驱动才能继续 node 的 async queue。服务方法返回不是队列推进点,buffer 回收才是。

4.2 释放推进 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_free_buf()

c
if (buffer->async_transaction &&
        buffer->target_node) {
    struct binder_node *buf_node =
            buffer->target_node;
    struct binder_work *w;

    binder_node_inner_lock(buf_node);
    BUG_ON(!buf_node->has_async_transaction);
    w = binder_dequeue_work_head_ilocked(
            &buf_node->async_todo);
    if (!w) {
        buf_node->has_async_transaction = false;
    } else {
        binder_enqueue_work_ilocked(
                w, &proc->todo);
        binder_wakeup_proc_ilocked(proc);
    }
    binder_node_inner_unlock(buf_node);
}

当前 buffer 被释放时,驱动从 async todo 取队头;没有下一笔就清除状态位,有下一笔就把它转移到 proc todo 并唤醒消费者。队列因此形成“一个 active buffer + 一个等待链表”的单节点串行模型。

4.3 目标进程退出 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_node_release()、binder_release_work()

c
binder_release_work(proc, &node->async_todo);

目标 node 所属进程退出时,驱动先释放 node 的 async todo work;这些未交付的 oneway transaction 不再等待 buffer 回收。目标 proc 随后的 deferred release 还会清理 proc todo;发送方进程退出时,它自己的未交付完成通知也由对应 work release 路径收束。

5. 冻结路径 ​

5.1 目标冻结 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_proc_transaction()

c
if (proc->is_frozen) {
    frozen = true;
    proc->sync_recv |= !oneway;
    proc->async_recv |= oneway;
}

if ((frozen && !oneway) ||
        proc->is_dead) {
    binder_inner_proc_unlock(proc);
    binder_node_unlock(node);
    return frozen
            ? BR_FROZEN_REPLY
            : BR_DEAD_REPLY;
}

冻结目标对同步 transaction 返回 BR_FROZEN_REPLY;oneway 则允许排队,并记录 proc->async_recv = true。如果 async transaction 被挂起等待解冻,调用者得到 BR_TRANSACTION_PENDING_FROZEN,不是成功执行完成。

5.2 Pending通知 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_transaction()、binder_thread_read()

c
if (return_error == BR_TRANSACTION_PENDING_FROZEN)
    tcomplete->type =
            BINDER_WORK_TRANSACTION_PENDING;
binder_enqueue_thread_work(
        thread, tcomplete);

发送线程最终读到 BR_TRANSACTION_PENDING_FROZEN。这个命令只说明 async work 已经进入冻结等待状态,不能证明目标方法已执行;解冻后 work 仍需从合适队列被消费,buffer 释放也仍是最终推进点。

5.3 恢复时机 ​

解冻动作不会把服务方法直接调用起来。它只改变目标 proc 的冻结状态和后续投递条件;真正的执行顺序仍由目标线程读取 proc todo、调用 executeCommand()、处理 Parcel 并释放 buffer 决定。恢复路径不能跳过 async queue 的队头约束。

6. 更新事务 ​

6.1 匹配条件 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_can_update_transaction()

c
if ((t1->flags & t2->flags &
        (TF_ONE_WAY | TF_UPDATE_TXN)) !=
        (TF_ONE_WAY | TF_UPDATE_TXN))
    return false;
if (!t1->to_proc || !t2->to_proc)
    return false;
return t1->to_proc->tsk == t2->to_proc->tsk &&
       t1->code == t2->code &&
       t1->flags == t2->flags &&
       t1->buffer->pid == t2->buffer->pid &&
       t1->buffer->target_node->ptr ==
           t2->buffer->target_node->ptr &&
       t1->buffer->target_node->cookie ==
           t2->buffer->target_node->cookie;

更新不是“相同接口就覆盖旧消息”。新旧事务必须同目标 proc、同 code、同 flags、同发送 pid,并且指向同一 node 的 ptr/cookie;两者还必须同时带 TF_ONE_WAY | TF_UPDATE_TXN。

6.2 仅冻结队列 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_proc_transaction()、binder_find_outdated_transaction_ilocked()

c
if ((t->flags & TF_UPDATE_TXN) && frozen) {
    t_outdated = binder_find_outdated_transaction_ilocked(
            t, &node->async_todo);
    if (t_outdated) {
        list_del_init(&t_outdated->work.entry);
        proc->outstanding_txns--;
    }
}
binder_enqueue_work_ilocked(
        &t->work, &node->async_todo);

驱动只在 pending_async 且目标 frozen 时扫描 node->async_todo。被替换的旧 transaction 在释放锁后清理 buffer、对象和计数;新 transaction 仍排在 async todo 中。正常运行的非冻结队列不会因为 TF_UPDATE_TXN 自动丢弃旧 work。

7. 异步配额 ​

7.1 空间入口 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:binder_alloc_new_buf_locked()

c
if (is_async &&
        alloc->free_async_space < size)
    return ERR_PTR(-ENOSPC);

buffer->async_transaction = is_async;
if (is_async) {
    alloc->free_async_space -= size;
    if (debug_low_async_space_locked(alloc))
        buffer->oneway_spam_suspect = true;
}

oneway 的空间限制发生在 buffer 分配阶段,早于 node queue 投递。async 配额不足时,事务根本不会进入 async_todo;这和“队列已有前一笔所以排队”是两种不同状态。

7.2 Spam检测 ​

源码文件:kernel/common/drivers/android/binder_alloc.c

相关函数:debug_low_async_space_locked()

c
if (alloc->free_async_space >=
        alloc->buffer_size / 10) {
    alloc->oneway_spam_detected = false;
    return false;
}

if (num_buffers > 50 ||
        total_alloc_size >
            alloc->buffer_size / 4) {
    if (!alloc->oneway_spam_detected) {
        alloc->oneway_spam_detected = true;
        return true;
    }
}
return false;

检测在 async 空间低于总 buffer 的 10% 后才统计当前 pid;超过 50 个 async buffer 或占用超过总 buffer 的 25% 才标记本次 buffer。它是一次性 suspect 边沿,不是每笔 oneway 都返回错误,也不是队列长度的直接计数。

7.3 返回路径 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_transaction()、binder_thread_read()

c
if (t->buffer->oneway_spam_suspect)
    tcomplete->type =
            BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT;
else
    tcomplete->type =
            BINDER_WORK_TRANSACTION_COMPLETE;

后续 read 处理 BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT 时,如果 proc 开启 oneway spam detection,写出 BR_ONEWAY_SPAM_SUSPECT;否则仍写 BR_TRANSACTION_COMPLETE。因此相同内核 buffer 状态在 feature 未开启时不会改变用户态命令。

8. 失败与清理 ​

8.1 目标死亡 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_proc_transaction()、binder_cleanup_transaction()

c
if (proc->is_dead) {
    binder_inner_proc_unlock(proc);
    binder_node_unlock(node);
    return BR_DEAD_REPLY;
}

oneway 也不能投递到已死亡 proc。已经在 async_todo 中的 work 则会在 node/proc release 中被取出并清理;未交付的 oneway 不会产生服务端 reply,发送方只会根据其 transaction completion/error work 结束等待。

8.2 替换清理 ​

被 TF_UPDATE_TXN 替换的旧 work 在释放锁后处理:

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_proc_transaction()

c
if (t_outdated) {
    struct binder_buffer *buffer =
            t_outdated->buffer;
    t_outdated->buffer = NULL;
    buffer->transaction = NULL;
    binder_release_entire_buffer(
            proc, NULL, buffer, false);
    binder_alloc_free_buf(
            &proc->alloc, buffer);
    kfree(t_outdated);
}

替换不是只从链表删除指针:旧 transaction 的 buffer、offsets 对象和 allocator 区间都要回收,目标 proc 的 outstanding count 也已在出队时同步减少。

8.3 进程回收 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_release_work()

c
case BINDER_WORK_TRANSACTION_PENDING:
case BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT:
case BINDER_WORK_TRANSACTION_COMPLETE:
    kfree(w);
    binder_stats_deleted(
            BINDER_STAT_TRANSACTION_COMPLETE);
    break;

进程退出时,尚未交付的完成通知、冻结 pending 通知和 spam suspect work 都需要释放;队列中的真实 transaction work 则走 binder_cleanup_transaction()。这保证发送方或目标 proc 退出不会留下只属于 oneway 的悬挂 work。

9. 测试边界 ​

9.1 基本无回复 ​

源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp

相关测试:BinderLibTest.NopTransactionOneway

cpp
Parcel data, reply;
EXPECT_THAT(m_server->transact(
        BINDER_LIB_TEST_NOP_TRANSACTION,
        data, &reply, TF_ONE_WAY),
        StatusEq(NO_ERROR));

输入是一个空 Parcel 和 TF_ONE_WAY,断言是调用成功;它验证用户态无需普通 reply 即可完成,不证明目标方法已经在调用返回前执行,也不证明队列只使用一个线程。

9.2 FIFO队列 ​

源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp

相关测试:BinderLibTest.OnewayQueueing

cpp
data.writeStrongBinder(callBack);
data.writeInt32(500000);
data2.writeStrongBinder(callBack2);
data2.writeInt32(0);

EXPECT_THAT(pollServer->transact(
        BINDER_LIB_TEST_DELAYED_CALL_BACK,
        data, nullptr, TF_ONE_WAY),
        StatusEq(NO_ERROR));
EXPECT_THAT(pollServer->transact(
        BINDER_LIB_TEST_DELAYED_CALL_BACK,
        data2, nullptr, TF_ONE_WAY),
        StatusEq(NO_ERROR));

第一笔延迟 500000 微秒,第二笔不延迟;单线程 server 中第二笔因此进入 async queue。测试等待两个 callback 并检查结果,证明同一目标 node 的等待 work 不越过当前 active work;它不证明时钟精度、全局 FIFO 或多 node 之间的顺序。

9.3 冻结和spam ​

源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp

相关测试:BinderLibTest.Freeze、BinderLibTest.FreezeTxn 以及 oneway spam netlink 测试路径

Freeze 在冻结前发送多笔 oneway,并读取 sync_recv/async_recv 状态;FreezeTxn 冻结另一个 server 后断言同步调用返回 frozen error,解冻后再次成功。两者共同覆盖 frozen proc 对 async/sync 的不同记录与返回边界。spam 测试在冻结目标上发送大 async vector,等待 netlink 的 BR_ONEWAY_SPAM_SUSPECT 事件;它依赖 kernel feature、proc 检测开关和 netlink 支持,不能外推成所有设备默认都会报告 spam。

10. 源码导航 ​

遇到 oneway 调用“返回成功但服务没按预期处理”时,可以按这个顺序定位:

  1. 查看 IPCThreadState::transact() 是否真的带 TF_ONE_WAY,以及发送方收到的是 BR_TRANSACTION_COMPLETE、BR_TRANSACTION_PENDING_FROZEN 还是 BR_ONEWAY_SPAM_SUSPECT;
  2. 在 binder_transaction() 确认目标 buffer 的 async_transaction 和 target_node;
  3. 在 binder_proc_transaction() 区分第一笔直接进入 thread/proc todo,还是后续进入 node->async_todo;
  4. 检查目标 proc 是否 frozen/dead,以及 TF_UPDATE_TXN 是否满足完整匹配条件;
  5. 进入 binder_free_buf(),确认当前 async buffer 是否释放、has_async_transaction 是否清零或下一笔是否转入 proc todo;
  6. 最后检查 async quota、oneway_spam_suspect 和 allocator free 状态。

源码搜索:

bash
rg -n "TF_ONE_WAY|binder_proc_transaction|has_async_transaction|async_todo" \
  kernel/common/drivers/android/binder.c \
  kernel/common/drivers/android/binder_internal.h \
  kernel/common/include/uapi/linux/android/binder.h

rg -n "binder_free_buf|BINDER_WORK_TRANSACTION_PENDING|BR_TRANSACTION_PENDING_FROZEN|BR_ONEWAY_SPAM_SUSPECT" \
  kernel/common/drivers/android/binder.c \
  frameworks/native/libs/binder/IPCThreadState.cpp

rg -n "OnewayQueueing|NopTransactionOneway|FreezeTxn|ONEWAY_SPAM" \
  frameworks/native/libs/binder/tests/binderLibTest.cpp \
  frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp

如果能解释“为什么第一笔 oneway 不一定进入 async_todo”“为什么服务方法返回仍不能推进下一笔”“为什么 frozen + TF_UPDATE_TXN 才允许替换旧 work”“为什么 async quota 耗尽发生在 queue 之前”,就已经掌握了 Binder oneway 队列的真实生命周期。