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
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()
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()
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
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()
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()
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()
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()
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);
}决策可以压缩为:
| 条件 | 目标队列 | 是否立即唤醒 |
|---|---|---|
| 首笔且找到空闲 thread | thread->todo | 是 |
| 首笔但没有空闲 thread | proc->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()
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()
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()
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()
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()
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()
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()
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()
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()
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()
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()
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()
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()
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()
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
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
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 调用“返回成功但服务没按预期处理”时,可以按这个顺序定位:
- 查看
IPCThreadState::transact()是否真的带TF_ONE_WAY,以及发送方收到的是BR_TRANSACTION_COMPLETE、BR_TRANSACTION_PENDING_FROZEN还是BR_ONEWAY_SPAM_SUSPECT; - 在
binder_transaction()确认目标 buffer 的async_transaction和target_node; - 在
binder_proc_transaction()区分第一笔直接进入 thread/proc todo,还是后续进入node->async_todo; - 检查目标 proc 是否 frozen/dead,以及
TF_UPDATE_TXN是否满足完整匹配条件; - 进入
binder_free_buf(),确认当前 async buffer 是否释放、has_async_transaction是否清零或下一笔是否转入 proc todo; - 最后检查 async quota、
oneway_spam_suspect和 allocator free 状态。
源码搜索:
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 队列的真实生命周期。
