Binder总结分析_native层

作者: wo883721 | 来源:发表于2017-12-10 14:53 被阅读87次

    上一篇文章:
    Binder总结分析_应用层

    Binder架构在native层到底是如何实现的。本文是基于Gityuan大神的Binder系列—开篇系列文章简单总结的,如果想了解更多细节,请前往Gityuan大神博客。

    我们就从添加服务和获取服务为例。

    添加服务

      // 如添加一个MediaPlayerService服务
      defaultServiceManager()->addService(String16("media.player"), new MediaPlayerService());
    

    defaultServiceManager

    首先通过defaultServiceManager方法获取ServiceManager代理IBinder对象,从源码分析看来它是一个BpServiceManager实例。

    IBinder对象有两个重要子类:

    1. BBinder :这个类的子类就是Binder实体(比如说这里的MediaPlayerService类)。
    2. BpBinder:这个类的子类是Binder代理类(比如这里的BpServiceManager),它有个最重要的属性mHander,用它来找到对应的服务端。

    和应用层相似,BpServiceManager实现接口方法的作用就是将参数存放到Parcel中,然后调用BpBinder的transact方法,进行Binder请求。

    这里有个重要的问题,就是这个MediaPlayerService该怎么传递。它是一个Binder实体对象,应该只能在当前进程中,别的进程应该只能有它的代理对象BpBinder。

    先介绍一个重要的结构体flat_binder_object,当Binder驱动进程间数据传递的时候,用它来指代IBinder对象。

    1. 如果IBinder是一个BpBinder对象,那么它的type就是BINDER_TYPE_HANDLE或者BINDER_TYPE_WEAK_HANDLE,binder和cookie都为0。handle值就是BpBinder的handle值。(至于这个handle值哪里产生的,就要看Binder.cpp的binder_transaction方法了)
    2. 如果IBinder是一个BBinder对象,那么它的type就是BINDER_TYPE_BINDER或者BINDER_TYPE_WEAK_BINDER。cookie就指向BBinder的指针。
      通过parcel.cpp的writeStrongBinder方法将IBinder对象转成一个flat_binder_object对象。

    在BpBinder的transact方法中,它会调用IPCThreadState的transact,并将自己的mHandle属性也当参数传递过去。

    IPCThreadState

    在IPCThreadState的transact方法中,会调用两个重要方法:

    1. writeTransactionData方法:这个方法会将传递来的参数组装成一个binder_transaction_data结构体对象,然后将cmd(BC_TRANSACTION)和这个结构体对象tr都写入IPCThreadState的属性mOut中。
    2. waitForResponse方法:这个方法重要调用了talkWithDriver方法,与Binder驱动进行数据交互,并使用一个while (1)死循环,一直等待Binder驱动响应。

    binder_transaction_data这个结构体对象很重要,它拥有的属性:

    1. target是一个共同体 通过它来找到目标Server进程的binder_proc,然后才能进行数据交互。1) target.handle:用它可以找到对应的binder_ref,通过它找到目标的binder_node,最后找到目标进程binder_proc。 2) target.ptr:binder_node的指针。
    2. code:就是Client端与Server端双方约定的命令码,让Server端调用对应的方法处理数据。
    3. sender_pid和sender_euid:发送端进程的pid和发送端进程的uid。我们通过 target找到目标Server端,那么从Server端返回数据的时候,怎么找到原来的Client端,就是通过这个。
    4. data_size和data.ptr.buffer: 一个表示Parcel中数据的大小,一个是Parcel中数据对应的指针。
    5. offsets_size和data.ptr.offsets: data.ptr.buffer不是已经代表整个Parcel数据了么,为什么还要这两个变量呢?还记得我们在Parcel中写入了一种特殊结构体flat_binder_object,那怎么从Parcel中获取它呢?就是通过这个两个变量。offsets_size表示Parcel中flat_binder_object个数,data.ptr.offsets表示Parcel中flat_binder_object对应的偏移量。

    Binder 驱动

    IPCThreadState的talkWithDriver这个方法,就是真的与Binder驱动进行数据交互了。创建binder_write_read结构体对象bwr,并调用ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)方法,与Binder驱动进行数据交互。

    既然与Binder驱动进行数据交互,就涉及到两个操作读和写,写就是Client端将数据体递给Binder驱动,读就是获取Binder驱动响应回来的结果值。所以binder_write_read结构体有两个重要指针。

    1. write_buffer指向传递的数据对象,这里指向了mOut.data(),即我们刚刚通过writeTransactionData方法写入的数据。
    2. read_buffer指向接收数据的对象,这里是mIn.data()。在这个talkWithDriver方法调用完成之后,在waitForResponse会方法中会读取mIn里面的值,如果Binder驱动有响应,那么这里就有值。

    下面就进入binder.c中的ioctl方法,它根据传递的cmd(BINDER_WRITE_READ),调用binder_ioctl_write_read方法。

    1. 首先将用户空间bwr结构体拷贝到内核空间中,但是注意一下,因为bwr结构体含有两个指针,指针肯定只拷贝引用了,所以当正式用到这个指针数据时,还要进行对应拷贝。
    2. 然后查看bwr结构体的write_buffer和read_buffer中有没有数据,分别调用对应方法。在这里write_buffer中有数据,那么调用binder_thread_write方法。
    binder_thread_write

    在binder_thread_write方法中,首先要从write_buffer中获取cmd,这个cmd是我们在IPCThreadState的writeTransactionData方法中,写入到mOut里的,而write_buffer又是mOut数据的指针。得到cmd是BC_TRANSACTION,然后再将write_buffer中的binder_transaction_data赋值到内核空间,最后调用binder_transaction方法。

    注意,binder_transaction_data是通过指针指向数据所在地址,所以这里的拷贝,仍然没有将真实的数据拷贝到内核,即在addService方法存放在Pacel中的数据,Pacel数据是在binder_transaction方法中,才拷贝到内核的。也就是说只进行一次拷贝。

    binder_transaction

    binder_transaction这个方法非常重要,进程间的数据传输就是发生在这里。

    1. 获取target_proc 、target_thread、target_node、target_list等对象。

    根据reply值不同(分BC_TRANSACTION和BC_REPLY两种不同情况),这里获取这些值得方式不同.如果是BC_TRANSACTION,那么就根据 tr->target.handle找到对应binder_ref,然后可以确定target_node,进而找到target_proc。

    1. 创建binder_transaction结构体对象,它用于Binder驱动进程间相互通信的。
    2. 向目标进程target_proc->todo(即target_list)中添加BINDER_WORK_TRANSACTION事务,接下来进入目标Server端进程。
    注意

    这里还有一个非常重要的过程,拷贝用户空间的binder_transaction_data中ptr.buffer和ptr.offsets到目标进程的binder_buffer(Pacel中存放的数据),并对包含的flat_binder_object数据进行特殊处理。先以添加服务为例,获取服务处理下面再说。

    1. 先循环遍历binder_transaction_data中所有的flat_binder_object对象。
    2. 因为是添加服务,那么它的type是BINDER_TYPE_BINDER。对于一个Binder实体,就要实现Binder实体只存在本进程中,其他进程只是它的引用。那么怎么做到呢? 就要说两个关键的结构体,binder_node和binder_ref。
    3. binder_node它代表一个Binder实体,它有个proc属性指向所在的进程binder_proc ,而在进程binder_proc中有一个nodes属性,它存储了本进程所拥有的Binder实体。所以我们添加服务时,先从本进程binder_proc的nodes查找有没有用对应的binder_node,没有就创建,然后再添加到nodes中。
    4. binder_ref它代表别的进程对binder_node(Binder实体)的引用,它有个node属性指向引用的binder_node。在binder_proc中有refs_by_node和refs_by_desc两个属性。这里用了很巧妙方法,当获取一个binder_node,将它传递给目标进程target_proc,从目标进程target_proc的refs_by_node中查找,如果找到就返回这个binder_ref。如果没有找到,就创建一个binder_ref,将它的node指向binder_node,添加到refs_by_node中,然后在设置desc属性,从1开始递增(这个desc就是handle),再添加到refs_by_desc中。
    5. 最后改变flat_binder_object的值,type改成BINDER_TYPE_HANDLE,handle设置成 ref->desc,再将它的binder和cookie清除,那么Server端接受的就是一个Binder引用了,通过这个handle可以从Server端进程的refs_by_desc查找到binder_ref然后就获取到binder_node(Binder实体)。

    当binder_transaction中给目标进程添加任务后,就会触发目标进程的binder_thread_read方法,这里的目标进程是ServiceManager,最终会调用到真正处理方法,在service_manager.c中的svcmgr_handler方法。

    这个方法中,读取我们addService方法中传递的参数,但是这里注意一下,它的flat_binder_object中数据已经被我们修改了,就剩下一个有用值handle。而service_manager内部有一个链表,会记录添加服务的name和handle值,查找服务的时候就是根据服务名,返回对应的handle值。

    最后会调用binder_send_reply方法,返回响应的结果值。

    获取服务

    就是要获取对应IBinder代理对象,前面的操作流程和添加过程都是一样的,就是在binder_transaction中,因为获取服务是没有传递Binder的,所以不用做flat_binder_object的处理。
    然后调用到service_manager.c中的svcmgr_handler方法,根据服务名,得到对应的handle值,用它构建一个flat_binder_object对象,写到返回值中,然后调用binder_send_reply方法,这个方法会也会调用ioctl方法,进行Server端和Binder的数据交互。最终还是会调用到binder_transaction方法。
    那么在响应时,对flat_binder_object数据是怎么处理的呢?

    1. 这个时候flat_binder_object的类型是BINDER_TYPE_HANDLE。先通过这个handle值从本进程中查找到对应的binder_ref,然后得到Binder实体binder_node。
    2. 如果binder_node的proc和target_proc是相同的,那代表请求服务的进程就是服务实体所在进程,那么就直接使用Binder实体,而不用它的代理。所以将 fp->type = BINDER_TYPE_BINDER, fp->binder = ref->node->ptr; fp->cookie = ref->node->cookie;这样flat_binder_object就存储了Binder实体。
    3. 如果binder_node的proc和target_proc是不同,那么就要用这个binder_node在进程target_proc中查找对应的binder_ref,得到新的handle值,然后覆盖flat_binder_object原有的handle值。
    4. 为什么这里要获取新的handle值呢?那要了解handle的作用,通过handle可以从本进程的binder_proc中查找到对应的binder_ref,就可以得到binder_node(binder实体)。那么就可以知道原来的handle,它只能从server进程的binder_proc查到binder_ref,而对我们的目标进程binder_proc来说是无意义的,所以我们要通过binder_node在目标进程binder_proc中获取对应binder_ref,然后将它的desc赋值给fp->handle。

    最后这个值会返回给Client端,在Parcel.cpp放中readStrongBinder获取。

    这个方法作用和writeStrongBinder正好相反,它是将flat_binder_object对象转成IBinder对象。

    1. 如果flat->type是BINDER_TYPE_BINDER,那么它的flat->cookie就是一个Binder实体。
    2. 如果flat->type是BINDER_TYPE_HANDLE,那么用它的flat->handle通过getStrongProxyForHandle方法,得到一个BpBinder(handle)对象。

    相关文章

      网友评论

        本文标题:Binder总结分析_native层

        本文链接:https://www.haomeiwen.com/subject/qclpixtx.html