Android - Binder驱动
以下资料摘录整理自老罗的Android之旅博客,是对老罗的博客关于Android底层原理的一个抽象的知识概括总结(如有错误欢迎指出)(侵删):
http://blog.csdn.net/luoshengyang/article/details/8923485
http://blog.csdn.net/luoshengyang/article/details/12957169
整理by Doing
Binder机制介绍
传统的IPC ,例如Pipe和Socket,执行一次通信需要两次数据拷贝
内存共享机制虽然只需要执行一次数据拷贝,但是它需要结合其它IPC(如:信号量)来做进程同步,效率同样不理想
Binder是一种高效且易用的IPC机制,提供远程过程调用(RPC)功能:
- 一次数据拷贝
- Client/Server通信模型
- 既可用作进程间通信,也可用作进程内通信
在Android系统的Binder机制中,由一系统组件组成,分别是Client、Server、Service Manager和Binder驱动程序(其中Client、Server和Service Manager运行在用户空间,Binder驱动程序运行内核空间)。Binder就是一种把这四个组件粘合在一起的粘结剂了,其中,核心组件便是Binder驱动程序了,Service Manager提供了辅助管理的功能,Client和Server正是在Binder驱动和Service Manager提供的基础设施上,进行Client-Server之间的通信。
Service Manager和Binder驱动已经在Android平台中实现好,开发者只要按照规范实现自己的Client和Server组件就可以了。
- Client、Server和Service Manager实现在用户空间中,Binder驱动程序实现在内核空间中
- Binder驱动程序和Service Manager在Android平台中已经实现,开发者只需要在用户空间实现自己的Client和Server
- Binder驱动程序提供设备文件/dev/binder与用户空间交互,Client、Server和Service Manager通过open和ioctl文件操作函数与Binder驱动程序进行通信
- Client和Server之间的进程间通信通过Binder驱动程序间接实现
- Service Manager是一个守护进程,用来管理Server,并向Client提供查询Server接口的能力
一次数据拷贝
Binder驱动为每一个进程分配4M的内核缓冲区(物理页面,可以是不连续),用作数据传输:
- 4M内核缓冲区所对应的物理页面除了映射在内核空间之外,还会被映射在进程的用户空间(同时使用进程虚拟地址空间和内核虚拟地址空间来映射同一个物理页面)
- 4M内核缓冲区所对应的物理页面是按需要分配的,一开始只有一个物理页被被映射
- 用户空间:0G ~ 3G
- 内核空间:3G ~ 4G。其中,3G ~ (3G + 896M)范围的地址是用来映射连续的物理页面的,这个范围的虚拟地址和对应的实际物理地址有着简单的对应关系,即对应0~896M的物理地址空间;而(3G + 896M) ~ (3G + 896M + 8M)是安全保护区域(例如,所有指向这8M地址空间的指针都是非法的);因此使用(3G + 896M + 8M) ~ 4G地址空间来映射非连续的物理页面
进程间的一次数据拷贝:
Binder进程间通信机制的精髓所在,同一个物理页面,一方映射到进程虚拟地址空间,一方面映射到内核虚拟地址空间,这样,进程和内核之间就可以减少一次内存拷贝了,提到了进程间通信效率。举个例子如,Client要将一块内存数据传递给Server,一般的做法是,Client将这块数据从它的进程空间拷贝到内核空间中,然后内核再将这个数据从内核空间拷贝到Server的进程空间,这样,Server就可以访问这个数据了,但是在这种方法中,执行了两次内存拷贝操作,而采用我们Binder机制,只需要把Client进程空间的数据拷贝一次到内核空间,然后Server与内核共享这个数据就可以了,整个过程只需要执行一次内存拷贝,提高了效率。
Client/Server通信模型
Server进程启动时,将在本进程内运行的Service注册到Service Manager中,并且启动一个Binder线程池,用来接收Client进程请求
Client进程向Service Manager查询所需要的Service,并且获得一个Binder代理对象,通过该代理对象即可向Service发送请求
句柄(handle):
在程序设计中,句柄(handle)是一种特殊的智能指针。当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。 句柄与普通指针的区别在于,指针包含的是引用对象的内存地址,而句柄则是由系统所管理的引用标识,该标识可以被系统重新定位到一个内存地址上。这种间接访问对象的模式增强了系统对引用对象的控制(封装)。通俗的说就是我们调用句柄就是调用句柄所提供的服务,即句柄已经把它能做的操作都设定好了,我们只能在句柄所提供的操作范围内进行操作,但是普通指针的操作却多种多样,不受限制。
Service Manager注册及其代理获得
(Service Manager:一个特殊Service,它的代理句柄值永远等于0)
Service Manager是成为Android进程间通信(IPC)机制Binder守护进程的过程:
- 打开/dev/binder文件:open("/dev/binder", O_RDWR);
- 建立128K内存映射:mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
- 通知Binder驱动程序它是守护进程:binder_become_context_manager(bs);
- 进入循环等待请求的到来:binder_loop(bs, svcmgr_handler);
Service代理获取
对于普通的Server来说,Client如果想要获得Server的远程接口,那么必须通过Service Manager远程接口提供的getService接口来获得,这本身就是一个使用Binder机制来进行进程间通信的过程。
而对于Service Manager这个Server来说,Client如果想要获得Service Manager远程接口,却不必通过进程间通信机制来获得,因为Service Manager远程接口是一个特殊的Binder引用,它的引用句柄一定是0。
Service注册
Server获得了Service Manager远程接口之后,把自己的Service添加到Service Manager中去,然后把自己启动起来,在一个无穷循环中等待Client的请求。
Client和Server的通信过程
Binder进程间通信机制的Java接口
- 每一个Java层的Binder本地对象(Binder)在C++层都对应有一个JavaBBinder对象,后者是从C++层的BBinder继承下来的
- 每一个Java层的Binder代理对象(BinderProxy)在C++层都对应有一个BpBinder对象
- 于是Java层的Binder进程间通信实际上就是通过C++层的BpBinder和BBinder来进行的,与C++层的Binder进程间通信 一致
Binder机制在应用程序框架层中的Java接口,主要就是Service Manager、Server和Client这三个角色的实现了。通常,在应用程序中,我们都是把Server实现为Service的形式,并且通过IServiceManager.addService接口来把这个Service添加到Service Manager,Client也是通过IServiceManager.getService接口来获得Service接口,接着就可以使用这个Service提供的功能了,这个与运行时库的Binder接口是一致的。(XXX.AIDL自动生成的IXXX.java类中的Proxy内部类的Binder对象(mRemote)实际上是一个BinderProxy对象,即对应于C++层的BpBinder对象,mRemote的transact成员函数是一个JNI方法)
Binder对象
Binder对象(flat_binder_object)的类型:
BINDER_TYPE_BINDER和BINDER_TYPE_WEAK_BINDER类型的flat_binder_object传输发生在:
- Server进程主动向Client进程发送Service (匿名Service )
- Server进程向Service Manager进程注册Service
BINDER_TYPE_HANDLE和BINDER_TYPE_WEAK_HANDLE类型的flat_binder_object传输发生在:
- 一个Client向另外一个进程发送Service代理
BINDER_TYPE_FD类型的flat_binder_object传输发生在:
- 一个进程向另外一个进程发送文件描述符
Binder对象的引用计数
- BBinder:位于用户空间,通过智能指针管理,有mStrong和mWeak两个引用计数
- BpBinder:位于用户空间,通过智能指针管理,有mStrong和mWeak两个引用计数
- binder_node:位于内核空间,有internal_strong_refs、local_weak_refs和local_strong_refs三个引用计数,以及一个binder_ref引用列表refs
- binder_ref:位于内核空间,有strong和weak两个引用计数
Binder对象之间的引用关系:
- Binder对象引用关系小结:
-BBinder被binder_node引用
-binder_node被binder_ref引用
-binder_ref被BpBinder引用
-BBinder和BpBinder运行在用户空间
-binder_node和binder_ref运行在内核空间
-binder_node和binder_ref运行在内核空间
- 内核空间足够健壮,保证binder_node和binder_ref不会异常销毁
- 用户空间不够健壮,BBinder和BpBinder可能会异常销毁
- BpBinder异常销毁不会引发致命问题,但是BBinder异常销毁会引发致命问题
- 需要有一种方式来监控BBinder和BpBinder的异常销毁:
Binder对象异常销毁监控:
- 所有执行Binder IPC的进程都需要打开/dev/binder文件
- 进程异常退出的时候,内核保证会释放未正常关闭的它打开的/dev/binder文件,即调用与/dev/binder文件所关联的release回调函数
- Binder驱动通过实现/dev/binder文件的release回调函数即可监控Binder对象的异常销毁,进而执行清理工作
BBinder异常销毁的时候,不单止Binder驱动需要执行清理工作,引用了它的BpBinder所在的Client进程也需要执行清理工作,所以需要有一种BBinder死亡通知机制:
- Client进程从Binder驱动获得一个BpBinder
- Client进程向Binder驱动注册一个死亡通知,该死亡通知与上述BpBinder所引用的BBinder相关联
- Binder驱动监控到BBinder所在进程异常退出的时候,检查该BBinder是否注册有死亡通知
- Binder驱动向注册的死亡通知所关联的BpBinder所运行在的Client进程发送通知
- Client进程执行相应的清理工作
Binder驱动对Binder对象(flat_binder_object)的 处理:
(源进程:client;目标进程:server)
Binder驱动对类型BINDER_TYPE_BINDER 的Binder对象(flat_binder_object)的 处理:
- 在源进程中找到对应的binder_node。如果不存在,则创建。
- 根据上述binder_node在目标进程中找到对应的binder_ref。如果不存在,则创建。
- 增加上述binder_ref的强引用计数和弱引用计数(智能指针)
- 构造一个类型为BINDER_TYPE_HANDLE的flat_binder_object对象。
- 将上述flat_binder_object对象发送给目标进程。
Binder驱动对类型BINDER_TYPE_WEAK_BINDER 的Binder对象(flat_binder_object)的 处理:
- 在源进程中找到对应的binder_node。如果不存在,则创建。
- 根据上述binder_node在目标进程中找到对应的binder_ref。如果不存在,则创建。
- 增加上述binder_ref的弱引用计数。(智能指针)
- 构造一个类型为BINDER_TYPE_WEAK_HANDLE的flat_binder_object对象。
- 将上述flat_binder_object对象发送给目标进程。
Binder驱动对类型BINDER_TYPE_HANDLE 的Binder对象(flat_binder_object)的 处理:
- 在源进程中找到对应的binder_ref。
- 如果上述binder_ref所引用的binder_node所在进程就是目标进程:
- 增加上述binder_node的强引用计数和弱引用计数(智能指针)
- 构造一个类型为BINDER_TYPE_BINDER的flat_binder_object
- 将上述flat_binder_object发送给目标进程
- 如果上述binder_ref所引用的binder_node所在进程不是目标进程:
- 为目标进程创建一个binder_ref,该binder_ref与上述binder_ref引用的是同一个binder_node
- 增加上述新创建的binder_ref的强引用计数和弱引用计数(智能指针)
- 构造一个类型为BINDER_TYPE_HANDLE的flat_binder_object
- 将上述flat_binder_object发送给目标进程
Binder驱动对类型BINDER_TYPE_WEAK_HANDLE 的Binder对象(flat_binder_object)的 处理:
- 在源进程中找到对应的binder_ref。
- 如果上述binder_ref所引用的binder_node所在进程就是目标进程:
- 增加上述binder_node的弱引用计数(智能指针)
- 构造一个类型为BINDER_TYPE_WEAK_BINDER的flat_binder_object
- 将上述flat_binder_object发送给目标进程
- 如果上述binder_ref所引用的binder_node所在进程不是目标进程:
- 为目标进程创建一个binder_ref,该binder_ref与上述binder_ref引用的是同一个binder_node
- 增加上述新创建的binder_ref的弱引用计数(智能指针)
- 构造一个类型为BINDER_TYPE_WEAK_HANDLE的flat_binder_object
- 将上述flat_binder_object发送给目标进程
Binder驱动对类型BINDER_TYPE_FD 的Binder对象(flat_binder_object)的 处理:
- 在源进程中找到对应的struct file结构体
- 将上述struct file结构体 保存在目标进程的打开文件列表中
- 构造一个类型为BINDER_TYPE_FD的flat_binder_object
- 将上述flat_binder_object发送给目标进程
Binder线程池
- 每一个Server进程在启动的时候都会创建一个Binder线程池,并且向里面注册一个Binder线程
- 之后Server进程可以无限地向Binder线程池注册新的Binder线程
- Binder驱动发现Server进程没有空间的Binder线程时,会主动向Server进程请求注册新的Binder线程(Binder驱动主动请求Server进程注册新的Binder线程的数量可以由Server进程设置,默认是16)
- 在Server进程中,Client进程发送过来的Binder请求由Binder线程进行处理
Binder线程调度机制
- 在Binder驱动中,每一个Server进程都有一个todo list,用来保存Client进程发送过来的请求,这些请求可以由其Binder线程池中的任意一个空闲线程处理
- 在Binder驱动中,每一个Binder线程也有一个todo list,用来保存Client进程发送过来的请求,这些请求只可以由该Binder线程处理
- Binder线程没事做的时候,就睡眠在Binder驱动中,直至它所属的Server进程的todo list或者它自己的todo list有新的请求为止
- 每当Binder驱动将Client进程发送过来的请求保存在Server进程的todo list时,都会唤醒其Binder线程池的空闲Binder线程池,并且让其中一个来处理
- 每当Binder驱动将Client进程发送过来的请求保存在Binder线程的todo list时,都会将其唤醒来处理
同步请求优先于异步请求
- 同一时刻,一个BBinder只能处理一个异步请求
- 第一个异步请求将被保存在目标进程(server进程)的todo list中
- 第一个异步请求未被处理前,其它的异步请求将被保存在对应的 binder_node的async todo list中
- 第一个异步请求被处理之后,第二个异步请求将从binder_node的async todo list转移至目标进程的todo list等待处理
- 依次类推……
此外,所有同时进行的异步请求所占用的内核缓冲区大小不超过目标进程的总内核缓冲区大小的一半
与请求相关的三个线程优先级:
- 源线程的优先级
- 目标线程的优先级
- 目标binder_node的最小优先级(注册Service时设置)
- Binder线程处理同步请求时的优先级:取{源线程,目标binder_node,目标线程}的最高优先级;保证目标线程以不低于源线程优先级或者binder_node最小优先级的优先级运行
- Binder线程处理异步请求时的优先级:取{目标binder_node,目标线程}的最高优先级;保证目标线程以不低于binder_node最小优先级的优先级运行
Binder线程的事务(binder_transaction)堆栈
默认情况下, Client进程发送过来的请求都是保存在Server 进程的todo list中。
然而有一种特殊情况:
- -源进程P1的线程A向目标进程发起了一个请求T1,该请求被分发给目标进程P2的线程B处理
- -源进程P1的线程A等待目标进程P2的线程B处理处理完成请求T1
- -目标进程P2的线程B在处理请求T1的过程中,需要向源进程P1发起另一个请求T2
- -源进程P1除了线程A是处于空闲等待状态之外,还有另外一个线程C处于空闲等待状态
这时候请求T2应该分发给线程A处理,还是线程C处理?
- -如果分发给线程C处理,则线程A仍然是处于空闲等待状态
- -如果分发给线程A处理,则线程 C可以处理其它新的请求
所以应该分发给A(通过binder_transaction的堆栈(Binder线程的事务堆栈)维护):