本文可以视为 Binder 学习指南 和 为什么Android 要采用 BInder 作为 IPC 机制 两篇文章的学习笔记,因此文章仅为笔者个人复习使用 ,同时希望能给 对以上两篇文章有所了解的朋友 提供参考。
binder 是 Android 系统实现IPC(跨进程通信)的机制,它沟通和联系了各个组件,是Android系统最为重要的成员之一。
进程:
进程是操作系统中的一个重要概念,基本的 CS 课程都会讲到,不同进程之间的数据是不共享的(内存隔离),那么进程之间要进行数据传递,就需要有一套对应的系统机制了。
进程隔离:
进程隔离使用了虚拟地址空间(这个概念就不引申了),A 进程和B进程的虚拟地址不同,以此避免 A进程的数据写入B进程。
内核:
Linux Kernel ,它是系统运行的核心,独立于普通的应用程序。它可以访问受到保护的内存空间,同时也有权限访问所有硬件资源。
用户:
这里的用户就是上层的普通应用程序的意思
为什么区分用户空间 与 内核空间?
从内核的设定可以看出,它是非常重要的,也拥有极高的权限,因此不可以让上层应用程序随意的调用和访问它,所以需要一套保护机制,告诉应用程序,你只可以访问哪些特定的资源。所以就从逻辑上抽象出用户空间和内核空间,将它们隔离开来。
系统调用
虽然说抽象出了 用户空间 和 内核空间将它们隔离开,但是不可避免的,应用程序会有需要访问内核的时候,例如访问文件、使用网络。这时候,就有了系统调用的概念。
系统调用是用户空间访问内核空间的唯一入口接口,通过这种方式,所有资源的访问都会在内核的控制之下了,安全性自然大大增加。
用户态:
当在执行上层应用程序自己的代码的时候,就称之处于用户态,这时候进程的特权等级最低(3级)
内核态:
当通过系统调用,执行内核代码的时候,就称之处于内核态,这时候进程的特权等级最高(0级)。只有在满足一定特权等级的时候,CPU才能执行对应的特权指令。
用户空间与用户空间通信
用户空间与用户空间之间想要通信,第一时间想到的当然是让内核提供支持,毕竟不同的用户空间都可以访问同一个内核空间。
linux内核中的socket和管道都是解决用户空间之间通信问题的,但是Binder并不是Linux内核的一部分啊,这时候就用到了Linux的动态可加载内核模块机制(LKM)
内核模块
在Linux中,模块不能单独运行,但是它可以单独编译,内核模块就是在运行时再 连入 链接到内核中,作为内核的一部分 ,在内核空间中运行。
在Android系统里,为了解决用户进程之间通信问题而引入的内核模块就是:binder驱动。
binder 驱动:
驱动就是操作硬件的接口,为了实现binder通信的过程,binder使用了一种“硬件”,因此这个模块被叫做binder驱动。
性能更好:
在移动设备上(尤其是早期的Android设备,硬件水平太差了),性能毫无疑问是非常重要的一项指标,binder机制比起传统的socket和管道等有更好的性能:Binder数据拷贝只需要一次(匿名共享内存),而管道、消息队列、Socket都需要2次。
安全性更高:
传统的跨进程通信机制,通常都没有针对通信的两端做身份上的校验(例如socket的IP地址完全可以伪造),而Android系统作为一个开放的生态,必然对安全性有更高的要求。
binder机制从协议上就 ~~设置了 ~~ 支持了对通信双方的身份校验,提高了安全性。这也是Android权限模型的基础
通信的双方:
Binder通信的双方一般称为:Server端 和 Client端,可以类比于网络通信中的服务端和客户端,实际上它们往往就是两个用户进程
通信录:
就像打电话时候需要的电话簿一样,通信录记录了 Server 端的地址信息,这样 Client 端才能找到对应的 Server 端,并向它发送消息。
通信录如何知道 Server 端的信息呢?
这就要求Server端自己要去通信录里面注册了,Binder通信的通信录工作,是由 ServiceManager 来完成的,也可以简称为 SM(不要想歪了,这是失眠的意思
基站:
同样以打电话来类比,一次通信的过程,当然少不了基站的支持,只有依靠它才能真正实现在通信双方之间传递消息
Binder通信的基站工作,是由Binder驱动来完成的
常规的进程间通信方式:
对于两个进程 A 和 B 来说,内核可以访问它们两个的数据,最常见的跨进程通信方式,当然就是利用内核。
例如 A 要向 B 发送一段数据,那么 A 就可以将数据拷贝到内核空间,然后内核再将数据拷贝到 B。
前面说到,用户空间操控内核空间需要通过系统调用,刚好Linux也提供了对应的系统调用:copy_from_user, copy_to_user。
Binder 机制跨进程通信的定义:
通信这个概念其实是比较宽泛的
Binder 机制实现跨进程通信,其实是指 Client 进程可以访问 Server 进程中的对象及其方法,这当然也是通信的一种方式
Binder 并没有用上述的常规的进程间通信方式
隐藏步骤:一个进程申请成为ServiceManager,也就是SM进程
Server 端向 SM 注册:Server 端要向 SM 注册,告知 SM 自己的信息和自己有什么能力(提供什么方法)。例如:进程名是 Zhangsan ,拥有一个 Object 对象,它有个 add 方法。
Client 端从 SM 查询:Client 端想要访问 Zhangsan的这个 Object 对象,就需要联系 SM 查询,让SM给它返回对应的对象,但是注意,这里返回的对象不是原对象。
Binder 驱动对查询的中间处理:
数据在内核空间传递的时候,会经过 Binder 驱动,Binder 驱动判断到这是一个跨进程的调用,它不会将真正的 Object 对象返回给 A ,而是会返回一个 ObjectProxy 对象。
这个对象的方法和 Object 对象一模一样,它也有 add 方法,但是它不具备真正 Object 对象所拥有的能力,也就是它是一个代理对象,是假的。
Client 端调用:Client 拿到 ObjectProxy 对象之后,并不会感知到它和 Object 对象不一样,它还是照常当作在调用 Object 的 add 方法。
Binder 驱动对调用的中间处理:
这个调用当然又要经过 Binder 驱动,Binder 驱动 通过查表判断到 这是 ObjectProxy 对象的调用,就会去调用真正的 Object 对象的 add 方法,并且将得到的结果,通过 ObjectProxy.add 方法返回给进程 A。由此就完成了一次跨进程的调用,而 A 进程的调用方在此过程中其实是完全无感知的。
隐藏的内容 – Server 与 SM 的通信:
上述过程其实省略了很多细节,例如 Server 进程与 SM 大多数时候肯定也不是同一个进程的,那么 Server 进程向 SM 注册的过程,本身也是一次跨进程通信,自然也是利用了 Binder 机制
SM 中持有的 Object 对象,自然也不是真正的 Object 对象,其实它也是一个 代理对象,是一个 ObjectProxy 。
Server 进程的本地对象仅有一个,只有这个是有真正的能力的,其他进程所拥有的全部都是它的代理
总结:Client 进程持有的只是 Server 端的代理,代理对象协助Binder驱动完成了跨进程通信。
通信机制:
通常来说,binder 指的是它所代表的 IPC 通信机制,经常说的 AIDL 其实就是 Binder 机制实现 IPC 通信。
Server进程:
对于Server进程来说,Binder 指的是 Binder 本地对象,它是拥有实际能力的对象
Client进程:
对于Client进程来说,Binder 指的是 Binder 代理对象,它只是 Binder 本地对象的一个远程代理
当然,Client进程并不知道这些,它可以讲 Binder 代理对象当作本地对象来使用
传输过程:
对于这一整个传输过程来说,Binder 指的是这种能跨进程传输的特殊对象
每次跨进程传输,经过Binder驱动时,驱动会自动完成 本地对象 和 代理对象的转换
通信过程 -> 方法调用:
binder 机制把一个IPC通信的过程,变成了对 Binder 对象的方法调用
对于使用者来说,它完全不需要关心细节,只需要知道 Binder 对象即可,这就是抽象
跨进程引用对象:
在上述机制之下,不同进程可以持有同一个“对象”的引用,并且引用还可以传递,可以像访问一个普通的Java对象一样,去访问位于另一个进程的 binder 对象
binder 模糊了进程的边界,淡化了进程间通信的过程,不同的进程的代码,像是实际运行在同一个面向对象的程序中一样
两种类型的转换
上述说到,在IPC过程中,本地对象 和 代理对象需要相互转换,这是由Binder驱动来完成的
两种类型的数据结构
要能做到这一点,Binder 驱动中需要存放所有的跨进程 Binder 对象的相关信息
其中,本地对象是一个叫 binder_node 的数据结构,代理对象则是由 binder_ref 来代表的,后者也被称为binder 引用或是 binder句柄。
IInterface:
IInterface 是一个接口,代表的是 远程server端对象所具有的能力
IXXX:
假设这个是根据我们写的AIDL文件生成的接口
它继承了 IInterface
放在aidl这个场景,就是声明这个需要跨进程传输的对象,它有哪些方法可供调用
IBinder:
IBinder 是一个接口,代表 跨进程传输 的能力,它的这种能力是底层Binder驱动支持的
在跨进程传输过程中,驱动会自动识别它的真实身份(本地对象,代理对象),从而自动转换
Binder:
Binder 继承自 IBinder,它实际就是 Binder本地对象(这里就能理解上面Binder实质含义所说的了
BinderProxy:
BinderProxy 也继承自 IBinder,它实际就是 Binder代理对象
它原先应该是 Binder 的一个内部类,现在应该是独立出来了
Stub:
Stub 是IXXX内部的一个抽象的静态内部类
它继承自 Binder 类 (继承|is a),表明它是一个具有跨进程传输能力的 Binder 对象
它实现了 IXXX 接口,但是对应的方法,并没有实现,因此真正使用的时候,我们需要继承它然后实现接口对应的方法
通过上面的 继承+实现,将AIDL中定义的方法,转变为了一个具有跨进程能力的类
Proxy:
Proxy 是IXXX内部的一个静态内部类
它持有一个外部传入的IBinder对象(组合|has a ),其实就是BinderProxy ,这里注意区分的是用了组合的形式
它也实现了 IXXX 接口,同时实现了对应的方法,外部对它的方法调用,它会转调给持有的BinderProxy 对象。
这是一个很典型的代理类,为什么要用组合而不是继承呢?
Default:
这是一个新增的东西,顾名思义,是提供 IXXX 接口的一个默认实现
当BinderProxy调用失败(IPC通信失败)的时候,就会转调这个默认的实现
使用者可以继承它,通过 setDefaultImpl 方法注入实例,自定义一些默认行为
以如下所示的AIDL文件为例,说明一次跨进程调用的过程
// Declare any non-default types here with import statements
interface ICompute {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
int add(int a,int b);
}
bindService -> onServiceConnected
onServiceConnected 回调里,方法参数中会拿到一个远程的 service
ComputeImpl -> asInterface
ComputeImpl 继承自 ICompute.stub,asInterface是一个静态的方法
可以看到它的内部实现,它会去判断(调用 queryLocalInterface 查找),传入的对象是一个binder本地对象,还是一个binder代理对象
//bindService
private void customBindService() {
mBind = false;
Intent intent = new Intent(this, ComputeService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.d(TAG, "onServiceConnected: ComputeService");
mComputer = ComputeImpl.asInterface(iBinder);
mBind = true;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.d(TAG, "onServiceDisconnected: ComputeService");
mComputer = null;
mBind = false;
}
}, Context.BIND_AUTO_CREATE);
}
//ComputeImpl -> asInterface
public static com.magic.tulensa.ICompute asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.magic.tulensa.ICompute))) {
return ((com.magic.tulensa.ICompute) iin);
}
return new com.magic.tulensa.ICompute.Stub.Proxy(obj);
}
上面讲到,如果是跨进程调用,那么Client实际拿到的是一个Proxy对象
所以下面讲一下 Proxy 对象是如何去调用真正的 ComputeImpl 的方法的
Proxy -> add
构造了两个 Parcel 对象,分别取去承接方法需要的入参和返回值
调用 BinderProxy 的transact 方法
如果调用失败,则调用 default 实现
BinderProxy -> transact
这是一个本地方法,它的实现在native层,具体来说在 android_util_Binder.cpp
它会调用 talkWithDrive 方法,这个方法又会通过系统调用 ioctl,Client 进程进入内核态执行代码,调用 add 方法的线程会挂起等待返回结果
而binder驱动完成一系列的操作之后,会唤醒 server 进程,调用 server 进程中对应的 binder 对象的 onTransact 方法
Binder(ComputeImpl) ->onTransact
每个aidl 方法都有一个编号,onTransact 方法是根据这个编号来做匹配的
同样是利用两个 Parcel 对象,分别取去承接方法需要的入参和返回值
这个方法会将结果返回给驱动,驱动唤醒client进程里面挂起等待的add方法的线程
一次跨进程调用就结束啦
//Proxy -> add
public int add(int a, int b) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(a);
_data.writeInt(b);
boolean _status = mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().add(a, b);
}
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
//BinderProxy -> transact
public native boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException;
//Binder -> onTransact
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_add: {
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
IActivityManager(android/app/IActivityManager.java)
这个实际就是上面所说的 IXXX,根据AIDL文件自动生成的接口
它里面也包含了 Default 、Proxy 等类
ActivityManagerService
它继承自 IActivityManager.Stub ,所以它就是 binder 本地对象了
ApplicationThread
继承自IApplication.stub,AMS通过它,反向调用目标进程的相关方法(四大组件的生命周期回调)
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback,
ActivityManagerGlobalLock {
//省略
}
Binder 的重要性不言而喻,本文系统的梳理了Binder相关的基础概念
包括IPC通信的含义,实现原理,binder 的含义,binder 通信过程中的角色及对应的类,aidl 通信过程的分析等等
但是这些都还是最基本的概念,要想深入理解Binder,需要去源码里面钻研
三、Binder 通信模型
五、Binder 的实质含义 – 2、面向对象的思想
六、解析JAVA层Binder
Binder设计与实现
《Android开发艺术探索》第二章:IPC机制
Binder 学习指南
为什么Android 要采用 BInder 作为 IPC 机制