在移动端越来越饱和的状态下,我们会发现出去找工作的时候,动不动就是你看过xx源码,你了解xx原理,其实这个时候,大家应该都想骂娘的吧。面试造火箭,工作拧螺丝,这句话大家都懂。与其说面试要求越来越高,不如换个角度想想,这也不乏是一种筛选人才的方式,大家都一样,招谁都一样,为啥要你呢?工作越来越久,我发现其实那些原理东西,对我们平时的开发还是很有帮助的,比如有时出现问题需要定位原因,你必须熟悉里面的原理,才能找到真正错误的原因,直至解决它。话不多说,接下来我们介绍一下Binder。
我们将从这几个方面学习一下Binder机制:
1.Binder是什么
2.Binder相比其他IPC方式有什么优势
3.Binder机制如何实现跨进程通信的
4.Binder机制在AIDL实现流程
了解Binder之前,我们先了解一下Linux基础知识。
进程隔离
我们知道操作系统中各个进程是相对独立的,这样做的目的是保护操作系统中进程互不干扰,避免A进程写入到B进程的情况发生。进程隔离的实现是通过虚拟地址空间,A、B进程虚拟地址不同,这样就防止A进程写入数据信息到B进程。我们知道在Android中,一个进程相当于一个应用,不同应用之间经常会通信,由于进程隔离的存在,这时候,需要用到一种特殊的通信机制即跨进程通信(IPC)。
用户空间/内核空间
Linux作为一个操作系统, Kernel是操作系统的核心,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。对于一个高级安全级别、核心的东西,显然不允许其他应用程序随便访问的,所以需要对Kernel有一套保护机制。这套保护机制,告诉那些应用程序,只能访问允许访问的资源,不允许访问的资源会被拒绝,于是就把上层的应用程序和Kernel抽象的隔离开,分别称之为User Space和Kernel Space。
系统调用
我们虽然把一个进程分为用户空间和内核空间,但是不可避免用户空间访问内核资源,比如写文件和访问网络等,而用户空间访问内核空间唯一方式就是系统调用。当应用程序想访问内核空间,必须要通过系统调用的方式去访问,避免用户程序越权访问,从而保障了系统的安全和稳定。
进程中用户空间和内核空间通过系统调用交互,主要用到两个函数。
1.copy_from_user():将用户空间的数据拷贝到内核空间
2.copy_to_user():将内核空间的数据拷贝到用户空间
Binder驱动
我们通过系统调用可以实现用户空间对内核空间的访问,但是用户空间之间的访问怎么实现呢,这里Linux系统内核为我们添加了支持:Linux的动态可加载内核模块(LKM)机制即模块是具有独立功能的程序,可以被单独编译,但不能独立运行,运行时被链接到内核作为内核的一部分在内核空间运行。在Android系统里面,我们通过添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁完成通信。
在Android系统中,这个运行在内核空间的,负责各个用户进程通过Binder通信的内核模块叫做Binder驱动。
Binder是什么
讲了那么多晦涩难懂的概念,我们捋一捋Binder究竟是什么呢,其实这个需要从不同的角度来看。
1.从IPC角度来讲,Binder是Android中一种跨进程通信的方式,实现不同应用之间的数据通信
2.从模块组成来讲,Binder是一种虚拟设备物理驱动,是Service Manager
连接各种Manager
和对应的ManagerService
的桥梁(后面会讲)
3.从面向对象和C/S模型来讲,Client
通过Binder和远程的Server
进行通讯(后面会讲)
Binder相比其他IPC有什么优势
Linux已经拥有管道,system V IPC,socket等IPC手段,却还要倚赖Binder来实现进程间通信,说明Binder具有无可比拟的优势。
和其他IPC相比较:
- 效率高:除共享内存外,其他IPC方式,需要两次数据拷贝,Binder通过内存映射,仅需一次拷贝,共享内存虽无需拷贝内存,但控制复杂,难以使用。
- 安全性好:传统IPC接送方无法获得对方可靠的UID/PID,因为只能由用户在数据包里填入UID/PID,这样不可靠,容易篡改,Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志,还有就是传统IPC访问接入点是开放的,无法建立私有通道。比如命名管道的名称,system V的键值,socket的ip地址或文件名都是开放的,只要知道这些接入点的程序都可以和对端建立连接,容易被恶意程序利用。
Binder机制如何实现跨进程通信的
先来看一张图
从上图可以看出,Binder机制通信过程包括4个角色:
Client:服务的请求方
Server:服务的提供方
ServiceManager(简称SM):为Client、Server分别提供查询和注册服务,Client、Server和SM之间的通信也是通过Binder
Binder驱动:负责Binder通信机制的建立,提供一系列底层支持
如上图所示,Binder通信过程是这样的:
1.Server在ServerManager中注册,Server进程在创建的过程中,同时会创建对应的Binder实体,并为其取一个字符形式,可读易记的名字,将这个Binder连同名字以数据包的形式通过Binder驱动发送给SM,通知SM注册一个名叫张三的Binder,它位于某个Server中。驱动为这个进程的Binder创建位于内核中的实体节点以及SM对实体的引用,将名字及新建的引用打包传递给SM。SM收数据包后,从中取出名字和引用填入一张查找表中。(引自Android Binder设计与实现)
2.Client通过Service Manager获取服务:Client知道服务中Binder实体的名字后,通过名字从ServiceManager获取Binder实体的引用。
3.Client使用服务与Server进行通信:Client通过调用Binder实体与Server进行通信。
用一段通俗易懂的话,来描述这个过程,我们可以将整个过程看作A、B两个人要通信,假设他们通过打电话联系,首先A需要知道B的电话,这个时候我们需要通过查询通讯录,查找B名字对应的号码,然后拨打电话,两个人就可以联系上了,在这之间我们知道,电话之间信息的传递需要依赖基站。我们可以把SM比作通信录,基站对应我们建立通信机制的驱动。至此,A、B通信的步骤如下:
1.SM建立(建立通讯录):首先一个进程向驱动提出申请为SM,驱动同意后,SM负责管理各个Service,此时建立了一张空白的通讯录。
2.各个Server向SM注册(完善通讯录):每个Server端进程启动之后,向SM报告,我叫张三,要找我返回一个地址0x1234;其他进程如此,这样就建立一个表存储了个Server的名字和地址,类似于,我叫张三,联系我请打10000这个电话。
3.Client和Server通信,向SM查询,我要找张三,请告诉我怎么联系,SM收到后返回一个地址0x1234这个地址给他;Client收到后,用这个号码拨通这个电话,这样就联系上了。(引自Binder学习指南)
我们了解了Binder通信过程,Binder通信的原理是咋样的呢?我们前面有说到过系统调用,两个进程通信,由于进程隔离的存在,需要借助内核帮助,通过系统调用进入内核态。然后内核程序在内核空间分配内存,开辟一块内核缓存区,调用copy_from_user() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中。同样的,接收方进程在接收数据时在自己的用户空间开辟一块内存缓存区,然后内核程序调用 copy_to_user() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区。这样数据发送方进程和数据接收方进程就完成了一次数据传输,我们称完成了一次进程间通信,这是我们传统的IPC通信过程,Binder使用的是另外一种方式内存映射。
内存映射:简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。
一次完整的 Binder IPC 通信过程通常是这样:
- 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
- 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
- 发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
Binder机制在AIDL实现流程
我们前面说了一大堆的原理、概念,接下来,我们看看在Android系统,代码中是怎么实现的。
我们首先新建一个binder文件,定义一个接口,计算两数之和的方法
interface IMyAidlTest {
int sum(int a, int b);
}
Build一下,系统会为我们生成一个对应的IMyAidlTest.java文件
public interface IMyAidlTest extends android.os.IInterface
{
/**IMyAidlTest默认实现*/
public static class Default implements com.ddup.binder.learn.IMyAidlTest
{
@Override public int sum(int a, int b) throws android.os.RemoteException
{
return 0;
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** 定义一个静态本地stub对象继承自Binder并且实现了对应的接口. */
public static abstract class Stub extends android.os.Binder implements com.ddup.binder.learn.IMyAidlTest
{
private static final java.lang.String DESCRIPTOR = "com.ddup.binder.learn.IMyAidlTest";
/** 将一个描述符Interface和当前binder绑定起来,为以后queryLocalInterface使用 */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* 通过这个方法在onServiceConnected调用返回一个远程服务,如果不是同一个进程返回代理对象
*/
public static com.ddup.binder.learn.IMyAidlTest asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.ddup.binder.learn.IMyAidlTest))) {
return ((com.ddup.binder.learn.IMyAidlTest)iin);
}
return new com.ddup.binder.learn.IMyAidlTest.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override 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_sum: // 调用sum方法
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.sum(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
default: // 默认调用父类onTransact方法
{
return super.onTransact(code, data, reply, flags);
}
}
}
// 代理类
private static class Proxy implements com.ddup.binder.learn.IMyAidlTest
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public int sum(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_sum, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().sum(a, b);
}
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public static com.ddup.binder.learn.IMyAidlTest sDefaultImpl;
}
static final int TRANSACTION_sum = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
public static boolean setDefaultImpl(com.ddup.binder.learn.IMyAidlTest impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.ddup.binder.learn.IMyAidlTest getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public int sum(int a, int b) throws android.os.RemoteException;
}
首先IMyAidlTest是个接口类继承自IInterface,IInterface也是个接口,里面只定义了一个asBinder()方法,通过这个方法可以返回当前关联的Binder对象。
public interface IInterface
{
/**
* Retrieve the Binder object associated with this interface.
* You must use this instead of a plain cast, so that proxy objects
* can return the correct result.
*/
public IBinder asBinder();
}
再看一个关键方法,asInterface(),我们在bindService时,在onServiceConnected调用这个方法,可以拿到一个远程服务Service,这个方法里面做了什么操作呢?
public static com.ddup.binder.learn.IMyAidlTest asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.ddup.binder.learn.IMyAidlTest))) {
return ((com.ddup.binder.learn.IMyAidlTest)iin);
}
return new com.ddup.binder.learn.IMyAidlTest.Stub.Proxy(obj);
}
从代码中可以看到,首先会判断当前binder对象是否为空,为空直接返回null,如果不为空,接着往下走,首先会调用queryLocalInterface方法查询当前IInterface,在这之前,我们可以看到,其实初始化stub的时候,已经调用attachInterface()方法设置了一个描述符DESCRIPTOR,接着往下,将查询到的IInterface对象和当前进程的IInterface对比,如果一致,代表在同一个进程直接强转返回一个Binder本地对象,不一致则代表当前client和server不在同一个进程,返回Binder对象的Proxy代理对象。
我们可以看到这个方法里面传人一个参数IBinder,这个IBinder究竟实现了那些东西呢?
可以看出IBinder就是个接口,Binder就是实现了它的接口,实现IBinder,相当于拥有一种跨进程通信的能力。
最后我们再看一下sum()方法是怎么实现的,我们知道调用远程服务的方法都是通过代理对象实现的,看下代理对象的sum()方法
@Override public int sum(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_sum, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().sum(a, b);
}
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
我们可以看到先通过Parcel序列化,然后把我们传人的参数写入data,把返回结果写入reply,最后调用mRemote.transact()方法
/**
* Default implementation rewinds the parcels and calls onTransact. On
* the remote side, transact calls into the binder to do the IPC.
*/
public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
最终调用的是Binder里面的transact()方法,接着往下看其实它调用了Binder本地对象的onTransact()方法并把结果返回
@Override 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_sum:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.sum(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
梳理一下,client发起请求调用server中的sum()方法,首先client和server不在一个进程,服务连接成功通过asInterface()方法获得Binder对象的proxy,然后调用 transact()
方法,由于是同步方法,此时线程阻塞,server收到IPC请求后,调用目标对象的 Binder.onTransact()
方法,然后返回带结果的 Parcel。并将结果返回给驱动,驱动唤醒挂起的Client进程里面的阻塞线程并将结果返回,一次跨进程调用就完成了。
最后总结一下,Binder机制是一种跨进程通信机制(IPC),相比于传统的IPC具有效率高、安全性好的特点。
一次通过Binder机制跨进程通信的流程如下:
- ServiceManager 初始化
- 当该应用程序启动时,SM向Binder驱动申请为服务管理者,Binder驱动同意后,并为SM新建 对应的 Binder 实体
- Server 向 ServiceManager 注册自己
- Server 向 Binder 驱动发起注册请求,Binder 为它创建 Binder 实体,然后如果 SM 中没有这个 Server 时就添加 Server 名称与 Binder 引用到它的 Binder 引用表中
- Client 获取远程服务
- Client 首先会向 Binder 驱动发起获取服务的请求,传递要获取的服务名称
- Binder 驱动将该请求转发给 ServiceManager 进程
- ServiceManager 查找到 Client 需要的 Server 对应的 Binder 实体的 Binder 引用信息,然后通过 Binder 驱动反馈给 Client
- Client 收到 Server 对应的 Binder 引用后,会创建一个 Server 对应的远程服务(即 Server 在当前进程的代理)
- Client 通过代理调用 Server
- Client 调用远程服务,远程服务收到 Client 请求之后,会和 Binder 驱动通信
- 因为远程服务中有 Server 的 Binder 引用信息,因此驱动就能轻易的找到对应的 Server,进而将Client 的请求内容发送 Server
Thanks: