珍惜作者劳动成果,如需转载,请注明出处。
http://blog.csdn.net/zhengzechuan91/article/details/50577631

Android使用Binder作为进程间通信的工具,基本遍布在Android系统的各个角落,其重要性不言而喻。我们理解了Binder的原理,对我们理解Android系统原理有非常重要的意义。Binder底层代码及其复杂,我们如果没有对Binder有一个系统的理解,那么我们很可能会迷失在源码中,半途放弃。所以这篇文章我会尽量少的分析源码,而更多的是从原理角度来分析。如果搞明白了Binder实现的原理,那么再去深入的研究源码,就会更加游刃有余。

Binder概述

Binder通信过程中涉及到Client、Server、ServiceManager、Binder驱动,前三者运行在用户空间,而Binder驱动运行在内核空间,ServiceManager是在系统启动后就开始运行起来的,负责统一管理各式各样的Server服务,每个Server服务首先将自己注册到ServiceManager,Client根据服务名称去ServiceManager查找Server,ServiceManager会将此服务的代理Binder(BpBinder)告诉Client,Client再将代理Binder包装成代理Server服务(BpXxxService),Client就可以使用这个代理Server服务调用Server端的服务,就跟调用Server本身一样。

经典的Binder通信代码

ServiceManager

int main(int argc, char **argv){    struct binder_state *bs;    void *svcmgr = BINDER_SERVICE_MANAGER;    bs = binder_open(128*1024);    binder_become_context_manager(bs);    svcmgr_handle = svcmgr;    binder_loop(bs, svcmgr_handler);}

Server

int main(int argc, char** argv){    sp<ProcessState> proc(ProcessState::self());    sp<IServiceManager> sm = defaultServiceManager();    MediaPlayerService::instantiate();    ProcessState::self()->startThreadPool();    IPCThreadState::self()->joinThreadPool();}

Client

int main(int argc, char **argv) {    sp<IServiceManager> sm = defaultServiceManager();     sp<IBinder> binder;      binder = sm->getService(String16("media.player"));      sMediaPlayerService =         interface_cast<IMediaPlayerService>(binder);  }

数据结构

在开始学习Binder通信之前,我们先对会使用到的结构体有个简单的了解,不用马上全部记住,只需要在分析时用到了再回头来看:

【struct binder_proc】 执行mmap后的进程在内核空间对应的数据结构,里面保存了用户空间地址、内核空间地址、内核缓冲区buffer、物理页面、Binder线程池、Binder实体对象红黑树、Binder引用对象红黑树等,这是最重要的结构体。

【struct binder_node】:内核空间的Binder实体对象,主要保存了Binder引用队列列表、指向Server组件的指针、待处理的工作项。

【struct binder_ref】:内核空间的Binder引用对象,主要保存了Binder引用对象的句柄、Binder实体对象指针。

【struct binder_buffer】:内核空间的内核缓冲区最终会被切分成一块一块的binder_buffer,有效数据保存在内部变量data中。

【struct binder_thread】:处理Binder通信的线程池,里面保存了等待队列、事务堆栈。

【struct binder_transaction】:Binder线程处理请求都是以事务为单位的,主要保存发起事务线程、处理事务线程和进程、Binder驱动为事务分配的内核缓冲区。

【struct binder_write_read】:进程与Binder驱动交互时的数据结构。通过ioctl()向Binder驱动发送命令协议(BC)保存在write_buffer,接收Binder驱动返回协议(BR)的结果保存在read_buffer。

【struct binder_transaction_data】:进程与Binder驱动通信时会将Parcel转换为binder_transaction_data,其中的动态缓冲区中buffer保存通信数据,offsets保存Binder对象flat_binder_object的位置。

【struct flat_binder_object】:数据缓冲区中的Binder对象,Binder实体对象时保存指向Server组件的地址,Binder引用对象时保存对象的句柄。

Binder通信过程

ServiceManager的启动过程

  1. ServiceManager在系统一启动在init.rc就运行起来;
  2. binder_open()打开Binder虚拟设备,并且为当前进程创建binder_proc结构体,Bindder驱动为ServiceMamager在内核空间分配了128K的内核缓冲区,并且是只读的,只能由内核去修改;
  3. 通过io命令BINDER_SET_CONTEXT_MGR将自己注册为Binder驱动的”大管家”,并且创建一个binder_thread作为主线程,还会为ServiceManager的Binder本地对象创建一个Binder实体对象,这些都是在Binder驱动中完成的;
  4. 执行bind_loop()首先使用io命令BINDER_WRITE_READ将当前线程在Binder驱动中注册为Binder线程,然后返回到用户控件。再执行死循环,使用BINDER_WRITE_READ命令来从Binder驱动中取出Client发来的消息:如果没有消息,则当前线程睡眠在Binder驱动程序的binder_thread_read()函数中,否则通过binder_parse()来解析消息,而这个方法处理了从Binder驱动返回的所有返回协议(BR),而重点是在BR_TRANSACTION返回协议的处理上,svcmgr_handler()中处理具体的请求类型,我们通过ServiceManager无非就是想查找server服务、添加server服务、遍历server服务,而这些功能都是通过内部维护的svclist列表来实现的。最后再将查找或添加的结果通过binder_send_reply()通知到Binder驱动,这样,BR_TRANSACTION返回协议才处理完毕。

注:前面Binder驱动在解析BINDER_WRITE_READ命令时会判断,如果写缓冲区bwr.write_buffer不为空,则从进程向Binder驱动写命令协议(BC),通过copy_from_user()将用户空间的结构体binder_write_read拷贝到内核空间,若读缓冲区bwr.read_buffer不为空,则从Binder驱动向进程写返回协议(BR),通过copy_to_user()将内核空间的结构体拷贝到用户空间。

Server/ServiceManager代理对象的获取

在学习ServiceManager代理对象的获取前,我们先想一下为什么要使用代理,我们在学习代理模式的时候知道,使用代理就是为了对调用者屏蔽真实对象的细节,以免发生耦合,这里也是一样,Binder驱动在和ServiceManager交互时,无需知道ServiceManager内部的构造和具体实现,只需和IServiceManager接口交互即可。

我们根据上面Client部分的代码来分析下:首先defaultServiceManager()是个单例,返回的是new BpServiceManager(new BpBinder(0)),全局中只要调用此方法返回的都是这个对象。然后通过BpServiceManager的getService()来获取此Server服务的Binder代理对象,内部是通过BpBinder(0)的transact()方法与Binder驱动通信来从ServiceManager中查找的。再通过interface_cast()将Server服务的Binder代理对象包装成BpXxxService(),此时代理Server就可以像使用Server服务一样,因为它们都实现了IXxxService接口。

Client用来与Binder通信的从下往上分别为ProcessState/IPCThreadState -> BpBinder -> BpServiceManager/BpXxxService,越往上封装的越抽象。BpBinder的transact()用来向驱动发送命令,实际调用的是IPCThreadState的transact()方法。

从上面我们清晰的看出,每一次代理对象方法的调用,都涉及到一次Binder通信。因为代理对象方法分调用本质上是调用了BpBinder的transact()方法向Binder驱动发送命令,而其实际上又调用了IPCThreadState的transact()方法,该方法内部则与Binder驱动进行了通信。

Server进程的启动过程


1. ProcessState::self()是单例模式,第一次调用会打开Binder设备,并且Binder驱动会给Server进程分配(1M-4k * 2)大小的内核缓冲区;
2. 通过defaultServiceManager()获取到ServiceManager的代理对象。
3. 初始化Server服务,就是创建一个Server本地对象,然后通过ServiceManager代理注册到ServiceManager里面,看似简单,实际上这个过程比较复杂。
(1) 使用代理注册时首先是将binder对象使用flat_binder_object结构体写入到Parcel对象中,Parcel格式有两个缓冲区,mData里内容可能是整数、字符串或flat_binder_object,而mObjects保存的是每个flat_binder_object的位置,而且两个缓冲区也会根据需要自动扩展空间。
(2) 通过Binder代理对象的transaction()发送BC_TRANSACTION命令协议,由于BC_TRANSACTION是通过io命令BINDER_WRITE_READ发送到Binder驱动的,所以要将Parcel对象转化为binder_transaction_data结构体,然后通过writeTransactionData()将此结构体写入到IPCThreadState的命令协议缓冲区mOut中,然后waitForResponse()里面执行一个死循环,调用talkWithDriver()将mOut转换为binder_write_read发送到Binder驱动,另外还会从返回协议缓冲区mIn中读取返回值。
(3) Binder驱动调用binder_transaction()处理命令请求,根据句柄值找到Binder引用对象,再通过引用对象找到实体对象,然后在目标进程中找到一个等待其它事务但暂时空闲的线程去处理BR_TRANSACTION返回协议,是通过将binder_transaction结构体封装成BINDER_WORK_TRANSACTION工作项加入到目标todo队列,这时候我们需要将源进程中传递的binder_transaction_data中的数据缓冲区拷贝到binder_transaction的数据缓冲区中,然后取出其中的binder对象,在Binder驱动中目标进程创建Binder实体对象和引用对象,并为引用对象分配句柄值。然后通知源进程之前发送的BC_TRANSACTION已经收到,将binder_work结构体封装成BINDER_TRANSACTION_COMPLETE添加到源线程的todo队列中,以便源线程在返回用户空间之前可以处理这个工作项,最后BR_TRANSACTION返回协议就写入到了IPCThreadState的返回协议缓冲区mIn。
(4)如果是同步请求,则工作项会添加到todo队列中,而如果是异步请求,则会添加到目标Binder实体对象的async_todo队列中,为了节省资源,异步请求每次最多只能执行一个工作项。如果是同步请求,则目标wait队列会被唤醒,继续执行binder_thread_read(),从todo队列中去取BINDER_WORK_TRANSATION工作项,从这个工作项里取出binder_transaction,处理这个工作项的方式是向目标线程发送一个BR_TRANSACTION返回协议,这个协议里包装了binder_transaction_data,这里将binder_transaction里的通信数据拷贝到binder_transaction_data,实际上只是将binder_transaction_data的指针指向了binder_transaction,这些缓冲区都是在内核空间,然后将BR_TRANSACTION返回协议拷贝到目标线程提供的用户空间的数据缓冲区中,这时候ServiceManager在Binder驱动的主线程就会在binder_thread_read被唤醒,返回到用户空间后,就会在binder_parse()中解析BR_TRANSACTION返回协议并将Service添加到svclist中,再通过binder_send_reply()通知Binder驱动返回结果,实际上是发送了BC_REPLY协议,Binder驱动处理完这个协议后,会在请求注册service到servicemanager的线程的todo队列z中添加一个BINDER_WORK_TRANSACTION的工作项,这时候此线程在binder_thread_read()被唤醒,处理这个工作项,然后binder_transaction中返回的目标实体对象是空的,线程将binder_transaction中的通信数据转换为binder_transaction_data,然后包装在BR_REPLY返回协议中拷贝到thread提供的用户空间缓冲区,然后线程切换到用户空间后,就可以在IPCThreadState的waitForResponse()中处理BR_REPLY返回协议了。
4. 开启Binder线程池首先创建主线程并把主线程加入到Binder线程池里,这是通过向Binder驱动发送BC_ENTER_LOOPER命令协议实现的,如果不是主线程,则发送BC_REGISTER_LOOPER命令协议。

Binder通信的本质

Binder通信的本质有两点:

  1. Binder驱动中目标线程代替源线程执行任务,源线程在源进程里,而目标线程在目标进程里,源线程要执行任务,只需将其包装成事务,放在目标线程的todo队列中,目标线程就会代替源线程处理这个事务,而且Binder线程会在用户空间和内核空间来回切换,充当Binder通信的信使。
  2. 用户空间和内核空间共享同一块物理页面:2.6后linux系统会为每个进程分配4G的虚拟空间,其中0~3G的用户空间是每个进程独有的,而3G~4G的内核空间是所有进程共享的,进程B在mmap()后Binder驱动为进程分配一块内核缓冲区,最大只能申请4M,用户空间和内核空间共享同一块内存,B进程的用户空间和内核空间同大小,有线性映射关系,内核空间的缓冲数据保存在binder_proc->buffer中。如下图,进程A要将数据发送到进程B,先用copy_from_user()将数据从用户空间拷贝到内核空间的缓冲区中,由于B进程用户空间和内核空间共享内存,当然可以在B进程的用户空间访问。


Binder间交互

从图中我们看出,Binder代理对象(BpBinder)在client发起请求时,会在Binder驱动中先找到Binder引用对象(binder_ref),在通过引用对象找到Binder实体对象(binder_node),而通过就Binder实体对象就能找到Server进程的本地对象。

java层的代码与其相似,只是通过JNI进行了包装,这里就不分析了。

更多相关文章

  1. SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
  2. 关于android应用--内存的优化
  3. Android(安卓)EventBus你需要了解的都在这
  4. android binder 进程间通信机制1-binder 驱动程序
  5. Android(安卓)线程消息循环机制
  6. 卷二 Dalvik与Android源码分析 第二章 进程与线程 2.2 Dalvik线
  7. Android(安卓)跨进程通信基础
  8. android一个小网络图片查看器
  9. android之ThreadLocal详解

随机推荐

  1. Android(安卓)ADB实现解析
  2. SparseArray详解,我说SparseArray,你说要!
  3. Android中绑定SQLite到ListActivity
  4. window.navigator.userAgent的用处
  5. android 百度推送的集成
  6. Android(安卓)EditText限制输入数字和字
  7. Android基于AudioManager、PhoneStateLis
  8. Android(安卓)Activity分析
  9. android TextInputLayout
  10. 【Android(安卓)Developers Training】 1