跳至主要內容

Binder Phases

Someone大约 6 分钟AndroidkernelAndroidBinder

本文主要讲述 Binder 流程中的各个阶段,起到一个 Overview 的目的。

Phases Overview

我们想对 binder 做性能优化(这块部分如果后续较为完善的话可以考虑独立出去),可以从几个 phase 进行分解:

  • 🐇🐇 phase 1: app to BpBinder: 从 app 到 Binder 客户端。
  • phase 2: BpBinder to driver
  • phase 3: driver send logic
  • 🐇🐇 phase 4: driver to BBinder
  • 🐇🐇 phase 5: BBinder to server
  • phase 6: server logic
  • phase 7: server back BBinder
  • phase 8: BBinder back driver
  • phase 9: driver reply logic
  • phase 10: driver back BpBinder
  • phase 11: BpBinder back app

为方便理解,我们引用下面的图来进行参考说明。

binder phases

Phase 1

App to BpBinder, 图中的 AMP clinet 到 BpBinder 的过程,这个过程主要发生了以下事情:

在 APP 侧,调用 startService(), 在调用该函数之间,会调用到其他的逻辑,我们分别给出一个 OHOS 的示例和 Android 的示例。

OHOS

列举了两种方式:期约和回调函数。

import rpc from "@ohos.rpc"
// 使用期约

let option = new rpc.MessageOption()
let data = rpc.MessageParcel.create()
let reply = rpc.MessageParcel.create()
// 往 data 里写入参数

proxy.sendRequest(1, data, reply, option)
    .then(function (result) {
        if (result.errCode != 0) {
            console.error("send request failed, errCode: " + result.errCode)
            return
        }
        // 从result.reply里读取结果
    })
    .catch(function (e) {
        console.error("send request got exception: " + e);
    })
    .finally(() => {
        data.reclaim()
        reply.reclaim()
    })

使用期约的情况下,客户端完成了几件事情。首先是往 Parcel 对象中写入参数,而后是调用 proxy 的 sendRequest 方法,得到服务端的回复后进行处理。

我们所说的 App to BpBinder 的过程就是:客户端数据封装 –> 调用 BpBinder(在 OHOS 中称作 Proxy) 提供的方法。

// 使用回调函数
function sendRequestCallback(result) {
            try {
                if (result.errCode != 0) {
                    console.error("send request failed, errCode: " + result.errCode)
                    return
                }
                // 从 result.reply 里读取结果
            } finally {
                result.data.reclaim()
                result.reply.reclaim()
            }
        }
let option = new rpc.MessageOption()
let data = rpc.MessageParcel.create()
let reply = rpc.MessageParcel.create()

// 往 data 里写入参数
proxy.sendRequest(1, data, reply, option, sendRequestCallback)

使用回调函数的过程较为简单,直接是封装数据 –> 发送请求(在我们 phase 1 的过程中)

Android

public ComponentName startService(IApplicationThread caller, 
                                  Intent service, String resolvedType, 
                                  String callingPackage, int userId) 
    throws RemoteException {
    // 获取或创建 Parcel 对象
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(IActivityManager.descriptor);
    data.writeStrongBinder(caller != null ? caller.asBinder() : null);
    service.writeToParcel(data, 0);
    // 写入 Parcel 数据
    data.writeString(resolvedType);
    data.writeString(callingPackage);
    data.writeInt(userId);

    // 通过 Binder 传递数据
    mRemote.transact(START_SERVICE_TRANSACTION, data, reply, 0);
    // 读取应答消息的异常情况
    reply.readException();
    // 根据 reply 数据来创建 ComponentName 对象
    ComponentName res = ComponentName.readFromParcel(reply);

    data.recycle();
    reply.recycle();
    return res;
}

和 JS 的过程基本上一致,挑选其中一些细节进行说明:

➡️➡️ Parcel.obtain() 完成了什么事情?从缓存池中获取一个 Parcel 对象。

public static Parcel obtain() {
    // sOwnedPool 为 Parcel 对象的缓存池
    final Parcel[] pool = sOwnedPool;
    synchronized (pool) {
        Parcel p;
        // POOL_SIZE = 6
        for (int i=0; i<POOL_SIZE; i++) {
            p = pool[i];
            if (p != null) {
                pool[i] = null;
                return p;
            }
        }
    }
    //当缓存池没有现成的 Parcel 对象,则直接创建
    return new Parcel(0);
}

关于 Parcel 对象的创建,我们留到 Pracel 章节进行详细说明。

Phase 1 Summary

🩸🩸 从上面上个场景的代码分析我们可以看出来,主要的操作耗时还是在 Pracel 过程中。在后续的研究中,我们可以看到 Pracel 对象的创建和 native world 也是息息相关的,所以这一个 phase 是存在一些优化的空间在的。

Phase 2

BpBinder to driver, 这个过程主要是从客户端 native 到 driver 的过程。

OHOS

从上文我们了解到,客户端会调用 proxy 的 sendRequest 方法,对应的实现如下:

Android

从上文我们了解到,客户端调用到 native 是通过 mRemote.transact 来实现的,该方法的实现如下:

final class BinderProxy implements IBinder {
    public boolean transact(int code, Parcel data, Parcel reply, int flags) 
        throws RemoteException {
        //用于检测 Parcel 大小是否大于 800k
        Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
        // 调用 native
        return transactNative(code, data, reply, flags);
    }
}

这个方法的功能非常简单,主要完成对 native 方法 transactNative 的调用。

transactNative 参数

mRemote.transact() 方法中的

  • code: START_SERVICE_TRANSACTION,
  • data 保存 descriptorcaller, intent, resolvedType, callingPackage, userId这 6 项信息。

transactNative 方法对应的 native 函数为 android_os_BinderProxy_transact.

android_util_Binder.cpp

static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
    jint code, jobject dataObj, jobject replyObj, jint flags)
{
    ...
    //将 java Parcel 转为 C++ Parcel
    Parcel* data = parcelForJavaObject(env, dataObj);
    Parcel* reply = parcelForJavaObject(env, replyObj);

    //gBinderProxyOffsets.mObject 中保存的是 new BpBinder(handle) 对象
    IBinder* target = (IBinder*) env->GetLongField(obj, gBinderProxyOffsets.mObject);
    ...

    //此处便是 BpBinder::transact()
    status_t err = target->transact(code, *data, reply, flags);
    ...

    //最后根据 transact 执行具体情况,抛出相应的 Exception
    signalExceptionForError(env, obj, err, true , data->dataSize());
    return JNI_FALSE;
}

抛开无关细节,主要是调用 target->transact 方法,target 是一个 BpBinder 对象:

BpBinder.cpp

status_t BpBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    if (mAlive) {
        status_t status = IPCThreadState::self()->transact(
            mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }
    return DEAD_OBJECT;
}

上述函数采取了单例模式,确保每个线程只有一个实例对象。主要还是调用到 IPCThreadState::transact 方法:

IPCThreadState.cpp

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    status_t err = data.errorCheck(); // 数据错误检查
    flags |= TF_ACCEPT_FDS;
    ....
    if (err == NO_ERROR) {
         // 传输数据
        err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    }

    if (err != NO_ERROR) {
        if (reply) reply->setError(err);
        return (mLastError = err);
    }

    // 默认情况下,都是采用非 oneway 的方式, 也就是需要等待服务端的返回结果、同步
    if ((flags & TF_ONE_WAY) == 0) {
        if (reply) {
            // reply 对象不为空
            err = waitForResponse(reply);
        }else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
    } else {
        err = waitForResponse(NULL, NULL);
    }
    return err;
}

通常情况下会走同步模式,也就是 oneway。两个模式都会调用到 waitForResponse 函数。

在看 waitForResponse 函数之间,我们先简单说明一下 transact 的主要过程:

首先、执行 writeTransactionData(第 10 行)向 mOut 写入数据,此时 mIn 还没有数据;

然后执行 waitForResponse 方法,该方法循环执行,直到收到应答消息,此间调用 talkWithDriver 和驱动交互,收到应答消息后写入 mIn, 根据响应码分类操作。

如果我们不再往下研究的话,此时客户端视角下的通信已经完成了,而我们的 phase 2 就是到此为止。

writeTransactionData 的实现大致如下:

IPCThreadState.cpp

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; // handle 指向 AMS
    tr.code = code;            // START_SERVICE_TRANSACTION
    tr.flags = binderFlags;    // 0
    tr.cookie = 0;
    tr.sender_pid = 0;
    tr.sender_euid = 0;

    const status_t err = data.errorCheck();
    if (err == NO_ERROR) {
        // data 为 startService 相关信息
        tr.data_size = data.ipcDataSize();   // mDataSize
        tr.data.ptr.buffer = data.ipcData(); // mData 指针
        tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t); // mObjectsSize
        tr.data.ptr.offsets = data.ipcObjects(); // mObjects 指针
    }
    ...
    mOut.writeInt32(cmd);         // cmd = BC_TRANSACTION
    mOut.write(&tr, sizeof(tr));  // 写入 binder_transaction_data数据
    return NO_ERROR;
}

23-24 行将数据写入了 mOut, 写入的类型是 binder_transaction_data 类型。

waitForResponse 的实现大概如下:

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    int32_t cmd;
    int32_t err;

    while (1) {
        if ((err=talkWithDriver()) < NO_ERROR) break;
        err = mIn.errorCheck();
        if (err < NO_ERROR) break; //当存在 error 则退出循环

         //每当跟 Driver 交互一次,若 mIn 收到数据则往下执行一次 BR 命令
        if (mIn.dataAvail() == 0) continue;

        cmd = mIn.readInt32();

        switch (cmd) {
        case BR_TRANSACTION_COMPLETE:
            //只有当不需要 reply, 也就是 oneway 时才会跳出循环,否则还需要等待.
            if (!reply && !acquireResult) goto finish; break;

        case BR_DEAD_REPLY:
            err = DEAD_OBJECT;         goto finish;
        case BR_FAILED_REPLY:
            err = FAILED_TRANSACTION;  goto finish;
        case BR_REPLY: ...             goto finish;

        default:
            err = executeCommand(cmd);
            if (err != NO_ERROR) goto finish;
            break;
        }
    }

finish:
    if (err != NO_ERROR) {
        if (reply) reply->setError(err); // 将发送的错误代码返回给最初的调用者
    }
    return err;
}

这里面又引入了关键的操作:talkWithDriver 和 executeCommand;

这两者的功能是:执行 binder 读写操作,也就是 BINDER_WRITE_READ, 会经过 syscall, 进入 Binder 驱动,调用驱动中的 binder_ioctl 函数。在 default 的情况下,会调用后者;其他情况下是在 talkWithDriver 对 executeCommand 进行调用。

OK,Phase 2 到此为止。

Phase 2 Summary

总结一下,BpBinder 到 Binder Driver 做了什么事情?

首先,我们找到 native 层的方法,