事务数据生命周期
binder_transaction_data 看起来只是一个结构体,但同一字段在发送、驱动投递和 reply 阶段并不 拥有同一种含义。target.handle 是客户端发出的引用描述,服务端收到的则是 target.ptr/cookie; sender_pid/euid 由驱动填写,不能相信发送方初始化的零值;data.ptr 既可以指向发送方 Parcel, 也可以指向目标进程映射的 buffer。
本文面向已经读过 Binder协议状态机 和 Binder对象编码 的读者,专门沿真实源码回答字段由谁填写、何时改写、谁读取、失败时如何解释。本文不展开 allocator、完整对象 translator 和 AIDL 参数编码。
1. 三种形态
核心结构
源码文件:kernel/common/include/uapi/linux/android/binder.h
struct binder_transaction_data {
union {
__u32 handle;
binder_uintptr_t ptr;
} target;
binder_uintptr_t cookie;
__u32 code;
__u32 flags;
__kernel_pid_t sender_pid;
__kernel_uid32_t sender_euid;
binder_size_t data_size;
binder_size_t offsets_size;
union {
struct {
binder_uintptr_t buffer;
binder_uintptr_t offsets;
} ptr;
__u8 buf[8];
} data;
};扩展结构
源码文件:kernel/common/include/uapi/linux/android/binder.h
struct binder_transaction_data_secctx {
struct binder_transaction_data transaction_data;
binder_uintptr_t secctx;
};
struct binder_transaction_data_sg {
struct binder_transaction_data transaction_data;
binder_size_t buffers_size;
};secctx 只在 BR_TRANSACTION_SEC_CTX 形态附加;buffers_size 只在 SG 命令形态附加。不能把 扩展尾部当作普通结构总是存在。
字段阶段表
| 字段 | BC请求 | BR交付 | BC reply | BR reply |
|---|---|---|---|---|
| target | handle | ptr/cookie | handle=-1,仅作占位 | ptr/cookie 为 0 |
| code | 调用码 | 原调用码 | 0 | 0 |
| flags | 用户态事务标志 | 驱动保留/补充 | reply flags | status/oneway 等 |
| sender_pid/euid | 置零 | 驱动填写 | 置零 | 驱动生成,不作为业务调用方身份 |
| data.ptr | 发送 Parcel 地址 | 目标 buffer 地址 | reply Parcel 地址 | reply buffer 地址 |
| offsets | 发送方对象表 | 目标 buffer 对象表 | reply 对象表 | reply 对象表 |
2. 请求填充
请求字段
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:writeTransactionData()
status_t IPCThreadState::writeTransactionData(int32_t cmd,
uint32_t binderFlags, int32_t handle,
uint32_t code, const Parcel& data,
status_t* statusBuffer) {
binder_transaction_data tr;
tr.target.ptr = 0;
tr.target.handle = handle;
tr.code = code;
tr.flags = binderFlags;
tr.cookie = 0;
tr.sender_pid = 0;
tr.sender_euid = 0;
const status_t err = data.errorCheck();
if (err == NO_ERROR) {
tr.data_size = data.ipcDataSize();
tr.data.ptr.buffer = data.ipcData();
tr.offsets_size =
data.ipcObjectsCount()
* sizeof(binder_size_t);
tr.data.ptr.offsets = data.ipcObjects();
}
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}发送端只提供 handle、code、flags、data/offsets 用户地址和大小。sender 身份字段被清零,target ptr 也被置零;这两个零值都尚未成为接收方语义。
status分支
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
if (err != NO_ERROR && statusBuffer) {
tr.flags |= TF_STATUS_CODE;
*statusBuffer = err;
tr.data_size = sizeof(status_t);
tr.data.ptr.buffer =
reinterpret_cast<uintptr_t>(statusBuffer);
tr.offsets_size = 0;
tr.data.ptr.offsets = 0;
} else if (err != NO_ERROR) {
return mLastError = err;
}TF_STATUS_CODE 让 data 区从普通 Parcel 变成单个 status_t;接收方必须先检查 flags,再决定 是否建立普通 reply 视图。
3. 驱动填充
事务状态
源码文件:kernel/common/drivers/android/binder.c
t->from_pid = proc->pid;
t->from_tid = thread->pid;
t->sender_euid = task_euid(proc->tsk);
t->code = tr->code;
t->flags = tr->flags;
t->is_async = !reply &&
(tr->flags & TF_ONE_WAY);
t->is_reply = reply;内核事务保存发送进程、线程、code、flags 和同步/异步状态。用户写入的 sender_pid/euid 不参与 身份来源。
目标选择
源码文件:kernel/common/drivers/android/binder.c
if (reply) {
in_reply_to = thread->transaction_stack;
target_thread =
binder_get_txn_from_and_acq_inner(
in_reply_to);
} else {
ref = binder_get_ref_olocked(
proc, tr->target.handle, true);
target_node =
binder_get_node_refs_for_txn(
ref->node,
&target_proc,
&return_error);
}新请求从 target.handle 查 ref/node;reply 从当前线程 transaction_stack 找回原调用方。 reply 阶段不能再把 target.handle 当普通服务查找入口。
buffer与身份
源码文件:kernel/common/drivers/android/binder.c
t->buffer = binder_alloc_new_buf(
&target_proc->alloc,
tr->data_size,
tr->offsets_size,
extra_buffers_size,
!reply && (tr->flags & TF_ONE_WAY));
trd->sender_euid =
from_kuid(current_user_ns(),
t->sender_euid);
trd->sender_pid =
t_from ? task_tgid_nr_ns(
t_from->proc->tsk,
task_active_pid_ns(current))
: 0;驱动把 data/offsets 尺寸用于目标 buffer 分配,并在 BR_TRANSACTION 中填写接收方可见的 sender 身份。服务端读取的 sender_pid/euid 来自内核事务,不是发送方 Parcel 字段。
4. 服务接收
BR_TRANSACTION
源码文件:kernel/common/drivers/android/binder.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;服务端收到的 target 是 ptr/cookie,data.ptr.buffer 指向目标进程映射的 buffer;这些字段由驱动 生成。BR_REPLY 的目标由事务栈确定。
secctx与SG
若 node 设置 TXN_SECURITY_CTX,驱动返回 BR_TRANSACTION_SEC_CTX 并附加 secctx;SG 命令使用 binder_transaction_data_sg 并增加 buffers_size。读取命令时必须先知道命令形态,再决定结构大小。
5. 服务读取
Parcel视图
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:executeCommand()
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);服务端 Parcel 不复制 data,而是把 transaction_data 的 buffer/offsets 作为外部视图。freeBuffer 连接 Parcel 生命周期与内核 buffer 释放。
身份恢复
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
mCallingPid = tr.sender_pid;
mCallingUid = tr.sender_euid;
mLastTransactionBinderFlags = tr.flags;服务线程在进入 onTransact 前保存调用方 pid/uid 和 flags;嵌套调用结束后恢复旧身份。
6. Reply形态
BC_REPLY
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:sendReply()
status_t IPCThreadState::sendReply(
const Parcel& reply, uint32_t flags) {
status_t statusBuffer;
status_t err = writeTransactionData(
BC_REPLY, flags, -1, 0,
reply, &statusBuffer);
if (err < NO_ERROR)
return err;
return waitForResponse(
nullptr, nullptr);
}BC_REPLY 明确写入 handle=-1、code=0;它们不表示原业务目标。驱动通过服务线程 transaction_stack 找到等待客户端。
BR_REPLY
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
case BR_REPLY: {
binder_transaction_data tr;
err = mIn.read(&tr, sizeof(tr));
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;
}BR_REPLY 可能承载普通 Parcel,也可能承载 TF_STATUS_CODE 的错误值。先检查 flags,再决定解析 对象还是读取 status_t。
7. flags边界
| flags | 结构 | 生效者 | 影响 |
|---|---|---|---|
| TF_ONE_WAY | transaction_data | 驱动/服务线程 | 无业务 reply |
| TF_STATUS_CODE | transaction_data | IPCThreadState | data 解释为 status |
| TF_ACCEPT_FDS | transaction_data | 驱动 | fd 转换许可 |
| TF_CLEAR_BUF | transaction_data | 驱动 | 完成后清理 buffer |
| TF_UPDATE_TXN | transaction_data | 驱动 | 合并异步事务 |
| FLAT_BINDER_FLAG_* | flat object | node | 节点属性 |
8. 测试输入
驱动序列
源码文件:frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp
相关测试:Transaction()
测试构造 BC_TRANSACTION + PING_TRANSACTION,允许非阻塞第一次读取 EAGAIN,再检查 BR_NOOP、BR_TRANSACTION_COMPLETE、BR_REPLY 和 reply 字段。它证明结构阶段和命令顺序,不证明 业务 AIDL 返回值。
大小边界
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
相关测试:VectorSent()、GargantuanVectorSent()、LimitExceededVectorSent()
测试用小 vector 验证 round trip,用接近 buffer 上限的 vector 验证成功,再用更大的 vector 断言 FAILED_TRANSACTION。测试数字是输入,不是所有设备的固定上限。
身份字段
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
相关测试:GetCallingUidWithSetuid()、GotSid()、WorkSourcePropagatedForAllFollowingBinderCalls()
这些测试检查 calling uid、安全上下文和 work source 恢复/传播。它们证明字段由驱动和 IPCThreadState 上下文交付,不证明所有服务都开启 security context 或 work source 传播。
9. 失败边界
非法目标
无效 handle 或死亡 node 会在 target 解析处失败,服务端不会执行 onTransact。
数据复制
data/offsets 用户指针不可访问、尺寸溢出或对象 offset 非法,会产生 BR_FAILED_REPLY;失败事务 的 buffer 和已转换对象需要回滚。
身份误读
BC 请求中的 sender_pid/euid 为零不是匿名调用,而是等待内核覆盖的未生效字段;只有服务端收到 BR_TRANSACTION 后,它们才是可用于调用方判断的驱动身份。
10. 阅读边界
字段生命周期
同一个 binder_transaction_data 在发送、驱动内部和接收阶段字段含义不同。下面的时序突出“谁写入、谁消费、 何时失效”,正文各源码段可按 debug id 对照。
发送端的用户指针在驱动复制完成后不再是接收端数据地址;接收端 trd.data.ptr.buffer 指向目标映射。reply 和释放分别结束事务状态与 buffer 状态,不能用一个“完成”时间替代两者。
本文证明了 binder_transaction_data 的字段生命周期、target 双语义、身份来源、data/offsets 指针、secctx/SG 扩展、reply/status 分支和测试边界。没有展开完整对象 translator、allocator、 AIDL 序列化、死亡通知、线程池、RPC Binder 或 SELinux。
继续阅读 Binder数据转换链 复习 data/offsets owner,再读 Binder协议状态机 对照 BC/BR 命令状态。
