深入Android系统(三)Binder-3-原理

·  阅读 1411

Binder的实现原理

涉及到原理源码肯定是少不了的,9.0 binder 相关的源码分为三部分:

  • Java:frameworks/base/core/java/android/os/Binder.java
  • native:frameworks/native/libs/binder/
  • driver:common/drivers/android/binder.c

还有一点需要明确的是:

  • 用户进程:针对内核空间或者binder驱动来说的,这里指的是向binder驱动发送消息的进程
  • 客户进程:针对binder通信服务进程来说的,这里指的是发起binder调用的进程

书中这两个名词没有做详细说明,一开始看的有点晕

源码在手,干啥都有

Binder设计相关的问题

Binder实现的远程调用是一种面向对象的远程调用,那么它和面向过程的远程调用区别在什么地方呢?

  • 面向过程的远程调用实现起来比较容易:
    • 只需要通过某种方式把需要执行的函数号参数传递到服务进程
    • 然后服务进程根据函数号参数执行对应的函数就完成了
  • 面向对象的调用则比较复杂:
    • 同一个服务类可以创建对个对象
      • 调用时不但要通过函数号参数来识别要执行函数
      • 同时还要指定具体的对象
    • 对象是有生命周期的,需要监听管理
      • 服务中的实体对象死亡后,客户进程的引用对象也需要删除
      • 这个过程需要自动完成而不能由上层的客户程序协助完成
      • 因此为了管理方便,客户进程中需要(ProcessState类的作用):
        • 集中管理本进程中所有的Binder引用对象
        • 并负责它们的创建和释放

设计复杂也带来了功能的强大,正因为Binder面向对象的,我们可以创建多个Binder实体对象来服务不同的客户,每个对象有自己的数据。相互之间不会干扰。

为了系统中所有引用对象实体对象能相互关联:

  • Binder在驱动中建立了一张所有进程的引用对象实体对象关联表
  • 有了关联,Binder又必须保证用户进程中实体对象引用对象跟驱动中的数据一致
    • 为了达到这个目标,Binder定义了自己的引用计数规则,而且这种规则是跨进程的。

参数的传递问题:

  • 一般的对象作为参数传递没有太大问题,只需要序列化反序列化就能实现。
  • 但是Binder对象作为参数传递的时候,就会面临实体对象引用对象相互转换的问题。
    • 为了让上层应用使用方便,这种转换在驱动中自动完成
  • 普通的IPC传递参数数据时,要经历两次数据复制的过程:
    • 一次是从调用者的数据缓冲区复制到内核的缓冲区
    • 一次是从内核的缓冲区复制到接受进程的读缓冲区
  • Binder为了提高效率:
    • 为每一个进程创建了一块缓存区,这块缓存区在内核和用户进程间共享
    • 传输数据到驱动,需要:
      • 从发送进程的用户空间缓存区复制到目标进程在驱动的缓存区
      • 目标进程从驱动中读取数据就不需要从内核空间复制到用户空间了,而是直接从内核共享的缓存区中读取
    • 这样减少了一次数据复制的过程

对于服务进程中Binder调用的执行:

  • 每次执行必须在一个线程中完成
  • 如果线程不停地创建和释放,会带来很大的系统开销。
  • 使用线程池来管理Binder调用的执行
    • Binder的设计中,除了第一个线程是应用层主动创建的
    • 线程池中的其他线程都是在驱动的请求下才创建的
    • 这样将线程数量降到最低,并保证从驱动到来的Binder调用有线程可以使用。

Binder的线程模型

Binder 的线程池

Zygote进程启动时,会调用AppRuntimeonZygoteInit函数(书中第8章,还没看到),代码如下:

    virtual void onZygoteInit()
    {
        sp<ProcessState> proc = ProcessState::self();
        ALOGV("App process: starting thread pool.\n");
        proc->startThreadPool();
    }
复制代码

所有Android应用都是从Zygote进程fork出来的。因此,这段代码对所有应用进程都有效。

  • onZygoteInit函数首先调用self函数来得到ProcessState类的实例,每个进程都只会有一个实例。ProcessState构造函数如下:
    ProcessState::ProcessState(const char *driver)
    : mDriverName(String8(driver))
    , mDriverFD(open_driver(driver))
    //......
    {
      if (mDriverFD >= 0) {
         // mmap the binder, providing a chunk of virtual >   address space to receive transactions.
        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
        if (mVMStart == MAP_FAILED) {
            // *sigh*
            ALOGE("Using %s failed: unable to mmap transaction memory.\n", mDriverName.c_str());
            close(mDriverFD);
            mDriverFD = -1;
            mDriverName.clear();
        }
      }
    }
    复制代码
  • ProcessState类做了两件事:
    • 调用open_driver()函数打开Binder设备
    • 调用mmap()函数在驱动中分配了一块内存空间
      • 这块内存空间大小略小于1MB,定义如下:
      #define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
      复制代码
      • 这块内存并不是给上层应用使用
      • 这块内存用于Binder驱动中接受传递给本进程的Binder数据
      • 这块内存在内核和本应用中共享
  • 得到ProcessState类的实例后,调用它的startThreadPool()函数启动线程池,看下代码:
    void ProcessState::startThreadPool(){
       AutoMutex _l(mLock);
       if (!mThreadPoolStarted) {
           mThreadPoolStarted = true;
            spawnPooledThread(true);
        }
    }
    复制代码
    • 先判断mThreadPoolStarted是否为true,不为true才继续执行
    • 然后把mThreadPoolStarted设置为true,这说明startThreadPool()在进程中只会运行一次
    • 接着调用spawnPooledThread函数,参数为true
      void ProcessState::spawnPooledThread(bool isMain)
      {
        if (mThreadPoolStarted) {
            String8 name = makeBinderThreadName();
            ALOGV("Spawning new pooled thread, name=%s\n", name.string());
            sp<Thread> t = new PoolThread(isMain);
            t->run(name.string());
        }
      }
      复制代码
    • spawnPooledThread函数的作用是创建加入线程池的线程
    • 函数中创建了一个PoolThread类,类的run函数会创建线程
    • 传入的参数为true,说明这个线程是线程池的第一个线程
    • 以后再创建的线程都是接到驱动通知后创建的,传入的参数为false,像这样
      status_t IPCThreadState::executeCommand(int32_t cmd){
        //......
         case BR_SPAWN_LOOPER:
            mProcess->spawnPooledThread(false);
            break;
        //......
      }
      复制代码
    • 我们再看下PoolThread类的业务实现threadLoop函数
      protected:
      virtual bool threadLoop()
      {
        IPCThreadState::self()->joinThreadPool(mIsMain);
        return false;
      }
      复制代码
      • 返回false代表执行一次,为什么是在threadLoop()中执行业务逻辑,可以看下Android 中的threadLoop
      • 具体调用细节大家阅读frameworks源码吧,路径应该是在:frameworks/av/services/audioflinger/Threads.h
      • threadLoop函数只是调用了IPCThreadStatejoinThreadPool函数,这个函数后面单练它

好的,我们先来梳理下线程池这部分内容:

  • 首先,应用启动时会执行onZygoteInit函数,这部分会打开Binder设备申请共享内存空间
  • 然后,执行ProcessStatestartThreadPool创建线程池
  • 然后,通过创建PoolThread的实例,创建线程池中的第一个线程
  • 最后,PoolThread只是简单调用了IPCThreadStatejoinThreadPool函数

关于IPCThreadState,稍后详谈

调用Binder服务的线程

客户端Binder服务的调用是通过IBinder的transact函数完成的。这里的IBInder实际上是BpBinder对象,代码如下:

status_t BpBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    // Once a binder has died, it will never come back to life.
    if (mAlive) {
        status_t status = IPCThreadState::self()->transact(
            mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }
    return DEAD_OBJECT;
}
复制代码

这部分代码做了:

  • 通过IPCThreadStateself静态函数获取IPCThreadState的指针
    • IPCThreadState对象会和每个线程关联
    • self函数会判断本线程是否有关联的IPCThreadState对象
    • 没有关联的对象则新创建一个IPCThreadState对象,并保存到当前线程中
  • 得到IPCThreadState对象后,接着调用了IPCThreadStatetransact,我们看下代码:
status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    status_t err;
    flags |= TF_ACCEPT_FDS;
    //......
    //把要发送的数据放到类的成员变量mOut中
    err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    if (err != NO_ERROR) {
        if (reply) reply->setError(err);
        return (mLastError = err);
    }
    if ((flags & TF_ONE_WAY) == 0) {// 同步调用方式
        //......
        if (reply) {
            // 调用者要求返回结果,此时向底层发送数据并等待返回值
            err = waitForResponse(reply);
        } else {
            // 调用者不需要返回值,但是还要等待远程执行完毕
            // 这里用fakeRely来接收返回的Parcel对象
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
       //......
    } else {// 异步调用方式,函数会立即返回
        err = waitForResponse(NULL, NULL);
    }
    return err;
}
复制代码
  • IPCThreadStatetransact首先执行writeTransactionData包要传输的数据保存到mOut变量中
  • 然后通过waitForResponsemOut中的数据通过ioctl发送给底层驱动

对于一个进程而言,只有一个Binder驱动文件描述符,所有ioctl调用都是使用这个描述符。如果客户端有好几个线程同时执行远程调用,它们都将在同一个描述符的ioctl函数上等待。那么当数据到来时,哪个线程会受到回复呢?

  • 通常的IO模型,在同一个描述符上等待的多个线程会被随机唤醒一个
  • 但是Android在Binder驱动中记录了每次Binder调用的信息,其中就包括线程ID,因此Binder驱动知道返回值应该交给哪个线程
    • 由驱动来处理线程的唤醒比在应用层做同样的事情要更简单,效率也更高
    • 但是,从架构的角度来看,这种设计比较糟糕,应用层和驱动产生了耦合。

好的,我们在对这部分做个小结:

  • 客户端从某个线程中发起调用,将参数打包后,通过ioctl函数传递给驱动
  • 客户端挂起并等待ioctl返回函数的结果
  • binder驱动记录下调用线程的信息,然后根据调用的binder对象寻找Binder服务所在的进程,也就是服务端
  • binder驱动找到服务端后先查看是否有空闲线程,没有则通知服务端创建
  • 服务端得到空闲线程后,根据binder驱动中保存的BBinder对象的指针调用相应的函数
  • 服务端在函数返回后在通过ioctl把结果打包传递给binder驱动
  • binder驱动根据返回信息查找调用者线程
  • binder驱动找到调用的线程后并唤醒它,并通过ioctl函数把结果传递回去
  • 客户端的线程得到返回结果后继续运行

Binder对象的传递

Binder对象作为参数传递是,会有两种情形:

  • Binder实体对象作为参数传递:非Binder对象的传递是通过在接收端复制对象完成的,但是Binder实体对象是无法复制的,因此需要在客户进程中创建一个Binder引用对象来代替实体对象。
  • Binder引用对象作为参数传递:
    • 如果传递的目的地是该引用对象对应实体对象所在的进程,那么:
      • Binder框架必须把这个引用对象转换成Binder实体对象
      • 不能再创建一个新的实体对象
      • 必须找到并使用原来的实体对象
    • 如果传递的目的地是另一个客户端进程,那么:
      • 不能简单的复制引用对象
      • 需要建立目的进程中的引用对象实体对象的关系
      • 这个关系的建立是在Binder驱动中完成的

Binder对象传递流程简介

Binder调用的参数传递是通过Parcel类来完成的。先来简单看下Binder实体对象转换成Binder引用对象的过程:

  • 服务进程中将IBinder(BBinder)对象加入到Parcel对象后,Parcel对象会:
    • 打包数据,并把数据类型标记为BINDER_TYPE_BINDER
    • BpBinder的指针放进cookie字段
    • 通过ioctlParcel对象的数据传递到Binder驱动
  • Binder驱动会检查传递进来的数据,如果发现了标记为BINDER_TYPE_BINDER的数据:
    • 会先查找和服务进程相关的Binder实体对象表
      • 如果表中还没有这个实体对象的记录,则创建新的节点,并保存信息。
    • 然后驱动会查看客户进程的Binder对象引用表
      • 如果没有引用对象的记录,同样会创建新的节点
      • 并让这个节点中某个字段指向服务进程的Binder实体对象表中的节点
    • 接下来驱动对Parcel对象中的数据进行改动:
      • 把数据从BINDER_TYPE_BINDER改为BINDER_TYPE_HANEL
      • 同时把handle的值设为Binder对象引用表中的节点
    • 最后,把改动的数据传到客户进程
  • 客户端接收到数据,发现数据中的Binder类型BINDER_TYPE_HANEL
    • 使用handle值作为参数,调用ProcessState类中的函数getStrongProxyFoHandle来得到BpBinder对象
      • 如果对象不存在则创建一个新的对象
      • 这个BpBinder对象会一直保存在ProcessStatemHandleToObject表中
    • 这样,客户端就得到了Binder引用对象

写入Binder对象的过程

有了上面的整体流程,我们来看下Binder对象的写入细节:

  • Parcel类中:
    • 写入Binder对象的函数是:
      • writeStrongBinder:写入强引用Binder对象
      • writeWeakBinder:写入弱引用Binder对象
    • 读取Binder对象的函数是:
      • readStrongBinder:获取强引用Binder对象
        • 强引用的Binder对象可以分为实体对象引用对象
      • readWeakBinder:获取弱引用Binder对象
        • 弱引用则没有区分实体对象引用对象

看下writeStrongBinder的代码:

status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
    return flatten_binder(ProcessState::self(), val, this);
}
status_t flatten_binder(const sp<ProcessState>& /*proc*/,
    const sp<IBinder>& binder, Parcel* out)
{
    // flatten_binder 整个方法其实是在向obj这个结构体存放数据
    flat_binder_object obj;

    if (IPCThreadState::self()->backgroundSchedulingDisabled()) {
        /* minimum priority for all nodes is nice 0 */
        obj.flags = FLAT_BINDER_FLAG_ACCEPTS_FDS;
    } else {
        /* minimum priority for all nodes is MAX_NICE(19) */
        obj.flags = 0x13 | FLAT_BINDER_FLAG_ACCEPTS_FDS;
    }
    
    if (binder != NULL) {
        // 调用localBinder函数开区分是实体对象还是引用对象
        IBinder *local = binder->localBinder();
        if (!local) { //binder引用对象
            BpBinder *proxy = binder->remoteBinder();
            if (proxy == NULL) {
                ALOGE("null proxy");
            }
            const int32_t handle = proxy ? proxy->handle() : 0;
            obj.hdr.type = BINDER_TYPE_HANDLE;
            obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
            obj.handle = handle;
            obj.cookie = 0;
        } else { // binder实体对象
            obj.hdr.type = BINDER_TYPE_BINDER;
            obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
            obj.cookie = reinterpret_cast<uintptr_t>(local);
        }
    } else {
        obj.hdr.type = BINDER_TYPE_BINDER;
        obj.binder = 0;
        obj.cookie = 0;
    }

    return finish_flatten_binder(binder, obj, out);
}
复制代码

flatten_binder整个方法其实是在向flat_binder_object这个结构体存放数据。我们看下flat_binder_object的结构:

struct flat_binder_object {
	/* 8 bytes for large_flat_header. */
	__u32		type;
	__u32		flags;
	/* 8 bytes of data. */
	union {
		binder_uintptr_t	binder;	/* local object */
		__u32			handle;	/* remote object */
	};
	/* extra data associated with local object */
	binder_uintptr_t	cookie;
};
复制代码

我们看下flat_binder_object中的属性:

  • type的类型:
    • BINDER_TYPE_BINDER:用来表示Binder实体对象
    • BINDER_TYPE_WEAK_BINDER:用来表示Bindr实体对象的弱引用
    • BINDER_TYPE_HANDLE:用来表示Binder引用对象
    • BINDER_TYPE_WEAK_HANDLE:用来表示Binder引用对象的弱引用
    • BINDER_TYPE_FD:用来表示一个文件描述符
  • flag字段用来保存向驱动传递的标志
  • union.binder在打包实体对象时存放的是对象的弱引用指针
  • union.handle在打包引用对象时存放的是对象中的handle值
  • cookie字段只用在打包实体对象时,存放的是BBinder指针

解析强引用Binder对象数据的过程

Parcel 类中解析数据的函数是unflatten_binder,代码如下:

status_t unflatten_binder(const sp<ProcessState>& proc,
    const Parcel& in, sp<IBinder>* out)
{
    const flat_binder_object* flat = in.readObject(false);

    if (flat) {
        switch (flat->hdr.type) {
            case BINDER_TYPE_BINDER:
                *out = reinterpret_cast<IBinder*>(flat->cookie);
                return finish_unflatten_binder(NULL, *flat, in);
            case BINDER_TYPE_HANDLE:
                *out = proc->getStrongProxyForHandle(flat->handle);
                return finish_unflatten_binder(
                    static_cast<BpBinder*>(out->get()), *flat, in);
        }
    }
    return BAD_TYPE;
}
复制代码

unflatten_binder的逻辑是:

  • 如果是BINDER_TYPE_BINDER类型的数据,说明接收到的数据类型是Binder实体对象,此时cookie字段存放的是本进程的Binder实体对象的指针,可直接转化成IBinder的指针
  • 如果是BINDER_TYPE_HANDLE类型的数据,则调用ProcessState类getStrongProxyForHandle函数来得到BpBinder对象,函数代码如下:
    sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
    {
       sp<IBinder> result;
       AutoMutex _l(mLock);
       // 根据handle查看进程中是否已经创建了引用对象
       // 如果进程中不存在handle对应的引用对象,在表中插入新的元素并返回
       handle_entry* e = lookupHandleLocked(handle);
       if (e != NULL) {
           IBinder* b = e->binder; 
           //根据返回元素中的binder值来判断是否有引用对象
           if (b == NULL || !e->refs->attemptIncWeak(this)) {
               if (handle == 0) { // handle为0 表示是ServiceManager的引用对象
                   Parcel data;
                   //发送PING_TRANSACTION检查ServiceManager是否存在
                   status_t status = IPCThreadState::self()->transact(
                           0, IBinder::PING_TRANSACTION, data, NULL, 0);
                   if (status == DEAD_OBJECT)
                      return NULL;
               }
               b = BpBinder::create(handle); // 创建一个新的引用对象
               e->binder = b;// 放入元素中的binder属性
               if (b) e->refs = b->getWeakRefs();
               result = b;
           } else {
               result.force_set(b);// 如果引用对象已经存在,放入到返回的对象result中
               e->refs->decWeak(this);
           }
       }
       return result;
    }
    复制代码

getStrongProxyForHandle()函数会调用lookupHandleLocked()来查找handle在进程中对应的引用对象。所有进程的引用对象都保存在ProcessStatemHandleToObject变量中。mHandleToObject变量定义如下:

Vector<handle_entry> mHandleToObject;
复制代码
  • mHandleToObject是一个Vector集合类,元素类型handle_entry
    • handle_entry结构很简单:
    struct handle_entry {
      IBinder* binder;
      RefBase::weakref_type* refs;
    };
    复制代码
  • lookupHandleLocked()函数就是使用handle作为关键项来查找对应的handle_entry,没有则创建新的handle_entry,并添加到集合中
  • 当获得handle_entry后,如果handle值为0,表明要创建的是ServiceManager引用对象
    • 并发送PING_TRANSACTION消息来检查ServiceManager是否已经创建

IPCThreadState类

每个Binder线程都会有一个关联的IPCThreadState类的对象。IPCThreadState类主要的作用是和Binder驱动交互,发送接收Binder数据,处理和Binder驱动之间来往的消息。

我们在Binder线程模型中已经知道:

  • Binder服务启动时,服务线程调用了joinThreadPool()函数
  • 远程调用Binder服务时,客户线程调用了waitForResponse()函数

这两个函数都是定义在IPCThreadState类中,我们分别来看下这两个函数。

waitForResponse()函数

函数定义如下:

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

    while (1) {
        if ((err=talkWithDriver()) < NO_ERROR) break;//和驱动通信
        err = mIn.errorCheck();
        if (err < NO_ERROR) break;
        if (mIn.dataAvail() == 0) continue;//没有数据,重新开始循环

        cmd = (uint32_t)mIn.readInt32();//读取数据
        //......
        switch (cmd) {
        case BR_TRANSACTION_COMPLETE:
            if (!reply && !acquireResult) goto finish;
            break;
        //......省略部分case语句
        case BR_REPLY:
        // Binder调用返回的消息
        default:
            err = executeCommand(cmd);
            if (err != NO_ERROR) goto finish;
            break;
        }
    }

finish:
    //...... 错误处理
    return err;
}
复制代码

waitForResponse()函数中是一个无限while循环,在循环中,重复下面的工作:

  • 调用talkWithDriver函数发送/接收数据
  • 如果有消息从驱动返回,会通过switch语句处理消息。
    • 如果收到错误消息或者调用返回的消息,将通过goto语句跳出while循环
    • 如果还有未处理的消息,则交给executeCommand函数处理

我们再仔细看下Binder调用收到的返回类型为BR_REPLY的代码:

        case BR_REPLY:
            {
                binder_transaction_data tr;
                //按照 binder_transaction_data 结构大小读取数据
                err = mIn.read(&tr, sizeof(tr));
                ALOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY");
                if (err != NO_ERROR) goto finish;

                if (reply) {
                    //reply 不为null,表示调用者需要返回结果
                    if ((tr.flags & TF_STATUS_CODE) == 0) {
                        //binder 调用成功,把从驱动来的数据设置到reply对象中
                        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, this);
                    } else {
                        //binder调用失败,使用freeBuffer函数释放驱动中分配的缓冲区
                        err = *reinterpret_cast<const status_t*>(tr.data.ptr.buffer);
                        freeBuffer(NULL,
                            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), this);
                    }
                } else {
                    //调用者不需要返回结果,使用freeBuffer函数释放驱动中分配的缓冲区
                    freeBuffer(NULL,
                        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), this);
                    continue;
                }
            }
复制代码

针对BR_REPLY类型的处理流程是:

  • 如果Binder调用成功返回,并且调用者也需要返回值
    • 把接收到的数据放在Parcel对象reply中返回
  • 如果Binder调用不成功,或者调用者不需要返回数据
    • 通过freeBuffer释放驱动中分配的缓冲区

因为Binder提供了一块驱动和应用层共享的内存空间,所以在接收Binder数据不需要额外创建缓冲区再进行一次拷贝了,但是如果不及时通知驱动释放缓冲区中占用的无用内存,会很快会耗光这部分共享空间。

上面代码中的reply->ipcSetDataReference方法,在设置Parcel对象的同时,同样也把freeBuffer的指针作为参数传入到对象中,这样reply对象删除时,也会调用freeBuffer函数来释放驱动中的缓冲区。

waitForResponse()函数的作用是发送Binder调用的数据并等待返回值。为什么还需要使用循环的方式反复和驱动交互?原因有两点:

  • 一是消息协议中要求应用层通过BC_TRANSACTION发送Binder调用数据后:
    • 驱动要先给应用层回复BC_TRANSACTION_COMPLETE消息,表示已经说到并且认可本次Binder调用数据
    • 然后上层应用再次调用talkWithDriver来等待驱动返回调用结果
    • 如果调用结果返回了,会收到BR_REPLY消息
  • 二是等待调用返回期间,驱动可能会给线程发送消息,利用这个线程帮忙干点活。。。。

joinThreadPool函数

Binder线程池部分已经知道:应用启动时会伴随着启动Binder服务,而最后执行到的方法就是joinThreadPool函数。

我们看下函数定义:

void IPCThreadState::joinThreadPool(bool isMain)
{
    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);

    status_t result;
    do {
        processPendingDerefs();
        //now get the next command to be processed, waiting if necessary
        result = getAndExecuteCommand();//读取并处理驱动发送的消息
        if (result < NO_ERROR && result != TIMED_OUT && result != -ECONNREFUSED && result != -EBADF) {
            abort();
        }
        // Let this thread exit the thread pool if it is no longer
        // needed and it is not the main process thread.
        if(result == TIMED_OUT && !isMain) {
            break;
        }
    } while (result != -ECONNREFUSED && result != -EBADF);

    mOut.writeInt32(BC_EXIT_LOOPER); //退出前向驱动发送线程退出消息
    talkWithDriver(false);
}
复制代码

joinThreadPool函数的结构是一个while循环。

  • 传入的参数isMain
    • 如果为true(通常是线程池第一个线程发起的调用),则向驱动发送BC_ENTER_LOOPER消息
      • 发送BC_ENTER_LOOPER的线程会被驱动标记为“主”线程
      • 不会在空闲时间被驱动要求退出
    • 否则,发送BC_REGISTER_LOOPER
    • 这两条消息都是告诉驱动:本线程已经做好准备接收驱动来的Binder调用了
  • 进入循环,调用了processPendingDerefs()函数
    • 用来处理IPCThreadState对象中mPendingWeakDerefsmPendingStrongDerefsBinder对象的引用计数
      • mPendingWeakDerefsmPendingStrongDerefs都是Vector集合
    • 当接收到驱动发来的BR_RELEASE消息时,就会把其中的Binder对象放到mPendingStrongDerefs
    • 并在processPendingDerefs()函数中介绍对象的引用计数
  • 调用getAndExecuteCommand函数
    • 函数中调用talkWithDriver读取驱动传递的数据
    • 然后调用executeCommand来执行

到这里,我们再来看下talkWithDriverexecuteCommand两个函数

talkWithDriver函数

talkWithDriver函数的作用是把IPCThreadState类中的mOut变量保存的数据通过ioctl函数发送到驱动,同时把驱动返回的数据放到类的mIn变量中。

talkWithDriver函数的代码如下:

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    if (mProcess->mDriverFD <= 0) {
        return -EBADF;
    }
    //ioctl 传输时所使用的的数据结构
    binder_write_read bwr;

    // Is the read buffer empty?
    // 判断 mIn 中的数据是否已经读取完毕,没有的话还需要继续读取
    const bool needRead = mIn.dataPosition() >= mIn.dataSize();
    // We don't want to write anything if we are still reading
    // from data left in the input buffer and the caller
    // has requested to read the next data.
    // 英文描述的很详细了哟
    // 如果不需要读取数据(doReceive=false,needRead=true),那么就可以准备写数据了
    const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
    bwr.write_size = outAvail;//表示要写入的长度
    bwr.write_buffer = (uintptr_t)mOut.data();//要写入的数据的指针

    // This is what we'll read.
    if (doReceive && needRead) {
        bwr.read_size = mIn.dataCapacity();
        bwr.read_buffer = (uintptr_t)mIn.data();
    } else {
        bwr.read_size = 0;
        bwr.read_buffer = 0;
    }
    // Return immediately if there is nothing to do.
    if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;

    bwr.write_consumed = 0;//这个字段后面会被填上驱动读取到的数据长度(写入到驱动的数据长度)
    bwr.read_consumed = 0;// 这个字段后面会被填上从驱动返回的数据长度
    status_t err;
    do {
// 9.0增加了一些平台判断,可能以后要多平台去支持了吧
#if defined(__ANDROID__)
        // 用ioctl和驱动交换数据
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        else
            err = -errno;
#else
        err = INVALID_OPERATION;
#endif
        if (mProcess->mDriverFD <= 0) {
            // 这个情况应该是设备节点不可用
            err = -EBADF;
        }
    } while (err == -EINTR);

    if (err >= NO_ERROR) {
        if (bwr.write_consumed > 0) {
            if (bwr.write_consumed < mOut.dataSize())
                // 如果已经写入驱动的数据长度小于mOut中的数据长度
                // 说明还没发送完,把已经写入驱动的数据移除掉
                // 剩下的数据等待下次发送
                mOut.remove(0, bwr.write_consumed);
            else {
                // 数据已经全部写入驱动,复位mOut
                mOut.setDataSize(0);
                // 做一些指针的清理工作
                processPostWriteDerefs();
            }
        }
        if (bwr.read_consumed > 0) {
            // 说明从驱动中读到了数据,设置好mInt对象
            mIn.setDataSize(bwr.read_consumed);
            mIn.setDataPosition(0);
        }
        return NO_ERROR;
    }

    return err;
}
复制代码
  • 准备发送到驱动中的数据保存在成员变量mOut
  • 从驱动中读取到的数据保存在成员变量mInt
  • 调用talkWithDriver时,如果mInt还有数据
    • 表示还没有处理完驱动发来的消息
    • 本次函数调用将不会从驱动中读取数据
  • ioctl函数
    • 使用的命令是BINDER_WRITE_READ
    • 需要binder_write_read结构体作为参数
    • 驱动篇再看

executeCommand 函数

executeCommand 函数是一个大的switch语句,处理从驱动传递过来的消息。

我们前面遇到了一些消息,大概包括:

  • BR_SPAWN_LOOP:驱动通知启动新线程的消息
  • BR_DEAD_BINDER:驱动通知Binder服务死亡的消息
  • BR_FINISHED:驱动通知线程退出的消息
  • BR_ERROR,BR_OK,BR_NOOP:驱动简单的回复消息
  • BR_RELEASE,BR_INCREFS,BR_DECREFS:驱动通知增加和减少Binder对象跨进程的引用计数
  • BR_TRANSACTION,:驱动通知进行Binder调用的消息

重点是BR_TRANSACTION,代码定义如下:

case BR_TRANSACTION:
        {
            binder_transaction_data tr;
            result = mIn.read(&tr, sizeof(tr));
            if (result != NO_ERROR) break; // 数据异常直接退出

            Parcel buffer;
            //用从驱动接收的数据设置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, this);

            const pid_t origPid = mCallingPid;
            const uid_t origUid = mCallingUid;
            const int32_t origStrictModePolicy = mStrictModePolicy;
            const int32_t origTransactionBinderFlags = mLastTransactionBinderFlags;
            // 从消息中取出调用者的进程ID和euid
            mCallingPid = tr.sender_pid;
            mCallingUid = tr.sender_euid;
            mLastTransactionBinderFlags = tr.flags;

            Parcel reply;
            status_t error;

            if (tr.target.ptr) {
                // We only have a weak reference on the target object, so we must first try to
                // safely acquire a strong reference before doing anything else with it.
                if (reinterpret_cast<RefBase::weakref_type*>(
                        tr.target.ptr)->attemptIncStrong(this)) {
                    // 如果ptr指针不为空,cookie保存的是BBinder的指针
                    // 调用cookie的transact函数
                    error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer,
                            &reply, tr.flags);
                    // 及时清除指针
                    reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this);
                } else {
                    error = UNKNOWN_TRANSACTION;
                }

            } else {
                //如果tr.target.ptr为0 表示是ServiceManager
                error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);
            }
            // 如果是同步调用,则把reply对象发送回去,否则什么也不做
            if ((tr.flags & TF_ONE_WAY) == 0) {
                LOG_ONEWAY("Sending reply to %d!", mCallingPid);
                if (error < NO_ERROR) reply.setError(error);
                sendReply(reply, 0);
            } else {
                LOG_ONEWAY("NOT sending reply to %d!", mCallingPid);
            }

            mCallingPid = origPid;
            mCallingUid = origUid;
            mStrictModePolicy = origStrictModePolicy;
            mLastTransactionBinderFlags = origTransactionBinderFlags;
        }
        break;
复制代码

BR_TRANSACTION消息的处理过程是:

  • 把消息解析出来后放置到Parcel类型的buffer对象中
  • 使用消息参数里的BBinder指针来调用transact函数
    • transact函数会传入函数号bufferreply
    • transact函数根据函数号来调用对应的服务函数
    • 服务函数执行完后,结果保存在reply对象中
  • 如果是同步调用,使用sendReply函数返回reply对象
  • 如果是异步调用,则什么也不做,结束调用
分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改