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

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对象
  • 如果是异步调用,则什么也不做,结束调用

Binder驱动

Binder 驱动是整个Binder框架的核心,这部分就会详细介绍消息协议内存共享机制对象传递的具体细节了

应用层和驱动的消息协议

Binder应用层IPCThreadStateBinder驱动之间通过ioctl来传递数据,因此,定义了一些ioctl命令

命令说明数据格式
BIDNER_WRITE_READ向驱动读取和写入数据,既可以单独读写,也可以同时读写。通过传入的数据中有无读写数据来控制struct binder_write_read
BINDER_SET_IDLE_TIMEOUT有定义未使用int64_t
BINDER_SET_MAX_THREADS设置线程池的最大线程数,达到上限后驱动将不会通知应用层启动新线程size_t
BINDER_SET_IDLE_PRIORITY有定义未使用int
BINDER_SET_CONTEXT_MGR将本进程设置为Binder系统管理进程,只有ServiceManager进程才会使用这个命令int
BINDER_THREAD_EXIT通知驱动当前线程要退出了,以便驱动清理和该线程相关的数据int
BINDER_VERSION获取Binder的版本号struct binder_verison

表中的命令中。最常用的命令是BIDNER_WRITE_READ,其余都是一些辅助性命令。BIDNER_WRITE_READ使用的数据格式定义如下:

struct binder_write_read {
	binder_size_t		write_size;	//计划向驱动写入的字节长度
	binder_size_t		write_consumed;	//实际写入驱动的长度,书中称驱动实际读取的字节长度
	binder_uintptr_t	write_buffer;//传递给驱动数据的buffer指针,也就是真正要写入的数据
	binder_size_t		read_size;	//计划从驱动中读取的字节长度
	binder_size_t		read_consumed;	//实际从驱动中读取到的字节长度
	binder_uintptr_t	read_buffer; //接收存放读取到的数据的指针
};

存放在read_bufferwrite_buffer中的数据也是有格式的,格式是消息ID加上binder_transaction_databinder_transaction_data定义如下:

struct binder_transaction_data {
	union {
		__u32	handle;
		binder_uintptr_t ptr;
	} target;               // BpBinder对象使用handle,BBinder使用ptr
	binder_uintptr_t	cookie;	// 对于BBinder对象,这里是BBinder的指针
	__u32		code;		// Binder服务的函数号码
	__u32	        flags;
	pid_t		sender_pid; // 发送方的进程ID
	uid_t		sender_euid; // 发送方的euid
	binder_size_t	data_size; // 整个数据区的大小
	binder_size_t	offsets_size; // IPC对象区域的大小	
	union {
		struct {
			binder_uintptr_t	buffer; // 指向数据区开头的指针
			binder_uintptr_t	offsets;// 指向数据区中IPC对对象区的指针
		} ptr;
		__u8	buf[8];
	} data;
};

结构binder_transaction_data中:

  • data.ptr指向传递给驱动的数据区的起始地址
    • 传递给驱动的数据区就是从应用层传递下来的Parcel对象的数据区
  • Parcel对象中如果打包了IPC对象(也就是Binder对象),或者文件描述符,数据区中会有一段空间专门用来保存这部分数据。
    • 因此使用data.ptr.offsets表示数据区IPC对象的起始位置
    • offsets_size表示数据区IPC对象的大小

Binder消息则根据发送接收分为两套,从命名上也比较容易区分:

  • 发送类型的指的是消息从应用层驱动的过程,通常以BC_开头
  • 接收类型的指的是消息从驱动应用层的过程,通常以BR_开头

发送给驱动的Binder命令列表

命令说明
BC_TRANSACTION发送Binder调用的数据
BC_REPLY返回Binder调用的返回值
BC_ACQUIRE_RESULT回应BR_ATTEMPT_ACQUIRE命令
BC_FREE_BUFFER释放通过mmap分配的内存块
BC_INCREFS增加Binder对象的弱引用计数
BC_ACQUIRE增加Binder对象的强引用计数
BC_DECREFS减少Binder对象的弱引用计数
BC_RELEASE减少Binder对象的强引用计数
BC_INCREFS_DONE回应BC_INCREFS指令
BC_ACQUIRE_DONE回应BC_ACQUIRE指令
BC_ATTEMPT_ACQUIRE将Binder对象的弱引用升级为强引用
BC_REGISTER_LOOPER把当前线程注册为线程池的主线程
BC_ENTER_LOOPER通知驱动,线程可以进入数据的发送和接收
BC_EXIT_LOOPER通知驱动,当前线程退出数据的发送和接收
BC_REQUEST_DEATH_NOTYFICATION通知驱动接收某个Binder服务的死亡通知
BC_CLEAR_DEATH_NOTYFICATION通知驱动不再接收某个Binder服务的死亡通知
BC_DEAD_BINDER_DONE回应BR_DEAD_BINDER命令

驱动返回的命令列表

命令说明
BR_ERROR驱动内部出错了
BR_OK命令成功
BR_TRANSACTIONBinder调用命令
BR_REPLY返回Binder调用的结果
BR_ACQUIRE_RESULT未使用
BR_DEAD_REPLY向驱动发送Binder调用时,如果对方已经死亡,则驱动回应此命令
BR_TRANSACTION_COMPLETE回应BR_TRANSACTION命令
BR_INCREFS要求增加Binder对象的弱引用计数
BR_ACQUIRE要求增加Binder对象的强引用计数
BR_DECREFS要求减少Binder对象的弱引用计数
BR_RELEASE要求减少Binder对象的强引用计数
BR_ATTEMPT_ACQUIRE要求将Binder对象的弱引用变成强引用
BR_NOOP命令成功
BR_SPAWN_LOOPER通知创建Binder线程
BR_FINISHED未使用
BR_DEAD_BINDER通知关注的Binder已经死亡
BR_CLEAR_DEATH_NOTYFICATION_DONE回应BC_CLEAR_DEATH_NOTYFICATION
BR_FAILED_REPLY如果Binder调用函数号不正确,回复本消息

Binder消息序列图

前面列出的两个命令列表看上去指令蛮多的,其实可以分为四类:

  • Binder线程管理相关
  • Binder方法调用相关
  • Binder对象引用计数管理相关
  • Binder对象死亡通知相关

看下面两个序列图:

Binder调用的消息序列图:
image

Binder对象引用计数管理的消息序列图:
image

Binder驱动分析

Binder驱动中定义的操作函数如下:

static const struct file_operations binder_fops = {
	.owner = THIS_MODULE,
	.poll = binder_poll,
	.unlocked_ioctl = binder_ioctl,
	.compat_ioctl = binder_ioctl,
	.mmap = binder_mmap,
	.open = binder_open,
	.flush = binder_flush,
	.release = binder_release,
};

Binder驱动中并没有实现常用的readwrite操作。数据的传输都是通过ioctl操作来完成的。

Binder驱动中定义了很多的数据结构,一些主要的数据结构如下:

数据结构说明
binder_proc每个使用open打开Binder设备文件的进程都会在驱动中创建一个biner_proc的结构,用来记录该进程的各种信息和状态。例如:Binder节点表节点引用表
binder_thread每个Binder线程Binder驱动中都有一个binder_thread结构,记录线程相关的信息,例如要完成的任务等
binder_nodebinder_proc中有一张Binder节点对象表,表项是binder_node结构。代表进程中的BBinder对象,记录BBinder对象的指针、引用计数等数据
binder_refbinder_proc中还有一张节点引用表,表项是binder_ref结构。代表进程中的BpBinder对象,保存所引用的对象binder_node指针。BpBinder中的mHandler值,就是它在索引表中的位置
binder_buffer驱动通过mmap的方式创建了一块大的缓存区,每次Binder传输数据,会在缓存区分配一个binder_buffer的结构来保存数据

在Binder驱动中,大量使用红黑树来管理数据。Binder驱动中是由内核提供的实现,具体表现是生命的一个全局变量binder_procs

static HLIST_HEAD(binder_procs);

所有进程的binder_proc结构体都将插入到这个变量代表的红黑树中。以binder_open的代码为例:

static int binder_open(struct inode *nodp, struct file *filp)
{
	struct binder_proc *proc;
	struct binder_device *binder_dev;

	binder_debug(BINDER_DEBUG_OPEN_CLOSE, "%s: %d:%d\n", __func__,
		     current->group_leader->pid, current->pid);

	proc = kzalloc(sizeof(*proc), GFP_KERNEL);//分配空间给binder_proc结构体
	//......
	get_task_struct(current->group_leader);// 获取当前进程的task结构
	proc->tsk = current->group_leader;
	//......
	INIT_LIST_HEAD(&proc->todo);// 初始化binder_porc中的todo队列
	//......
	filp->private_data = proc;// 将proc保存到文件结构中,供下次调用使用
	mutex_lock(&binder_procs_lock);
	// 将proc插入到全局变量binder_procs中
	hlist_add_head(&proc->proc_node, &binder_procs);
	mutex_unlock(&binder_procs_lock);
	//......
	return 0;
}

binder_open函数主要功能是:

  • 打开Binder驱动的设备文件
  • 为当前进程创建和初始化binder_proc结构体proc
  • proc插入到红黑树binder_procs
  • proc放入到file结构的private_data字段中
    • 调用驱动的其他操作时可以从file结构中取出代表当前进程的binder_proc

Binder的内存共享机制

前面介绍了,用户进程打开Binder设备后,会调用mmap在驱动中创建一块内存空间用于接收传递给本进程的Binder数据。我们看下mmap的代码(这部分是9.0的源码,和书中的结构有些不同):

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret;
	struct binder_proc *proc = filp->private_data;
	const char *failure_string;
	if (proc->tsk != current->group_leader)
		return -EINVAL;
	// 检查要求分配的空间是否大于SZ_4M的定义
	if ((vma->vm_end - vma->vm_start) > SZ_4M)
		vma->vm_end = vma->vm_start + SZ_4M;
    // 检查mmap的标记,不能带有FORBIDDEN_MMAP_FLAGS
	if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
		ret = -EPERM;
		failure_string = "bad vm_flags";
		goto err_bad_arg;
	}
	//fork 的子进程无法复制映射空间,并且不允许修改属性
	vma->vm_flags |= VM_DONTCOPY | VM_MIXEDMAP;
	vma->vm_flags &= ~VM_MAYWRITE;

	vma->vm_ops = &binder_vm_ops;
	vma->vm_private_data = proc;
	// 真正内存分配的逻辑在 binder_alloc_mmap_handler中
	ret = binder_alloc_mmap_handler(&proc->alloc, vma);
	//......
}
int binder_alloc_mmap_handler(struct binder_alloc *alloc,
			      struct vm_area_struct *vma)
{
	int ret;
	struct vm_struct *area;
	const char *failure_string;
	struct binder_buffer *buffer;

	mutex_lock(&binder_alloc_mmap_lock);
	//如果已分配缓存区,goto 错误
	if (alloc->buffer) {
		ret = -EBUSY;
		failure_string = "already mapped";
		goto err_already_mapped;
	}
	//在用户进程分配一块内存作为缓冲区
	area = get_vm_area(vma->vm_end - vma->vm_start, VM_ALLOC);
    //如果内存分配失败,goto 对应错误
	if (area == NULL) {
		ret = -ENOMEM;
		failure_string = "get_vm_area";
		goto err_get_vm_area_failed;
	}
	//把分配的缓冲区指针放在binder_proc的buffer字段
	alloc->buffer = area->addr;
	//重新配置下内存起始地址和偏移量
	alloc->user_buffer_offset =
		vma->vm_start - (uintptr_t)alloc->buffer;
	mutex_unlock(&binder_alloc_mmap_lock);
	//创建物理页结构体
	alloc->pages = kzalloc(sizeof(alloc->pages[0]) *
				   ((vma->vm_end - vma->vm_start) / PAGE_SIZE),
			       GFP_KERNEL);
			       
	alloc->buffer_size = vma->vm_end - vma->vm_start;
	//在内核分配struct buffer的内存空间
	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);

	//这部分和书中着实不太一样,看的晕晕的
	//目前只看出内存地址关联部分
	buffer->data = alloc->buffer;
	list_add(&buffer->entry, &alloc->buffers);
	buffer->free = 1;
	binder_insert_free_buffer(alloc, buffer);
	// 异步传输将可用空间大小设置为映射大小的一半
	alloc->free_async_space = alloc->buffer_size / 2;
	barrier();
	alloc->vma = vma;
	alloc->vma_vm_mm = vma->vm_mm;
	/* Same as mmgrab() in later kernel versions */
	atomic_inc(&alloc->vma_vm_mm->mm_count);

	return 0;
// ......对应的错误信息
}

内存分配时:

  • 首先调用get_vm_area用户进程分配一块地址空间
  • 接着在内核中也分配同样页数大小的空间
  • 然后把它们的物理内存地址绑定在一起
  • 这样应用内核间就能共享一块空间了

当发生Binder调用时:

  • 数据会从调用进程复制到内核空间
  • 驱动会在服务进程缓冲区中寻找一块合适大小的空间来存放数据
    • 因为服务进程用户空间缓冲区内核空间缓冲区是共享的
    • 所以服务进程不需要将数据再从内核空间复制到用户空间
    • 节省了一次复制过程
    • 提高了Binder通信效率

驱动的ioctl操作

ioctl在驱动中的实现是binder_ioctl函数,用来处理ioctl操作的命令,这些命令最常见的就是BINDER_WRITE_READ,用于Binder调用,先看下这个命令的处理过程:

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret;
	struct binder_proc *proc = filp->private_data;
	struct binder_thread *thread;
	unsigned int size = _IOC_SIZE(cmd);
	void __user *ubuf = (void __user *)arg;
	
	// 检查binder_stop_on_user_error的值是否小于2,这个值表示Binder中的错误数
	// 大于2则挂起当前进程到binder_user_error_wait的等待队列上
	ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
	if (ret)
		goto err_unlocked;
		
	// 获取当前线程的数据结构
	thread = binder_get_thread(proc);
	if (thread == NULL) {
		ret = -ENOMEM;
		goto err;
	}

	switch (cmd) {
	case BINDER_WRITE_READ:
	    //调用 binder_ioctl_write_read 方法
		ret = binder_ioctl_write_read(filp, cmd, arg, thread);
		break;
}
static int binder_ioctl_write_read(struct file *filp,
				unsigned int cmd, unsigned long arg,
				struct binder_thread *thread)
{
	int ret = 0;
	struct binder_proc *proc = filp->private_data;
	unsigned int size = _IOC_SIZE(cmd);
	void __user *ubuf = (void __user *)arg;
	struct binder_write_read bwr;

	if (size != sizeof(struct binder_write_read)) {
		ret = -EINVAL;
		goto out;
	}
	// 这里应该就是就是一次拷贝,把数据复制到内核中
	if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
		ret = -EFAULT;
		goto out;
	}
	if (bwr.write_size > 0) {
	    // 如果命令时传递给驱动的数据,调用 binder_thread_write 处理
		ret = binder_thread_write(proc, thread,
					  bwr.write_buffer,
					  bwr.write_size,
					  &bwr.write_consumed);
		//省略异常处理和log打印
	}
	if (bwr.read_size > 0) {
	    // 如果命令时读取驱动的数据,调用 binder_thread_read 处理
		ret = binder_thread_read(proc, thread, bwr.read_buffer,
					 bwr.read_size,
					 &bwr.read_consumed,
					 filp->f_flags & O_NONBLOCK);
		trace_binder_read_done(ret);
		binder_inner_proc_lock(proc);
		// 如果进程的todo队列不为空,唤醒用户进程处理
		if (!binder_worklist_empty_ilocked(&proc->todo))
			binder_wakeup_proc_ilocked(proc);
		binder_inner_proc_unlock(proc);
		//省略异常处理和log打印
	}
// 异常处理
}

BINDER_WRITE_READ命令有可能是向驱动写数据,也可能是从驱动读数据

  • 写数据,通过binder_thread_write方法
  • 读数据,通过binder_thread_read方法
  • 在下一节调用过程会细讲这两个方法

除了BINDER_WRITE_READ命令外,还有像BINDER_SET_MAX_THREADSBINDER_SET_CONTEXT_MGR等辅助通信的命令,大家可以阅读下源码。

学习的重点还是看调用过程的数据读写部分

Binder调用过程

Binder调用Binder驱动中最核心的活动,整个过程几乎涉及了Binder驱动所有的数据结构。我们先从调用的整体流程上来看下:

  • Binder调用客户进程中发起
    • 通过ioctl操作来运行驱动binder_ioctl
  • 驱动接收到命令BC_TRANSACTION
    • 找到代表服务进程binder_proc结构体
    • 服务进程的缓冲区分配一块内存来保存从客户进程传递的数据
    • ioctl回复消息BR_TRANSACTION_COMPLETED
  • ---------到这里,本次ioctl调用结束---------
  • 客户进程收到回复消息后
    • 再次调用ioctl进入等待,准备读取数据,直到调用结果返回

服务进程无法知道何时会有Binder调用到达,因此它至少由一个线程在ioctl上等待。

  • 驱动会在有调用到达时唤醒等待的线程,并将数据传给服务进程
  • 同一时刻可能有多个Binder调用到达
    • 驱动为每个调用创建一个binder_transaction的结构体
    • 结构体中的work字段插入到服务进程todo队列
      • todo队列是一个binder_work结构的列表
      • 每个进程都会有一个todo队列来接收需要完成的工作
      • 服务端用户进程ioctl执行读操作时
        • 会循环执行todo队列中需要完成的任务
        • 直到队列为空才挂起等待

客户进程的调用流程

上面刚刚讲到,客户进程发送的Binder调用消息会执行到函数binder_thread_write,这个函数的功能就是处理所有用户进程发送的消息,我们看下和Binder调用有关的部分:

static int binder_thread_write(struct binder_proc *proc,
			struct binder_thread *thread,
			binder_uintptr_t binder_buffer, size_t size,
			binder_size_t *consumed)
{
//......
        case BC_TRANSACTION:
		case BC_REPLY: {
			struct binder_transaction_data tr;
			// 包结构体tr的数据复制到内核
			if (copy_from_user(&tr, ptr, sizeof(tr)))
				return -EFAULT;
			ptr += sizeof(tr);
			// 调用 binder_transaction 函数处理
			binder_transaction(proc, thread, &tr,
					   cmd == BC_REPLY, 0);
			break;
		}
//......
}

BC_TRANSACTION命令的处理就是调用了binder_transaction函数,binder_transaction函数既要处理BC_TRANSACTION也会处理BC_REPLY消息,我们先看读取的情况,也就是reply=false的情况:

  1. 找到代表目标进程的节点:
        if (tr->target.handle) {
			struct binder_ref *ref;
			//查找handle对应的引用项
			ref = binder_get_ref_olocked(proc, tr->target.handle,
						     true);
			if (ref) {
			    //引用项的node字段保存了Binder对象节点的指针
			    //复制给target_node
				target_node = binder_get_node_refs_for_txn(
						ref->node, &target_proc,
						&return_error);
			} else {
                //......
			}
		//......
		} else {
		    //把管理进程的节点名放到target_node中
			target_node = context->binder_context_mgr_node;
			//......
		}
  1. 搜寻目标线程
		if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {
			struct binder_transaction *tmp;
			tmp = thread->transaction_stack;
			//......
			while (tmp) {
				struct binder_thread *from;
				spin_lock(&tmp->lock);
				from = tmp->from;
				if (from && from->proc == target_proc) {
					atomic_inc(&from->tmp_ref);
					target_thread = from;
					spin_unlock(&tmp->lock);
					break;
				}
				spin_unlock(&tmp->lock);
				tmp = tmp->from_parent;
			}
		}

上面这段代码的逻辑是:

  • 如果本次调用不是异步调用,并且调用者线程中的transaction_stack不为NULL,则在其中查找和本次调用具有相同目标进程的transaction_stack,如果找到,则把它的目标线程设置为本次调用的目标线程。
  • transaction_stack是一个列表,保存了本线程所有正在执行的Binder调用的binder_transaction结构体。
  • Binder驱动中用binder_transaction来保存一次Binder调用的所有数据,包括传递的数据、通信双发的进程、线程信息等。因为Binder调用涉及两个进程,还要向调用端传递返回值。
  • 所以,驱动中用结构binder_transaction保存还没结束的Binder调用
  • 通常情况下执行Binder调用时不会存在相同进程的Binder调用,因此target_thread的值大多为NULL
  1. 如果有目标线程,则使用目标线程中的todo队列,否则使用目标进程todo队列
	if (target_thread)
		e->to_thread = target_thread->pid;
	e->to_proc = target_proc->pid;
  1. 为当前的Binder调用创建binder_transaction结构,并用调用的数据填充它。
	t = kzalloc(sizeof(*t), GFP_KERNEL);
	//......
	tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
	//......
	if (!reply && !(tr->flags & TF_ONE_WAY))
		t->from = thread;
	else
		t->from = NULL;
	t->sender_euid = task_euid(proc->tsk);
	t->to_proc = target_proc;
	t->to_thread = target_thread;
	t->code = tr->code;
	t->flags = tr->flags;
	if (!(t->flags & TF_ONE_WAY) &&
	    binder_supported_policy(current->policy)) {
		/* Inherit supported policies for synchronous transactions */
		t->priority.sched_policy = current->policy;
		t->priority.prio = current->normal_prio;
	} else {
		/* Otherwise, fall back to the default priority */
		t->priority = target_proc->default_priority;
	}
  1. 目标进程(也就是服务端)的缓冲区分配空间,复制用户进程的数据到内核
    t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size,
		tr->offsets_size, extra_buffers_size,
		!reply && (t->flags & TF_ONE_WAY));
	//......
	if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)
			   tr->data.ptr.buffer, tr->data_size)) {
		//......
	}
	if (copy_from_user(offp, (const void __user *)(uintptr_t)
			   tr->data.ptr.offsets, tr->offsets_size)) {
		//......
	}
  • 这里复制的数据是Binder调用的参数数据,也是需要传递给服务进程的数据,因此需要缓冲区。这个缓冲区是在目标进程的大的缓冲区中分配的
  1. 处理传输中的Binder对象
off_end = (void *)off_start + tr->offsets_size;
	sg_bufp = (u8 *)(PTR_ALIGN(off_end, sizeof(void *)));
	sg_buf_end = sg_bufp + extra_buffers_size;
	off_min = 0;
	for (; offp < off_end; offp++) {
		//......
	}
  • 通过参数传递的Binder对象需要进行转换,这里的for循环就是进行转换操作,内容较多,会在下面处理传递的Binder对象章节进行讲解
  1. 将本次调用的binder_transaction结构体链接到线程的binder_stack列表中
if (!(t->flags & TF_ONE_WAY)) {
		//......
		t->need_reply = 1;
		t->from_parent = thread->transaction_stack;
		thread->transaction_stack = t;
		// 书中下面注释部分的代码已经整合到 binder_proc_transaction 函数中了
		// 首先,将结构 binder_transaction中的binder_work放到目标目标进程或线程的todo队列
		// 然后,创建一个新的binder_work的结构体,并将它放到发送线程的todo队列
		if (!binder_proc_transaction(t, target_proc, target_thread)) {
		    //...... 失败处理
		}
	}
  • binder_transaction结构中包含了一个binder_work的结构体,因此它可以被放到todo队列
  • 本地调用时binder_worktype被设置为BINDER_WORK_TRANSACTION后,插入了目标进程目标线程todo队列
  • 为了使客户进程能收到回复消息,这里也会创建一个新的binder_work的结构体,并把它的type设置成了BINDER_WORK_TRANSAXTION_COMPLETET,并将它插入到当前线程的todo队列

前面介绍过,在binder_thread_write函数执行完后,还会去判断是否需要执行binder_thread_read函数:

  • 由于调用端执行完BC_TRANSACTION后,会立刻执行ioctl指令
  • 所以,在调用操作上,binder_thread_read函数算是紧接着binder_thread_write函数后执行的

刚才新建的binder_work的结构体已经插入todo队列了,我们看下binder_thread_read函数会进行哪些和todo队列相关的操作:

while (1) {
        //......
        // 获得todo队列,获取失败则goto retry
		if (!binder_worklist_empty_ilocked(&thread->todo))
			list = &thread->todo;
		else if (!binder_worklist_empty_ilocked(&proc->todo) &&
			   wait_for_proc_work)
			list = &proc->todo;
		else {
			binder_inner_proc_unlock(proc);
			/* no data added */
			if (ptr - buffer == 4 && !thread->looper_need_return)
				goto retry;
			break;
		}
		//取出todo队列中的元素
		w = binder_dequeue_work_head_ilocked(list);
		//......
		switch (w->type) {
		    //......省略大量case语句
		    case BINDER_WORK_TRANSACTION_COMPLETE: {
			    binder_inner_proc_unlock(proc);
			    cmd = BR_TRANSACTION_COMPLETE;
			    //把返回消息通过put_user放到用户空间的指针中
			    if (put_user(cmd, (uint32_t __user *)ptr)) 
				    return -EFAULT;
			    ptr += sizeof(uint32_t);

			    binder_stat_br(proc, thread, cmd);

			    kfree(w);
			    binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
		    } break;
		    //......省略大量case语句
	    }
		//......
}
  • binder_thread_read函数所做的处理就是把回复消息BR_TRANSACTION_COMPLETE复制到用户空间
  • 这样客户进程就可以收到回复消息了

到这里,客户进程的调用结束了。但是,Binder调用才完成一半,接下来,看看服务进程是如何调用数据的。

服务进程的调用流程

服务进程至少有一个线程会在ioctl上等待调用的到来。服务进程调用ioctl时传递的是读数据的请求,所以最后调用的也是binder_thread_read函数,我们看下binder_thread_read函数完整的处理流程:

  1. 如果保存返回结果的缓冲区中还没有数据,先写入BR_NOOP消息:
    if (*consumed == 0) {
		if (put_user(BR_NOOP, (uint32_t __user *)ptr))
			return -EFAULT;
		ptr += sizeof(uint32_t);
	}
  1. 进入循环处理所有todo队列中的工作
while (1) {
    //......
}
  1. 读取线程或进程todo队列中需要完成的工作
        struct binder_work *w = NULL;
        //......
		if (!binder_worklist_empty_ilocked(&thread->todo))
			list = &thread->todo;
		else if (!binder_worklist_empty_ilocked(&proc->todo) &&
			   wait_for_proc_work)
			list = &proc->todo;
		//......
		w = binder_dequeue_work_head_ilocked(list);
  1. switch语句处理所有类型的工作
    switch (w->type) {
		case BINDER_WORK_TRANSACTION: {
			binder_inner_proc_unlock(proc);
			t = container_of(w, struct binder_transaction, work);
		} break;
    }
  • 经过客户进程的调用流程后,此时的服务进程中已经存在一个类型为BINDER_WORK_TRANSACTION的工作需要处理
  • 这里只是取出了和binder_work关联的binder_transaction结构体指针
  • 并保存到变量t
  1. 调整线程的优先级
        if (!t)
			continue;

		if (t->buffer->target_node) {
			struct binder_node *target_node = t->buffer->target_node;
			struct binder_priority node_prio;

			tr.target.ptr = target_node->ptr;
			tr.cookie =  target_node->cookie;
			node_prio.sched_policy = target_node->sched_policy;
			node_prio.prio = target_node->min_priority;
			binder_transaction_priority(current, t, node_prio,
						    target_node->inherit_rt);
			cmd = BR_TRANSACTION;
		}
  • 如果t为NULL,继续循环
  • 否则,开始准备返回的消息BR_TRANSACTION
  • 同时,设置线程的优先级
    • 如果调用线程的优先级低于当前线程指定的最低优先级,则把当前线程的优先级设为调用线程的优先级
    • 否则,把当前线程设为指定的最低优先级
    • 这意味着Binder线程会以尽量低的优先级运行
  1. 准备返回的数据
        tr.code = t->code;
		tr.flags = t->flags;
		tr.sender_euid = from_kuid(current_user_ns(), t->sender_euid);
		//......
		// 让tr中的data指针指向内核中保存的数据缓冲区
		tr.data_size = t->buffer->data_size;
		tr.offsets_size = t->buffer->offsets_size;
		tr.data.ptr.buffer = (binder_uintptr_t)
			((uintptr_t)t->buffer->data +
			binder_alloc_get_user_buffer_offset(&proc->alloc));
		tr.data.ptr.offsets = tr.data.ptr.buffer +
					ALIGN(t->buffer->data_size,
					    sizeof(void *));
		// 把 BR_TRANSACTION 消息复制到用户空间
		if (put_user(cmd, (uint32_t __user *)ptr)) {
			//......
			return -EFAULT;
		}
		ptr += sizeof(uint32_t);
		if (copy_to_user(ptr, &tr, sizeof(tr))) {
		    //把结构体tr数据复制到用户空间
			//......
			return -EFAULT;
		}
		ptr += sizeof(tr);

		//......
		break;//跳出while循环

这一段代码都是为消息BR_TRANSACTION准备返回数据,要注意的是:

  • 调用copy_to_user复制到用户空间的只是结构体tr的数据
  • 服务进程得到这个结构体之后,会直接读取它里面的data指针的数据
  • 数据准备完毕后,使用break语句跳出while循环
  1. 启动新线程
    if (proc->requested_threads == 0 &&
	    list_empty(&thread->proc->waiting_threads) &&
	    proc->requested_threads_started < proc->max_threads &&
	    (thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
	     BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */
	     /*spawn a new thread if we leave this out */) {
		proc->requested_threads++;
		//......
		if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))
			return -EFAULT;
	}

当前线程要执行Binder调用,新来的调用也需要线程来处理,因此:

  • 函数结束前会检查进程中可用的线程数
    • 如果需要创建新线程,则在返回的buffer数据中增加BR_SPAWN_LOOPER消息,服务进程收到这个消息会启动新的线程

完整的Binder调用过程还需要把回复消息传递给客户进程,这个过程使用的函数还是前面的这些,暂时不分析了。消化一下先

处理传递的Binder对象

前面介绍了Binder对象传递的原理和用户层的实现。(原理上整个人还是晕晕的),我们来看下Binder驱动如何实现Binder对象的传递的。

  • Binder驱动中,代表每个进程的结构binde_proc中有两个字段:nodesrefs_by_node
    • 这两个字段各指向两颗红黑树的头
    • nodes:指向的是Binder节点对象表,储存本进程中Binder实体对象相关的数据
    • refs_by_node:指向的是Binder引用对象表,存储本进程的Binder引用对象的数据以及对应的实体对象的节点指针

来个抽象点的图:
image

Binder驱动中处理对象转换的代码位于函数binder_transaction

        case BINDER_TYPE_BINDER:
		case BINDER_TYPE_WEAK_BINDER: {
			struct flat_binder_object *fp;
			fp = to_flat_binder_object(hdr);
			ret = binder_translate_binder(fp, t, thread);
		} break;
static int binder_translate_binder(struct flat_binder_object *fp,
				   struct binder_transaction *t,
				   struct binder_thread *thread)
{
	struct binder_node *node;
	struct binder_proc *proc = thread->proc;
	struct binder_proc *target_proc = t->to_proc;
	struct binder_ref_data rdata;
	int ret = 0;
	//根据binder值查找Binder对象表中的节点
	node = binder_get_node(proc, fp->binder);
	if (!node) { //没有则新建一个节点
		node = binder_new_node(proc, fp);
		if (!node)
			return -ENOMEM;
	}
	//......
	if (security_binder_transfer_binder(proc->tsk, target_proc->tsk)) {
		ret = -EPERM;
		goto done;
	}
	// 在接收端进程中寻找节点的引用,找不到会创建一个新的引用
	ret = binder_inc_ref_for_node(target_proc, node,
			fp->hdr.type == BINDER_TYPE_BINDER,
			&thread->todo, &rdata);
	if (ret)
		goto done;
	// 将传递的binder数据结构的type的值改为 BINDER_TYPE_HANDLE
	if (fp->hdr.type == BINDER_TYPE_BINDER)
		fp->hdr.type = BINDER_TYPE_HANDLE;
	else
		fp->hdr.type = BINDER_TYPE_WEAK_HANDLE;
	fp->binder = 0;
	// rdata.desc存放的是节点引用表中的序号,赋值给handle
	fp->handle = rdata.desc;
	fp->cookie = 0;
	//......
}

上面的流程是

  • 通过binder_get_node函数在发送进程的Binder对象节点表中查找节点
    • 查找是通过比较数据中的binder字段和节点中对应的字段进行的
    • binder字段中存放的是Binder对象的弱引用指针
    • 如果没找到
      • 新建节点
      • binder字段cookie字段的值保存到新节点
  • 通过binder_inc_ref_for_node函数在目标进程查找Binder节点引用
    • 没找到会新建一个
    • 引用指的是节点引用表中的refs_by_node节点,包含指向Binder节点的指针
  • 把数据的type字段的值改为BINDER_TYPE_HANDLEBINDER_TYPE_WEAK_HANDLE
  • handle字段的值设为在节点引用表的序号,这也是Binder引用对象handle值的来历

下面再看看如何处理类型BINDER_TYPE_HANDLEBINDER_TYPE_WEAK_HANDLE

        case BINDER_TYPE_HANDLE:
		case BINDER_TYPE_WEAK_HANDLE: {
			struct flat_binder_object *fp;

			fp = to_flat_binder_object(hdr);
			ret = binder_translate_handle(fp, t, thread);
		} break;
static int binder_translate_handle(struct flat_binder_object *fp,
				   struct binder_transaction *t,
				   struct binder_thread *thread)
{
    //......
    // 通过handle值查找节点引用
	node = binder_get_node_from_ref(proc, fp->handle,
			fp->hdr.type == BINDER_TYPE_HANDLE, &src_rdata);
	if (!node) {
	    //.....
		return -EINVAL;
	}
	//......
	if (node->proc == target_proc) {
	    // 如果目标进程就是Binder对象的进程,开始转换
		if (fp->hdr.type == BINDER_TYPE_HANDLE)
			fp->hdr.type = BINDER_TYPE_BINDER;
		else
			fp->hdr.type = BINDER_TYPE_WEAK_BINDER;
		fp->binder = node->ptr;
		fp->cookie = node->cookie;
		if (node->proc)
			binder_inner_proc_lock(node->proc);
		binder_inc_node_nilocked(node,
					 fp->hdr.type == BINDER_TYPE_BINDER,
					 0, NULL);
		//......
	} else {
		//......
		// 如果不是,则在目标进程新建一个Binder节点的引用
		ret = binder_inc_ref_for_node(target_proc, node,
				fp->hdr.type == BINDER_TYPE_HANDLE,
				NULL, &dest_rdata);

		//......
		fp->handle = dest_rdata.desc;
		fp->cookie = 0;
		trace_binder_transaction_ref_to_ref(t, node, &src_rdata,
		//......
	}
}

这部分代码的流程是:

  • 通过传输数据的handle字段在发送进程的节点引用表中查找
    • 正常情况下是可以找到,不能就错误返回
  • 如果目标进程就是Binder对象所在的进程
    • 开始进行转换,把数据的type字段转为BINDER_TYPE_BINDERBINDER_TYPE_WEAK_BINDERBINDER_WORK_TRANSACTION
    • bindercookie字段设置为节点中保存的值
  • 如果目标进程不是Binder对象所在的进程
    • 在目标对象中建立一个节点对象的引用

到这里呢,Binder原理部分就差不多了,已经了解了包括:

  • 客户端调用接收消息的过程
  • 服务端监听回复消息的过
  • 驱动传输数据调度线程的操作

现在,我们再来看最后的一小部分:ServiceManager的作用

ServiceManager的作用

关于ServiceManager,先简单描述:

  • ServiceManagerBinder架构中用来解析Binder名称的模块
  • ServiceManager本身就是一个Binder服务
  • ServiceManager并没有使用libbinder来构建Binder服务
    • 2020-09-04 同步了下项目代码,发现也替换成libbinder那一套了。。。。。
    • 不确定是不是更换仓储了
    • 要是这样就没意思了,简易版本对于加深binder理解还是很有帮助的
    • 我们暂且按照老的版本来看下吧
  • ServiceManager自己实现了一个简单的binder框架来直接和驱动通信。。。

ServiceManager源码路径在:frameworks/native/cmds/servicemanager,主要包含两个文件:

  • binder.c:用来实现简单的Binder通信功能

    • 简单版本的binder协议实现
    • 直接的ioctl操作与binder驱动通信
    • 不是本节的重点,感兴趣可以参照源码来学习啦
  • service_manager.c:用来实现ServiceManager的业务逻辑

    • 重点是了解ServiceManager如何响应Binder服务的注册和查询的
  • 这么重要的服务要在什么时间启动呢?

    • 这部分是在 system/core/rootdir/init.rc中配置
    on post-fs
        ......
        # start essential services
        start logd
        start servicemanager
        start hwservicemanager
        start vndservicemanager
    
    • hwservicemanager 用来支持HIDL
    • vndservicemanager 第三方厂商使用,应该是从Treble架构中出现的

ServiceManager的架构

ServiceManagermain函数开始:

int main(int argc, char** argv)
{
    struct binder_state *bs;
    union selinux_callback cb;
    char *driver;

    if (argc > 1) {
        driver = argv[1];
    } else {
        driver = "/dev/binder";
    }

    bs = binder_open(driver, 128*1024);
    if (!bs) {
        //......
        // 省略一些宏判断
        return -1;
    }

    if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }
    
    // 设置selinux callback
    cb.func_audit = audit_callback;
    selinux_set_callback(SELINUX_CB_AUDIT, cb);
    cb.func_log = selinux_log_callback;
    selinux_set_callback(SELINUX_CB_LOG, cb);
    
    //......
    // 省略一些宏判断,都是为了获取sehandle(检查SELinux权限)
    sehandle = selinux_android_service_context_handle();
    
    selinux_status_open(true);
    if (sehandle == NULL) {
        ALOGE("SELinux: Failed to acquire sehandle. Aborting.\n");
        abort();
    }
    if (getcon(&service_manager_context) != 0) {
        ALOGE("SELinux: Failed to acquire service_manager context. Aborting.\n");
        abort();
    }
    binder_loop(bs, svcmgr_handler);

    return 0;
}

main函数的流程如下:

  • 首先调用binder_open来打开binder设备和初始化系统
    • 同时创建一块128x1024大小的内存空间
  • 接着调用binder_become_context_manager把本进程设置为Binder框架的管理进程
    • binder_become_context_manager函数代码如下:
    int binder_become_context_manager(struct binder_state *bs)
    {
        return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
    }
    
    • 函数灰常简单,直接通过ioctl把控制命令BINDER_SET_CONTEXT_MGR发到了驱动
  • 最后执行binder_loop(bs, svcmgr_handler),简单处理后,循环等待消息

我们要说一说binder_loop(bs, svcmgr_handler),小弟C语言不熟,感觉这个操作很SAO:

  1. binder_loop的函数定义是:
void binder_loop(struct binder_state *bs, binder_handler func)
  • 第二个参数传入的是一个binder_handler类型
  1. 再来看下binder_handler类型的定义:
typedef int (*binder_handler)(struct binder_state *bs,
                              struct binder_transaction_data *txn,
                              struct binder_io *msg,
                              struct binder_io *reply);
  • 这是定义了一个函数指针类型
  • 返回值是int
  1. 再来看下binder_loop(bs, svcmgr_handler)中的svcmgr_handler函数的声明
int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    //......
    // 省略一些switch语句,稍后详解
    return 0;
}
  • 传入的指针是svcmgr_handler函数指针
  • 应该会自动转型为binder_handler指针类型
  • 毕竟函数参数、返回值都是一样的
  • 这部分是我觉得最神奇的地方。。。。。
    • 特意写了个C代码试了下,
    • 如果定义的函数的参数返回值typedef定义的函数指针不一致的话,强转编译会失败
    • 不过这种操作感觉还是很不正经。。(还是Java更严谨一些)
  1. 参数理解的差不多了,我们仔细看下binder_loop的实现,这两个参数传进去干了啥。

    PS:简化版就是更容易理解些。。。。。。

void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    uint32_t readbuf[32];
    
    // 老样子,用来记录写入到驱动的一些相关参数
    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;
    // 该属性应该是通知驱动已经准备好接收数据了
    readbuf[0] = BC_ENTER_LOOPER;
    binder_write(bs, readbuf, sizeof(uint32_t));

    for (;;) {
        //无限循环
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (uintptr_t) readbuf;
        // 开始读取数据
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);

        if (res < 0) {
            // 异常通信,跳过此次,继续循环
            ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
            break;
        }
        // 读取到数据后
        // 调用 binder_parse 进行数据解析
        // 顺便把 binder_handler 函数指针也传递进去
        res = binder_parse(bs, 0, (uintptr_t) readbuf, bwr.read_consumed, func);
        
        // 异常情况,继续循环
        if (res == 0 || res < 0) {
            //......
            break;
        }
    }
}
  • 有注释,很很简洁的逻辑
  • 最后执行到了binder_parse函数
  1. 我们继续跟进binder_parse函数
int binder_parse(struct binder_state *bs, struct binder_io *bio,
                 uintptr_t ptr, size_t size, binder_handler func)
{
    int r = 1;
    uintptr_t end = ptr + (uintptr_t) size;

    while (ptr < end) {
        //取指令
        uint32_t cmd = *(uint32_t *) ptr;
        switch(cmd) {
        //......
        // 由于binder_handler 在这里会被执行到
        // 所以我们先重点看这个,当Binder调用过来时
        case BR_TRANSACTION: {
            struct binder_transaction_data *txn = (struct binder_transaction_data *) ptr;
            // 数据检查
            if ((end - ptr) < sizeof(*txn)) {
                ALOGE("parse: txn too small!\n");
                return -1;
            }
            binder_dump_txn(txn);
            // 如果函数指针存在
            if (func) {
                unsigned rdata[256/4];
                struct binder_io msg;
                struct binder_io reply;
                int res;
                // 一些初始化操作
                bio_init(&reply, rdata, sizeof(rdata), 4);
                bio_init_from_txn(&msg, txn);
                //执行 binder_handler 函数指针指向的函数
                res = func(bs, txn, &msg, &reply);
                
                if (txn->flags & TF_ONE_WAY) {
                    // 这种情况不会返回结果
                    binder_free_buffer(bs, txn->data.ptr.buffer);
                } else {
                    // 返回结果数据
                    binder_send_reply(bs, &reply, txn->data.ptr.buffer, res);
                }
            }
            ptr += sizeof(*txn);
            break;
        }
        //......
    }
    return r;
}
  • 还是看注释哈,代码很简洁,注释很详细,哈哈哈
  • 到这里,我们可以看出来,真正处理调用服务的是binder_handler这个函数指针啊
  • 也就是说远程调用的处理逻辑在svcmgr_handler这个函数呀
    • 666!
  1. 我们来看下svcmgr_handler函数
int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    //......
    // 如果请求的目标服务不是ServiceManager,直接返回
    if (txn->target.ptr != BINDER_SERVICE_MANAGER)
        return -1;
        
    // 如果请求消息内容只是简单的测试通路,不需要继续执行,直接返回 0
    if (txn->code == PING_TRANSACTION)
        return 0;

    // 检查收到的消息 id 串
    strict_policy = bio_get_uint32(msg);
    s = bio_get_string16(msg, &len);
    if (s == NULL) {
        return -1;
    }
    //......
    // 检查SELinux 的权限
    if (sehandle && selinux_status_updated() > 0) {
        struct selabel_handle *tmp_sehandle = selinux_android_service_context_handle();
        if (tmp_sehandle) {
            selabel_close(sehandle);
            sehandle = tmp_sehandle;
        }
    }

    switch(txn->code) {
    case SVC_MGR_GET_SERVICE:
    case SVC_MGR_CHECK_SERVICE:
        // 处理查询或者获取服务的指令
        //......
        break;
    case SVC_MGR_ADD_SERVICE:
        // 处理注册服务的的指令
        //......
        break;

    case SVC_MGR_LIST_SERVICES: {
        //......
        // 处理获取服务列表的指令
    }
    default:
        ALOGE("unknown code %d\n", txn->code);
        return -1;
    }
    //发送返回消息
    bio_put_uint32(reply, 0);
    return 0;
}
  • ServiceManager的架构非常简单高效,只有一个循环来和binder驱动进行通信

ServiceManager提供的服务

svcmgr_handler函数中可以看到,ServiceManager提供了三种服务功能:

  • 注册Binder服务
  • 查询Binder服务
  • 获取Binder服务列表

注册Binder服务

case SVC_MGR_ADD_SERVICE中实现的注册Binder服务功能,具体的实现函数是do_add_service,代码如下:

int do_add_service(struct binder_state *bs, const uint16_t *s, size_t len, uint32_t handle,
                   uid_t uid, int allow_isolated, uint32_t dumpsys_priority, pid_t spid) {
    struct svcinfo *si;

    // 一些基础的信息判断
    if (!handle || (len == 0) || (len > 127))
        return -1;
    // 检查调用进程是否有权限注册服务
    if (!svc_can_register(s, len, spid, uid)) {
        //......省略log打印
        return -1;
    }
    // 查看要注册的服务是否已经存在
    si = find_svc(s, len);
    if (si) {
    // 如果存在,先把以前的Binder对象的引用计数减一
        if (si->handle) {
            svcinfo_death(bs, si);
        }
        // 把原先节点中的handle替换成新的handle
        si->handle = handle;
    } else {
        // 服务不存在,则生成新的列表项,初始化后加入列表
        si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
        if (!si) {
            // 内存申请失败,直接退出
            return -1;
        }
        // 一些初始化操作
        si->handle = handle;
        si->len = len;
        memcpy(si->name, s, (len + 1) * sizeof(uint16_t));
        si->name[len] = '\0';
        si->death.func = (void*) svcinfo_death;
        si->death.ptr = si;
        si->allow_isolated = allow_isolated;
        si->dumpsys_priority = dumpsys_priority;
        si->next = svclist;
        svclist = si;
    }
    // 增加Binder服务的引用计数
    binder_acquire(bs, handle);
    // 注册该Binder服务的死亡通知
    binder_link_to_death(bs, handle, &si->death);
    return 0;
}

do_add_service函数的流程是:

  • 首先检查调用的进程是否有注册服务的权限
    • 这部分是通过SELinux来控制的,后面会学习到
  • 接着检查需要注册的服务是否已经存在
    • 存在,把原来Binder服务在驱动的引用计数减一
    • 不存在
      • 新创建一个scvinfo结构
      • 填充需要待注册服务的相关信息到结构中
      • 把结构加入到服务列表svclist
  • 通知内核把Binder服务的引用计数加一
  • 注册该服务的死亡通知

有木有发现对于服务已经存在的情况:

  • 进行了先减一加一的操作
  • 是不是可以不用操作计数也可以呢?

可以思考下

查询Binder服务

case SVC_MGR_CHECK_SERVICE中处理查询服务的功能,具体的实现接口是do_find_service函数,代码如下:

uint32_t do_find_service(const uint16_t *s, size_t len, uid_t uid, pid_t spid)
{
    struct svcinfo *si = find_svc(s, len);
    if (!si || !si->handle) {
        return 0;
    }
    if (!si->allow_isolated) {
        // If this service doesn't allow access from isolated processes,
        // then check the uid to see if it is isolated.
        uid_t appid = uid % AID_USER;
        if (appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END) {
            return 0;
        }
    }
    if (!svc_can_find(s, len, spid, uid)) {
        return 0;
    }
    return si->handle;
}

do_find_service函数的主要工作是搜索列表、返回查找到的服务。请注意有一段判断uid的代码:

  • 在调用ServiceManageraddBinder服务时有个参数allowed_isolated,用来指定服务是否允许从沙箱中访问
  • 这里的代码应该是判断调用进程是否是一个隔离进程
    • 如果appidAID_ISOLATED_START(99000)AID_ISOLATED_END(99999)之间
    • 表明这个服务可以通过allowed_isolated来控制是否允许一般的用户进程来使用其服务

结语

终于、终于看完了。

书中后面其实还有ashmem 匿名共享内存的内容(Android自己实现的,在mmap基础上开发的,基于binder通信的内存共享)

想了想暂时不作为binder的笔记内容了,binder涉及的内容已经很复杂了,不过看完之后也是收获颇丰哈。

看来很必须要写导读(总结)了,哈哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值