《深入理解Android内核设计思想》笔记之Binder
image.png本篇文章是《深入理解Android内核设计思想》的读书笔记,可能就没书本来得详细。
Binder分为四个部分
- Binder驱动:运行于内核态,可以提供open(),ioctl(),mmap()等常用的文件操作
- ServiceManager:保存所有Binder Server。
- Binder Client
- Binder Service
一.Binder驱动
- 打开Binder驱动-binder-open,在里面创建了一个它自己的binder_proc实体
binder_proc是Binder驱动为应用进程分配的一个数据结构,用于存储和该进程有关的所有信息,如内存分配线程管理等。
binder-open{
生成binder_proc对象
binder_proc初始化
把它添加到Binder的全局管理中(资源互斥添加)
}
到这里,Binder驱动已经为用户创建了一个它自己的binder_proc实体,之后用户对Binder的设备操作将以这个对象为基础。
-
binder_mmap,最多支持4MB的操作
对于应用程序而言,调用mmap()方法可以把设备指定的内存块映射到应用程序的内存空间中
而对于Binder驱动来说,上层用户调用的mmap()最终就对应了binder_mmap()-
对于应用程序,它通过mmap()返回值得到一个虚拟内存地址,之后通过虚拟内存转换(分段分页)最终指向物理地址某个地方
-
对于Binder驱动而言,有个指针binder_proc->buffer指向某个虚拟内存地址,与应用程序指向一样,两者共用内存
-
-
binder_ioctl
可以替代read,write。对应命令 INDER_WRITE_READ,承担Binder驱动的大部分业务 -
小结
Binder驱动并没有脱离Linux的典型驱动模型,它提供了多个文件操作接口mmap,ioctl。其binder_ioctl实现了应用程序与Binder驱动之间的命令交互,可以说承载了Binder驱动中的大部分业务。
二.ServiceManager - Binder Service
-
ServiceManager启动
在init.rc时启动(参考系统启动章节),如果它发生重启,其他系统服务zygote,media,surfaceflinger,drm也会被重新加载 -
ServiceManager构建
int main{
binder_open打开Binder设备,做好初始化(各种内存地址)
binder_become_context_manager将自己设为Binder大管家,整个Android程序中只允许一个ServiceManager存在
binder_loop进入主循环
}
* binder_open初始化
> * binder_state 记录SM中有关于Binder的所有信息
* mmap由Binder驱动决定被映射到进程空间中的内存起始地址
* 映射区大小为128K,只读
* 从文件的起始地址开始映射
* binder_become_context_manager初始化
> * 调用到ioctl方法
* ioctl方法中发送向Binder Driver发送 **BINDER_SET_CONTEXT_MGR**的ioctl命令使其成为大管家。(只要向Binder驱动发送BINDER_SET_CONTEXT_MGR的ioctl命令即可,因为ServicerManager启动的很早,能确保它是系统中第一个向Binder驱动注册成管家的程序)
* binder_loop循环等待客户端请求
> * binder_write_read 执行**BINDER_WRITE_READ**所需的数据格式
* 开始循环读取消息(它的消息是从Binder驱动那里获取的)
* 从Binder驱动读取消息,通过**BINDER_WRITE_READ**命令
* binder_parse处理消息
* 不断循环,而且永远不会主动退出,除非出现致命错误
ServiceManager的功能架构比较简洁,内部维护着一个svclist列表,用于存储所有Server(Binder Service)的相关信息(以svcinfo为数据结构),查询和注册都是基于这个表展开
- 获取ServiceManager服务
准确说,获取SM服务是Binder Client需要做的工作之一。
访问SM服务流程
1.打开Binder设置
2.执行mmap内存映射
3.通过Binder驱动向SM发送请求(SM的handle为0)
4.获取结果
每个进程只允许打开一次Binder设备,且只做一次内存映射---所有需要使用Binder驱动的线程共享这一资源。
获取SM服务流程图
获取SM服务进程中ProcessState和IPCThreadState这两个类专用与Binder驱动通信,所以Java层代码使用Binder驱动实际上是基于他们来完成的,我们称为BpBinder。
应用程序通过代理ServiceManagerProxy(能够跨进程调用)来获取到SM服务,它所能提供的服务和服务端的SM必须是一致的,也就是它也要实现服务端SM对应的接口。
这里的SM指的是Binder层面的ServiceManager。
对于获取SM服务的代理ServiceManagerProxy,Android系统在ServiceManagerProxy上又加了一层封装ServiceManager.java
2.1 获取SM服务
首先调用ServiceManager.getService(name)
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return getIServiceManager().getService(name);
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
其中sCache用于记录getService的历史查询结果,加快查询速度。如果不在则调用getIServiceManager().getService(name);
getIServiceManager
方法用于获取一个IServiceManager
对象,而IServiceManager
接口的实现类则是ServiceManagerProxy
private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
return sServiceManager;
}
// Find the service manager
sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
return sServiceManager;
}
调用ServiceManagerNative的方法
static public IServiceManager asInterface(IBinder obj)
{
if (obj == null) {
return null;
}
IServiceManager in =
(IServiceManager)obj.queryLocalInterface(descriptor);
if (in != null) {
return in;
}
return new ServiceManagerProxy(obj);
}
先查询本地是否已经有了IServiceManager
,如果没有则新建一个。
最终则是调用到了ServiceManagerProxy.getService
了
public IBinder getService(String var1) throws RemoteException {
Parcel var2 = Parcel.obtain();
Parcel var3 = Parcel.obtain();
var2.writeInterfaceToken("android.os.IServiceManager");
var2.writeString(var1);
this.mRemote.transact(1, var2, var3, 0);
IBinder var5 = var3.readStrongBinder();
var3.recycle();
var2.recycle();
return var5;
}
该函数分为以下3部分
- 准备数据。也就是通过Parcel打包数据
- IBinder.transact,利用IBinder的transact将请求发送出去,而不用理会Binder驱动的open,mmap以及一大堆具体的 Binder协议中的命令。所以这个IBinder一定会在内部使用
ProcessState
和IPCThreadState
来与Binder驱动进行通讯。 - 获取结果。
上面transact后,我们就可以直接获取到结果了。如果大家用过socket,就知道这是一种阻塞式的函数调用。因为涉及到进程间通信,结果并不是马上就能获取到,所以Binder驱动一定要先将调用者线程挂起,直到有了结果才把它唤醒。
那么IBinder内部是怎么实现的呢?
2.2 IBinder内部
前面我们的IBinder对象是通过BinderInternal.getContextObject()
获取到的
而它则是一个native方法
public static final native IBinder getContextObject();
因为和Binder驱动打交道,最终都得通过JNI调用本地代码来实现。
而在native层中getContextObject方法通过ProcessState
把 创建的对象BpBinder转换为Java层的IBinder对象。
IBinder只是一个接口类,
在native层,IBinder实现类为BpBinder(由ProcessState创建)
而在Java层,IBinder实现类则是BinderProxy。
前面调用IBinder.transact内部调用流程
Paste_Image.png发现绕了个大圈子,最终处理用户的Binder请求还是通过IPCThreadState和ProcessState来实现的。
2.3 ProcessState
关键点在于
- 保证同一个进程中只有一个ProcessState实例存在,而且只有在ProcessState对象创建时才打开Binder设置(open_driver)以及做内存映射(mmap)
- 向上层提供IPC服务
- 与IPCThreadState分工合作,各司其职
可以看到ProcessState它是一个进程中的单实例,而IPCThreadState则是线程中的单实例。
可以说,IPCThreadState负责与Binder驱动进行具体的命令交互,因而它的transact函数非常重要,
而ProcessState只是负责打开了Binder节点并做mmap
三.Binder Client客户端
Binder的最大消费者是JAVA层的应用程序。一般情况下他们可以在程序代码的任何位置通过bindService,startActivity以及sendBroadcast等一系列接口方法来实现与其他进程的交互。
有了Binder驱动,Service Manager的努力,以及Android系统专门面向应用开发提供的Binder强有力的封装,才能使应用程序之间顺利地进行无缝通信。
3.1 例子bindService
应用程序如何能依托bindService来启动系统中其他进程提供的Service呢
-
①应用程序填写Intent,调用bindService发出请求
-
②收到请求的bindService(此时还在应用程序的运行空间中)将与ActivityManagerService(AMS)取得联系。根据前面的分析,为了获得AMS的Binder句柄,我们还要事先调用ServiceManager.getService,这里就涉及到进程间通信了。在得到AMS的句柄值后,程序才能真正地向它发起请求。
-
③AMS基于特定的最优匹配策略,从其内部存储的系统所有服务组件的资料中找到与Intent最匹配的一个,然后向它发送Service绑定请求。如果目标进程还不存在的话,AMS还要负责把它启动起来。
-
④被绑定的服务进程需要响应绑定,执行具体的操作,并在成功完成后通知AMS,然后由AMS再回调发起请求的应用程序(回调接口是ServiceConnection)
当Activity调用bindService时,最终调用到的是ContextImpl.bindService
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
UserHandle user) {
...
int res = ActivityManagerNative.getDefault().bindService(
mMainThread.getApplicationThread(), getActivityToken(), service,
service.resolveTypeIfNeeded(getContentResolver()),
sd, flags, getOpPackageName(), user.getIdentifier());
...
}
那么,应用程序又是如何找到AMS并与之建立联系的呢?
和ServiceManager一样,AMS也同样提供了ActivityManagerNative和ActivityManagerProxy代理
//ActivityManagerNative.getDefault()
static public IActivityManager getDefault() {
return gDefault.get();
}
这个gDefault.get又是什么
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
...
IActivityManager am = asInterface(b);
...
return am;
}
};
可以看到这里是通过单例模式来创建一个IActivityManager对象。
之后通过ServiceManager.getService("activity")方法取得ActivityManagerService的IBinder对象。
接着调用asInterface,通过这个IBinder创建一个可用的ActivityManagerProxy
static public IActivityManager asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IActivityManager in =
(IActivityManager)obj.queryLocalInterface(descriptor);
if (in != null) {
return in;
}
return new ActivityManagerProxy(obj);
}
此外,ActivityManagerNative的另一个作用是为ActivityManagerService的实现提供便利。AMS只需要继承自ActivityManagerNative就可以将用户的业务请求码与自己的内部实现函数连接起来。
Paste_Image.png四.AIDL——Android接口描述语言
它是用于定义客户端/服务端通信接口的一种描述语言。
要知道Server Manager本身也是一个BinderServer,AMS,WMS也是一个BinderServer,那么从Service Manager的实现来看,构建一个Binder Server所需的工作为:
1.启动的时机
比如SM是开机的时候通过init.rc文件启动的,这就保证了它是系统中第一个注册成服务大管家的Server,而WMS,AMS这些则是在SystemServer中启动的。
2.提供一致的服务接口
一个Binder Server应该向公众暴露它所能提供的服务,而且客户端使用的服务接口和服务器实现的服务接口必须是完全一致的。
对于WMS
通过分析aidl文件以及由它转换生成的java接口文件,我们知道一个AIDL接口包括了IwindowManager,IWindowManager.Stub和IWindowManagerManager.Stub.Proxy三个重要类。
后两者分别面向与WMS的服务端和Binder Client本地代理的实现,且都继承与IWindowManager,因而就保证了Client和Server是在完全一致的服务接口上进行通讯。
3.与Binder驱动的交互
一个Binder Server需要与Binder驱动做哪些交互呢?除去一系列必要的初始化以外(open,mmap等),就是要通过不断地ioctl来循环读写数据。
比如SM就是通过binder_loop函数中的一个for死循环来完成这一工作的。
而对于AMS,PMS这些,他们在SystemServer启动的过程中会开启一个线程来循环读取消息,这点和binder_loop的主体框架基本一致。
4.外界如何才能访问到这个Server服务?
- ①Service在ServiceManager中注册
这种方法普遍存在于Android系统服务中,如ActivityManagerService,WindowManagerService等都在ServiceManager中做了注册。调用者只需要通过ServiceManager.getService(name)就可以获取到Binder Server的本地代理,然后与之通信。
WMS可以通过addService把自己注册到Service Manager中。因而任何Binder Client都可以通过SM的getService接口获取到它的一个引用。称为实名的Server。
- ②通过其他Server作为中介
也就是Binder Server不需要在ServiceManager中注册,只需要通过一个第三方的实名Server。
比如WindowSession。
匿名Server安全性高。
更简单的来构建Binder Server就是使用AIDL.