Android(安卓)Binder进程间通信深入分析
珍惜作者劳动成果,如需转载,请注明出处。
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的启动过程
- ServiceManager在系统一启动在init.rc就运行起来;
- binder_open()打开Binder虚拟设备,并且为当前进程创建binder_proc结构体,Bindder驱动为ServiceMamager在内核空间分配了128K的内核缓冲区,并且是只读的,只能由内核去修改;
- 通过io命令BINDER_SET_CONTEXT_MGR将自己注册为Binder驱动的”大管家”,并且创建一个binder_thread作为主线程,还会为ServiceManager的Binder本地对象创建一个Binder实体对象,这些都是在Binder驱动中完成的;
- 执行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通信的本质有两点:
- Binder驱动中目标线程代替源线程执行任务,源线程在源进程里,而目标线程在目标进程里,源线程要执行任务,只需将其包装成事务,放在目标线程的todo队列中,目标线程就会代替源线程处理这个事务,而且Binder线程会在用户空间和内核空间来回切换,充当Binder通信的信使。
- 用户空间和内核空间共享同一块物理页面: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进行了包装,这里就不分析了。
更多相关文章
- SpringBoot 2.0 中 HikariCP 数据库连接池原理解析
- 关于android应用--内存的优化
- Android(安卓)EventBus你需要了解的都在这
- android binder 进程间通信机制1-binder 驱动程序
- Android(安卓)线程消息循环机制
- 卷二 Dalvik与Android源码分析 第二章 进程与线程 2.2 Dalvik线
- Android(安卓)跨进程通信基础
- android一个小网络图片查看器
- android之ThreadLocal详解