吐血搞定binder机制

前言

  binder机制作为android进程间通信一种非常重要的手段,在android系统中起到重要的作用,在应用层中我们所使用的activity,service等组件都需要和ActivityManagerService通信,包括我们使用的各种系统service,这种跨进程的通信都是通过binder来完成的,java层的代码经过jni调用最终由内核binder驱动进行数据传输,有人把binder比喻成android系统的血管,看不见摸不着但是却非常重要,可以说没有binder我们的app将孤立于android系统甚至无法正常运行,在学习binder机制的过程,整个过程是比较枯燥,需要一点点的耐心,这里要吐槽下android底层代码,用c,c++感觉真的是写的随心所欲,晦涩难懂,但是没有办法再难也得硬着头皮上啊,我不如地狱谁如地狱,之前断断续续看过几次binder,但最终都应该公司事多而放弃,这次也算是利用闲暇时间搞定了这块难啃的骨头。

binder中几个重要角色

  binder机制中会涉及到4个主要角色分别是Client,Service,ServiceManager,Binder内核驱动。binder的设计理念其实是采用C/S的架构,整个请求过程可以非常形象的用网络请求进行描述,Client就是我们的浏览器,Service即服务端,Binder内核驱动就是我们的网络,而ServiceManager的作用就是用来进行域名解析的,整个网站浏览的过程大致如下:在浏览器输入一个具体的网址如http://www/baidu.com,然后域名解析服务会将该网址解析成一个具体的ip地址,然后浏览器会把请求数据发送到该ip对应的服务器,服务器处理完毕后将结果返回给浏览器进行展示。
  在binder请求中,我们要使用某一个系统服务特定的功能,就可以类比成上述的浏览器请求页面数据,有些不同的地方在于Cliengt通过ServiceManager是获取到指定系统服务的binder引用,这个binder引用作用就类似于ip,当Client拿到这个binder引用后就能知道需要将数据发往具体的Service地址,这里有一个问题就是Service如何知道将处理完毕的数据返还给哪个Client,因为可能有多个Client同时对Service发起请求,这里就要分两种情况

(1) Service处理完请求后不需要将数据返回给Client,这个是由TF_ONE_WAY标志位来控制的,当发送的数据带上这个标志位后那么Service在处理完后不会将请求结果返回

(2) Service需要将数据返回给Client,此时TF_ONE_WAY没有被设置,当数据从binder内核传递给Service进行处理时会将Client的thread信息进行保存,具体会放到一个binder_transaction的队列中,当Service处理完请求将结果返回时就可以从binder_transaction中得到返回Client的thread信息。

整个过程可以使用如下图来表示

《吐血搞定binder机制》 QQ截图20181110013049.png

Client为了得到Service地址需要通过getservice向ServiceManger进行查询,而系统Service为了让client知道自己的地址需要通过addService向ServiceManger进行注册,图中之所以使用虚线是因为它们并不是直接与ServiceManger进行交互而是通过binder内核驱动完成addService和getService的整个过程。

ServiceManger的创建过程

  从上图可以知道一点,为了让client得到service地址需要借助servicemanger,而service为了让client知道自己的地址也需要借助servicemanger,那么这个servicemanger就成为了连接client和service的枢纽,了解servcemanger的创建过程就显得尤为重要了。

通过一张图来形象的表述整个过程
《吐血搞定binder机制》 QQ截图20181110014522.png

首先在android系统的启动脚本中已经指定了ServiceManger,所以可以保证系统启动后就会执行到servicemanager的main函数,代码如下

int main(int argc, char **argv)
{
    struct binder_state *bs;
    void *svcmgr = BINDER_SERVICE_MANAGER;
 
    bs = binder_open(128*1024);
 
    if (binder_become_context_manager(bs)) {
        LOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }
 
    svcmgr_handle = svcmgr;
    binder_loop(bs, svcmgr_handler);
    return 0;
}

main函数主要做了三件事,分别为binder_open,binder_become_context_manager,binder_loop。

  binder_open最终会调用到linux内核的binder_open函数,在binder内核中有一个binder_procs的数据结构,主要作用就是用来保存各个进程信息,binder_proc中通过threads成员变量保存该进程中所有的线程信息,当servicemanger调用到binder_open进入到binder内核后会创建一个binder_proc结构体,并最终将该binder_proc挂载到binder_procs上并将新创建的binder_proc设置给设备文件的private_data字段。

binder_become_context_manager这个函数主要就是做了两件事
  首先通过调用ioctl最终进入到binder内核中,通过设备文件的private_data字段得到进程信息binder_pro,然后调用binder_get_thread函数从binder_proc上找到发起binder_become_context_manager调用的binder_thread信息,binder_get_thread的内部逻辑会首先在binder_proc的threads队列上找匹配的binder_thread,此时threads上没有任何binder_thread所以逻辑会进入到创建binder_thread流程中,并将创建的binder_thread保存到threads上,那么下次再调用binder_get_thread的时候就能找到相应的binder_thread了。这是binder_become_context_manager做的第一件事,创建或者找到binder_thread
  第二件事就是通过调用binder_new_node创建一个binder_node实体对象,并将该binder_node设置给binder_context_mgr_node,binder_node中通过proc字段来表示对应的进程信息。这个binder_context_mgr_node非常重要是各个client今后获取各种系统服务都要打交道的一个binder实体对象

  最后一步会执行binder_loop,内部主要逻辑如下

 for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (unsigned) readbuf;
 
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
 
        if (res < 0) {
            LOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
            break;
        }
 
        res = binder_parse(bs, 0, readbuf, bwr.read_consumed, func);
        if (res == 0) {
            LOGE("binder_loop: unexpected reply?!\n");
            break;
        }
        if (res < 0) {
            LOGE("binder_loop: io error %d %s\n", res, strerror(errno));
            break;
        }
    }

它是一个死循环保证servicemanager一直处于监听的状态中,binder_loop中需要重点关注的两个函数分别为ioctl和binder_parse,ioctl是一个非常重要的调用,最终会和binder内核驱动打交道,利用该函数实现数据从用户空间传输到内核空间以及数据从内核空间拷贝回用户空间这两个主要作用,返回的数据会存放在bwr中,所以binder_parse要做的事情就是对传输回来的数据进行解析。

ioctl函数

这个函数最终会调用到binder内核的binder_ioctl,在阅读binder机制源码中会大量看到该函数,因为它是用来实现用户空间和内核空间数据传递的桥梁,忽略一些无关代码该函数内部主要做的两件事就是write和read,重点代码如下

switch (cmd) {
    case BINDER_WRITE_READ: {
        struct binder_write_read bwr;
        if (size != sizeof(struct binder_write_read)) {
            ret = -EINVAL;
            goto err;
        }
        if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
            ret = -EFAULT;
            goto err;
        }
        if (binder_debug_mask & BINDER_DEBUG_READ_WRITE)
            printk(KERN_INFO "binder: %d:%d write %ld at %08lx, read %ld at %08lx\n",
            proc->pid, thread->pid, bwr.write_size, bwr.write_buffer, bwr.read_size, bwr.read_buffer);
        if (bwr.write_size > 0) {
            ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
            if (ret < 0) {
                bwr.read_consumed = 0;
                if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                    ret = -EFAULT;
                goto err;
            }
        }
        if (bwr.read_size > 0) {
            ret = binder_thread_read(proc, thread, (void __user *)bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK);
            if (!list_empty(&proc->todo))
                wake_up_interruptible(&proc->wait);
            if (ret < 0) {
                if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
                    ret = -EFAULT;
                goto err;
            }
        }
        ......
        if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
            ret = -EFAULT;
            goto err;
        }
        break;

从代码可以看出binder_ioctl是先执行write然后才执行read操作的,这部分代码中有两个需要关注的地方,第一可以在代码中发现一对函数copy_from_user和copy_to_user,前者用来将用户空间数据拷贝到内和空间,后者用来将内核空间数据返回给用户空间,binder_ioctl就是通过这对函数实现内核态和用户态的数据传递。

第二个需要留意的就是binder_thread_write和binder_thread_read这两个函数,binder_thread_write在传递数据的时候核心代码如下

binder_thread_write(struct binder_proc *proc, struct binder_thread *thread,
                    void __user *buffer, int size, signed long *consumed)
{
    uint32_t cmd;
    void __user *ptr = buffer + *consumed;
    void __user *end = buffer + size;
 
    while (ptr < end && thread->return_error == BR_OK) {
        if (get_user(cmd, (uint32_t __user *)ptr))
            return -EFAULT;
        ......
        switch (cmd) {
            .....
        case BC_TRANSACTION:
        case BC_REPLY: {
            struct binder_transaction_data tr;
 
            if (copy_from_user(&tr, ptr, sizeof(tr)))
                return -EFAULT;
            ptr += sizeof(tr);
            binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
            break;
        }
        ......
        }
        *consumed = ptr - buffer;
    }
    return 0;
}

BC_TRANSACTION,BC_REPLY都是binder机制中预先设定好的命令用来执行特定的操作,可以在IPCThreadState.cpp的源码中找到对应用户空间和内核空间操作的命令

static const char *kReturnStrings[] = {
    "BR_ERROR",
    "BR_OK",
    "BR_TRANSACTION",
    "BR_REPLY",
    "BR_ACQUIRE_RESULT",
    "BR_DEAD_REPLY",
    "BR_TRANSACTION_COMPLETE",
    "BR_INCREFS",
    "BR_ACQUIRE",
    "BR_RELEASE",
    "BR_DECREFS",
    "BR_ATTEMPT_ACQUIRE",
    "BR_NOOP",
    "BR_SPAWN_LOOPER",
    "BR_FINISHED",
    "BR_DEAD_BINDER",
    "BR_CLEAR_DEATH_NOTIFICATION_DONE",
    "BR_FAILED_REPLY"
};

static const char *kCommandStrings[] = {
    "BC_TRANSACTION",
    "BC_REPLY",
    "BC_ACQUIRE_RESULT",
    "BC_FREE_BUFFER",
    "BC_INCREFS",
    "BC_ACQUIRE",
    "BC_RELEASE",
    "BC_DECREFS",
    "BC_INCREFS_DONE",
    "BC_ACQUIRE_DONE",
    "BC_ATTEMPT_ACQUIRE",
    "BC_REGISTER_LOOPER",
    "BC_ENTER_LOOPER",
    "BC_EXIT_LOOPER",
    "BC_REQUEST_DEATH_NOTIFICATION",
    "BC_CLEAR_DEATH_NOTIFICATION",
    "BC_DEAD_BINDER_DONE"
};

可以看出命令都是成对出现的,在阅读源码的过程中可以发现bc都是用户空间发起的命令,而br都是内核空间发起的命令,TRANSACTION表示用来传输数据而REPLY用来表示响应,除了TRANSACTION,REPLY,binder中还有其他命令如BC_REGISTER_LOOPER ,BR_SPAWN_LOOPER这两个命令都和binder创建线程有关。

现在回到servicemanager调用binder_loop的函数中来,会先判断是否执行binder_thread_write然后判断是否执行binder_thread_read,此时servicemanager没有数据要传输所以不会执行binder_thread_write,最终执行到binder_thread_read,关于为什么不会执行binder_thread_write而却会进入binder_thread_read可以在binder_loop函数中找到答案,核心代码如下

    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;
    
    readbuf[0] = BC_ENTER_LOOPER;
    binder_write(bs, readbuf, sizeof(unsigned));
 
    for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (unsigned) readbuf;
 
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
        ......
     }

可以看到bwr.write_size为0而 bwr.read_size不为0,binder_thread_read主要有两个作用,第一个作用就是判断todo队列上是否有数据,有的话取出返回给用户空间,否则会处于阻塞状态,总体逻辑就是首先判断binder_thread或者binder_proc的todo队列上是否有数据,没有数据就进入到阻塞状态,直到被service端在todo队列上插入数据被唤醒,唤醒后的binder_thread_read会将todo队列上的数据取出进行处理,最终将处理完毕的数据通过copy_to_user函数返回给用户空间,对应的流程图如下所示
《吐血搞定binder机制》 QQ截图20181112125030.png

第二个作用就是和binder线程池有关,默认情况下app启动时,如果app进程还不存在则zygote进程会fork出一个新进程,在这个新进程中做了两件很重要的事情,第一就是开启一个binder线程并让它进入到循环等待监听状态,有数据则处理没有数据就阻塞,它的原理有点类似于handler中的messagequeue消息队列,但是app进程中默认的binder线程池中只有一个binder线程,如果该app需要经常和其他app进行进程间通信一个binder线程来不及处理怎么办,所以binder_thread_read的第二个作用就是在binder线程不够用的时候去通知开启binder线程,在binder_proc结构体中通过

    int max_threads;                
    int requested_threads;          
    int requested_threads_started;
    int ready_threads;

这四个成员变量来控制是否需要开启binder线程,max_threads表示一个进程所能开启的最大binder线程数量,requested_threads表示请求发出的请求开启binder线程次数,requested_threads_started表示已经开启的binder线程个数,ready_threads表示空闲可利用的binder线程个数。这里简单列出和开启binder线程相关的代码

binder_thread_read(......){
    wait_for_proc_work = thread->transaction_stack == NULL &&
        list_empty(&thread->todo);
    if (wait_for_proc_work)
        proc->ready_threads++; 
        ......
        //todo队列上没有可执行任务则阻塞在这里
        ......
    if (wait_for_proc_work)
        proc->ready_threads--; 
    thread->looper &= ~BINDER_LOOPER_STATE_WAITING;
    ......   
    if (proc->requested_threads + proc->ready_threads == 0 &&
        proc->requested_threads_started < proc->max_threads &&
        (thread->looper & (BINDER_LOOPER_STATE_REGISTERED |
         BINDER_LOOPER_STATE_ENTERED))) 
        proc->requested_threads++;
        put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer);
    }
    return 0;
}

当有空闲线程可用的时候proc->ready_threads++,然后中间有一段判断todo队列上是否有可执行任务的代码,没有任务则阻塞住,从阻塞状态恢复后会执行proc->ready_threads–表示这个binder线程要被进行使用了,可以看到代码的最后有一段逻辑用来判断是否需要新开一个binder线程,当proc->requested_threads和proc->ready_threads均为0并且已启动的binder线程小于允许的最大值,thread->looper状态合法的情况下执行新建binder线程操作,主要涉及到的代码如图所示
《吐血搞定binder机制》 QQ截图20181112161921.png

注意流程图中调用spawnthredpool中传入的参数为false,也就是ismain为false,所以mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);写入的命令为BC_REGISTER_LOOPER,talkwithdriver函数内部主要简单概括起来就是它会操作mout和min,每个binder线程都具备这两个变量,mout用来将用户空间数据传递给内核空间,而min负责从内核空间读取数据到用户空间,所以talkwithdriver内部逻辑就是调用ioctl将mout中数据传输到内核空间,待内核处理完毕后从min中将数据读取回去,talkwithdriver关键代码如上图所示,从上述流程图可以发现第一步中proc->requested_threads++;而在最后一步开启binder完毕后执行了proc->requested_threads–;

回到binder_thread_read函数中来,经过上述介绍就应该明白它的两个作用了,servicemanager调用binder_loop最终进入到binder_thread_read,此时todo队列没有数据所以会直接进入到阻塞状态,到此servicemanager的启动流程就完整了,它会一直等待各个系统service启动并响应系统service发起的addservice调用。

系统service注册到servicemanager

拿MediaPlayerService举例,MediaPlayerService可以说已经成了分析binder的文章中的典型service,这里详细代码不想贴了实在太多了,想看详细代码的话可以在androidxref上查找,这里就贴出关键部分的代码,首先看下MediaPlayerService的main函数

    sp<ProcessState> proc(ProcessState::self());
    sp<IServiceManager> sm = defaultServiceManager();
    ......
    MediaPlayerService::instantiate();
    ......
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();

sp<ProcessState> proc(ProcessState::self());的作用概括起来比较简单,主要做的binder_open操作,这个操作已经在servicemanager启动中已经说过,主要作用就是打开binder内核设备文件和binder内核建立起联系,并在binder内核中创建一个binder_proc。

servicemanager已经启动起来后defaultServiceManager的作用就是拿到servicemanager的远程代理binder,该binder在源码中即为BpBinder(0),这里的0就表示servicemanager的binder引用值,该值固定为0,也就是说任何的系统service都可以通过BpBinder(0)就能知道servicemanager的地址,如果还记得上面servicemanager启动分析我就说过binder内核中使用binder_context_mgr_node来表示servicemanger的binder实体,只要内核发现binder引用值为0就会把数据发往binder_context_mgr_node指定的线程上。

MediaPlayerService::instantiate();内部会调用BpServiceManager的addservice方法,将MediaPlayerService注册到servicemanager上,这里就有几个地方需要注意下
(1)这里有一个调用链BpServiceManager.addService–》bpbinder.transact–》ipcthreadstate.transact,也就是说bpbinder之所以能把数据从用户空间传输到内核空间是通过ipcthreadstate来实现的,看一下ipcthreadstate的transact方法

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
   ......
    if (err == NO_ERROR) {
        LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(),
            (flags & TF_ONE_WAY) == 0 ? "READ REPLY" : "ONE WAY");
        err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    }
    
   ......
    
    if ((flags & TF_ONE_WAY) == 0) {
       ......
            err = waitForResponse(reply)
        ......
        }
    } else {
        err = waitForResponse(NULL, NULL);
    }
    
    return err;
}

已经去掉了很多无关代码,剩下的代码其中的关键部分,writeTransactionData内部逻辑会把需要传输的数据放到mout中,此时还并没有真正将数据给传递到内核中只是放在了mout中,然后回到transact执行waitForResponse函数,这里才会真正把数据给传递到binder内核,waitForResponse函数内部逻辑简化下就如下所示

 while (1) {
        if ((err=talkWithDriver()) < NO_ERROR) break;
        err = mIn.errorCheck();
        if (err < NO_ERROR) break;
        if (mIn.dataAvail() == 0) continue;
        
        cmd = mIn.readInt32();
       ......
        switch (cmd) {
              case BR_TRANSACTION_COMPLETE:
            if (!reply && !acquireResult) goto finish;
            break;
              其他解析cmd命令逻辑
        }
}

核心就在于talkWithDriver函数,该函数在servicemanager中已经介绍,内部又会调用ioctl完成数据的真正传输工作然后阻塞在read函数上,这样waitForResponse函数就介绍完毕了,回到transact中来,其实还有一个标志位非常值得注意就是TF_ONE_WAY,binder机制采用C/S的架构设计当C端发送数据后是否需要等待S的回复就是通过TF_ONE_WAY这个标志来控制的,当没有设置TF_ONE_WAY时会直接调用err = waitForResponse(NULL, NULL);注意传进去的两个参数都是null,在waitForResponse内部会发现

 switch (cmd) {
              case BR_TRANSACTION_COMPLETE:
            if (!reply && !acquireResult) goto finish;

直接会跳出while循环,BR_TRANSACTION_COMPLETE实际上在发送完数据后C端会在binder内核中写回来,这样关于TF_ONE_WAY的作用也就清楚了。到此 MediaPlayerService::instantiate();的内部逻辑也就分析清楚了,还有疑问的地方就在于ioctl在传输数据时到底做了哪些需要留意的操作。

ioctl在传输数据的时候依靠binder_thread_wirte来完成,而binder_thread_wirte中的核心函数就是binder_transaction,binder_transaction在调用bpservicemanager.addService的过程中它的作用就是找到目标进程即Servicemanager进程,并将数据存放到对应的进程的todo队列中,然后唤醒servicemanager进程,C端执行完唤醒代码后会最终回到waitForResponse函数,又开始新的一轮talkwithdriver 如此反复,不过在收到BR_REPLY命令的时候会跳出循环,否则就像上面说的one_way标志位也是可以跳出循环,不过这里由于不满足这两者所以又会进入新的一轮循环并最终阻塞在binder_thread_read上(实际上C端在binder内核发送完数据后会向用户空间发回一些BR命令waitForResponse会循环读取所有命令直到min中没有数据才回进入到binder_thread_read阻塞)。

这时由于已经唤醒Servicemanager,代码会从binder_thread_read中唤醒继续执行,接下来要做的事就是把从C端传过来的数据最终通过
copy_to_user传递给S端的用户空间,然后binder_thread_read还做了另外一个重要的事情就是

if (t->buffer->target_node) {
            ......
            cmd = BR_TRANSACTION;
} 
......
if (cmd == BR_TRANSACTION && !(t->flags & TF_ONE_WAY)) {
            t->to_parent = thread->transaction_stack;
            t->to_thread = thread;
            thread->transaction_stack = t;
        }

将原来的BC_TRANSACTION改成BR_TRANSACTION,并最终执行thread->transaction_stack = t将t挂载到了transaction_stack上,t中就包含了很多重要信息其中就存有C端进程信息,这也是为什么S端在处理完addservice逻辑后能将结果返回给C端的原因。最终经过层层调用会执行到binder_parse,该函数最终会完成addservice操作后通过sendreply将处理结果发回给C端,比较有意思的就是此时的C端严格来说已经变成S端了,而原来的S端在执行sendreply时就已经变成C端了。

这里需要注意一点的就是binder实体向binder引用的转换,C端向S端发起addservice注册时传递进来的binder是一个实体binder,而最终servicemanager在保存该binder信息的时候已经被转换成了binder引用,这个转换过程实际上是在binder_transaction中完成的

if (fp->type == BINDER_TYPE_BINDER)
                fp->type = BINDER_TYPE_HANDLE;
            else
                fp->type = BINDER_TYPE_WEAK_HANDLE;
            fp->handle = ref->desc;

fp就是从C端读取到的binder数据,ref就是binder内核中生成的binder引用,因此最终传递到S端时已经是binder引用数据了,servicemanager保存的所有系统service的binder信息都是引用,包括C端通过调用getservice获取到的service,其实就是一个binder引用,最终在C端通过自行包装成bpbinder而已。

经过上述分析后service向servicemanager注册的整个过程也就大致清楚了。

client端调用getservice

client调用getService的代码可以简化为如下代码

sp<IServiceManager> sm = defaultServiceManager();
 binder = sm->getService(“xxx”);

defaultServiceManager函数上面已经介绍过了,就不再说明,只要关心getService就可以了,getService中代码类似如下所示

                Parcel data, reply;
        data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
        data.writeString16(name);
        remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply);
        return reply.readStrongBinder();

这个过程和service向servicemanager注册非常类似,只不过addservice传入的是一个binder实体对象,而这里传入的是一个service名字,接下来的过程就和service向servicemanager注册流程差不多,不同之处在于当servicemanager在svclist中找到对应名字的服务后将该服务的binder引用值传递过来,在binder内核中会根据该binder引用值在发起getservice的binder_proc上创建一个binder_ref对象,随后就可以依据该binder引用值查找到该binder_ref对象,这里需要注意一点的就是虽然servicemanager返回了一个binder引用值给binder内核,但是最终client的binder_proc上所保存的binder引用值是一个新值,也就是说client上的binder引用值和servicemanager保存的binder引用值可以是不一样的,但是它们所指向的binder实体却可以是同一个对象,这部分关键代码如下所示

switch (fp->type) {
        ......
        case BINDER_TYPE_HANDLE:
        case BINDER_TYPE_WEAK_HANDLE: {
            struct binder_ref *ref = binder_get_ref(proc, fp->handle);
            if (ref == NULL) {
                ......
                return_error = BR_FAILED_REPLY;
                goto err_binder_get_ref_failed;
            }
            if (ref->node->proc == target_proc) {
                ......
            } else {
                struct binder_ref *new_ref;
                new_ref = binder_get_ref_for_node(target_proc, ref->node);
                if (new_ref == NULL) {
                    return_error = BR_FAILED_REPLY;
                    goto err_binder_get_ref_for_node_failed;
                }
                fp->handle = new_ref->desc;
                binder_inc_ref(new_ref, fp->type == BINDER_TYPE_HANDLE, NULL);
                ......
            }

servicemanager在将getservice查询信息传回来后最终client端会收到一个BR_REPLY命令,waitForResponse在处理完该命令后最终会跳出循环,从而最终执行到 reply.readStrongBinder();,该函数最终会创建一个bpbinder对象,里面保存的引用值就是内核中为client端创建的binder引用值。当client得到这个bpbinder之后就可以去获取到该service上的服务了,而这个过程又和service向servicemanger注册非常类似,不同的是service向servicemanger注册可以通过binder引用值0直接找到servicemanger的进程,而client发起服务调用是通过bpbinder上所保存的binder引用值来找到service所在的进程。还有一个不一样的地方在于当servicemanager在收到C端的请求后最终会通过一个函数指针来完成逻辑处理,而client端去请求其他系统service时是通过tr->cookie来找到对应的binder实体,从而调用transact方法,而transact方法内部会调用到ontransact,一些系统service已经重写过该方法因此会进入到内部进行相应逻辑处理。

binder机制中C端和S端经常可以互相转换,举个简单的例子,C端向一个CalService发起计算请求,当CalServi处理完毕后需要将结果会传给C端,如何回传回来,前面我们说过transaction_stack会保存C端的进程信息从而知道C端在哪里,但这是一个同步操作,必须等到S端处理完毕才回调用,如果S端处理比较耗时将会导致C端一直等待中,所以更好的办法就是当C端发起请求时随便带上一个binder对象,该binder对象就是用来处理S端的返回结果的,这样就由原来的同步请求变成了异步请求方式,并且可以做成C端和S端双向通信。典型的例子就是ams发起activity启动,所有的activity生命周期方法调用都是ams发起,而app进程在调用完每一个activity生命周期方法后就是通过applicationthread来将结果反馈回ams。

结尾

到此在native层上关于binder机制中所涉及到的servicemanager注册过程,service注册过程,client获取系统服务过程就大概全部讲完了,这其中涉及到的细节问题很多,包括很多数据结构体都没在文章列出来,文章只是自己在阅读源码时的一个心得总结,不可能面面俱到把所有细节都给讲明白,如果对于有些地方不明白的最好自己亲自去看看源码,本来不想写这篇文章因为太花时间了,但是最终强迫症逼迫自己还是写完了,也算是自己对于binder机制的一个总结,很早之前也尝试看过binder机制源码,但是最终都是静不下心而不了了之,这次也算是让自己对于这块知识有了一个较好的总结,看binder机制源码就如老罗所说的就是个reading the fucking source code的过程,但是这个过程又避免不了,唯一可以走的捷径就是多去看看前人关于这块知识的文章,吸取其中的精华部分并转化成自己的理解,收工下班回家~~~~

    原文作者:mandypig
    原文地址: https://www.jianshu.com/p/8fdc82cced8d
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞