Skip to content

transact发送

追踪 IPCThreadState::transact 从 Parcel 和 flags 到 BC_TRANSACTION、驱动交付、同步回复和错误收束。

基于android-17.0.0_r1
AndroidBindertransactNative框架源码阅读

transact发送 ​

Native Binder 的 transact() 不是一次函数调用,而是把目标 handle、事务 code、Parcel 数据和 flags 编成 binder_transaction_data,写入线程的输出 Parcel,再通过 BINDER_WRITE_READ 交给驱动。同步调用还要继续消费 BR_REPLY;oneway 调用只提交事务,不等待 reply。发送成功只代表驱动接受了事务,不代表服务端业务已经完成。

本文面向已经读过 IPCThreadState线程绑定、Binder事务发送 和 BINDER_WRITE_READ命令 的读者。本文聚焦 native IPCThreadState::transact() 的用户态主线、flags、同步/oneway 分叉和错误返回;驱动目标解析、buffer 分配与对象翻译沿用驱动专题的边界。

1. 入口参数 ​

1.1 transact ​

源码文件: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) {
    LOG_ALWAYS_FATAL_IF(data.isForRpc(),
                        "Parcel constructed for RPC, "
                        "but being used with binder.");

    flags |= TF_ACCEPT_FDS;
    status_t err = writeTransactionData(
            BC_TRANSACTION, flags, handle, code,
            data, nullptr);
    if (err != NO_ERROR) {
        if (reply) reply->setError(err);
        return (mLastError = err);
    }
    ...
}

Binder transaction 强制加上 TF_ACCEPT_FDS,表示目标端可以接受文件描述符。RPC Parcel 不能混入 kernel Binder;这是对象传输协议边界,不是业务 code 校验。

1.2 flags分叉 ​

cpp
if ((flags & TF_ONE_WAY) == 0) {
    if (reply) {
        err = waitForResponse(reply);
    } else {
        Parcel fakeReply;
        err = waitForResponse(&fakeReply);
    }
} else {
    err = waitForResponse(nullptr);
}

同步调用需要等待 reply;即使调用者没有提供 reply 指针,libbinder 仍可能使用临时 Parcel 消费驱动返回。oneway 不要求业务 reply,但仍要处理驱动层返回的失败状态。

2. 写入事务 ​

2.1 结构填充 ​

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

相关函数:writeTransactionData()

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

发送方只填充目标 handle、code、flags 和 Parcel 的数据/offsets 指针;sender pid/euid 由驱动和协议处理,不能从这里推导调用者身份。data.errorCheck() 失败时,事务不会进入输出队列。

2.2 输出队列 ​

writeTransactionData() 把命令和 binder_transaction_data 写入 mOut。此时数据仍在用户态 IPCThreadState 输出 Parcel 中,目标进程还没有收到事务;真正提交发生在后续 talkWithDriver()。

3. 驱动提交 ​

3.1 talkWithDriver ​

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

相关函数: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 = (uintptr_t)mOut.data();
    bwr.read_size = doReceive ? mIn.dataCapacity() : 0;
    bwr.read_buffer = doReceive
            ? (uintptr_t)mIn.data() : 0;

    if (ioctl(mProcess->mDriverFD,
              BINDER_WRITE_READ, &bwr) >= 0) {
        ...
        return NO_ERROR;
    }
    return -errno;
}

BINDER_WRITE_READ 可以同时写出 mOut 和读入 mIn。doReceive=false 时只提交输出;同步等待通常允许读回命令。若 mIn 仍有未消费数据,函数会避免覆盖输入缓冲。

3.2 输出消费 ​

成功 ioctl 后,mOut 根据 write_consumed 前移;如果全部消费,输出 Parcel 可继续承载下一条命令。驱动返回的 BR_* 命令进入 mIn,由 waitForResponse() 或线程循环消费,不是 transact() 直接调用服务对象。

4. 同步回复 ​

4.1 等待循环 ​

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

相关函数:waitForResponse()

cpp
while (true) {
    status_t err = talkWithDriver();
    if (err < NO_ERROR) break;
    err = mIn.errorCheck();
    if (err < NO_ERROR) break;
    if (mIn.dataAvail() == 0) continue;

    uint32_t cmd = mIn.readInt32();
    switch (cmd) {
    case BR_REPLY:
        ...
        return NO_ERROR;
    case BR_FAILED_REPLY:
        err = FAILED_TRANSACTION;
        goto finish;
    default:
        err = executeCommand(cmd);
        if (err != NO_ERROR) goto finish;
    }
}

等待期间可能先收到死亡通知、释放引用或其他 BR_* 命令;executeCommand() 负责消费这些命令,只有 BR_REPLY 才结束当前同步调用。服务端返回的 Binder status 和驱动传输错误是两个层级。

4.2 错误回写 ​

cpp
finish:
if (err != NO_ERROR) {
    if (acquireResult) *acquireResult = err;
    if (reply) reply->setError(err);
    mLastError = err;
    logExtendedError();
}
return err;

传输失败会写入 reply error、更新线程最近错误并记录扩展错误。调用者看到的 status_t 是本地 transport 结果,不应直接当作远端业务异常类型。

5. Oneway边界 ​

5.1 不等业务回复 ​

TF_ONE_WAY 事务提交后,发送线程不等待目标服务执行完成。驱动仍可能返回 buffer、冻结、oneway spam 等传输反馈;“oneway 返回 OK”只说明提交阶段没有立刻失败。

5.2 调用限制 ​

cpp
if ((flags & TF_ONE_WAY) == 0 &&
        mCallRestriction != ProcessState::CallRestriction::NONE) {
    if (mCallRestriction ==
            ProcessState::CallRestriction::ERROR_IF_NOT_ONEWAY) {
        ALOGE("Process making non-oneway call ...");
    } else {
        LOG_ALWAYS_FATAL(
                "Process may not make non-oneway calls");
    }
}

限制只针对本进程发出的同步调用;它不改变对端接口是否声明 oneway,也不阻止其他进程调用本进程。

6. 测试边界 ​

6.1 同步与oneway ​

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

NopTransaction 发送同步请求并断言 NO_ERROR;NopTransactionOneway 传 TF_ONE_WAY 并断言提交成功;两者覆盖 flags 分叉,不证明服务端业务完成时刻。

6.2 冻结错误 ​

FreezeTxn 在冻结目标进程后发送同步事务,断言返回 FROZEN_OBJECT 或 FAILED_TRANSACTION,再解冻并断言调用恢复成功。这个测试证明驱动错误能沿 transact() 返回,不证明所有失败都来自服务端。

6.3 可执行阅读 ​

bash
rg -n "IPCThreadState::transact|writeTransactionData|talkWithDriver|waitForResponse" \
  frameworks/native/libs/binder/IPCThreadState.cpp

rg -n "NopTransaction|NopTransactionOneway|FreezeTxn|TF_ONE_WAY" \
  frameworks/native/libs/binder/tests/binderLibTest.cpp

排查一次 native Binder 调用失败时,按 Parcel 校验、mOut 写入、BINDER_WRITE_READ、BR_* 返回和 reply error 五层定位;若是 oneway,则不要等待“服务执行完成”作为成功条件。