Binder基本原理****IPC与Binder简介
进程是程序的实体,它是程序的一次运行活动,同时也是操作系统资源分配和调度的基本单位。在操作系统中运行着许许多多的进程,为了保证系统的有序运行和进程间互不干扰,操作系统引入了进程隔离的概念来确保不同进程之间相互独立。进程隔离使用了虚拟地址空间技术,该技术通过为不同的进程分配不同的虚拟地址,使得对于每个进程来说都以为自己独享了整个系统,完全不知道其他进程的存在,这样就避免了进程间错误的相互写入数据而导致进程无法正常运行。然而,虽然进程隔离能够确保每个进程的数据安全,不被恶意破坏,但毕竟操作系统是一个有机的的统一整体,就像人体样,虽然人体的各个器官也是相互独立,但是若要完成某一个行为,就需要在大脑的控制下对相关器官进行调配,同时器官之间也会相互传递信号,操作系统亦是如此。操作系统是管理计算机硬件与软件资源的计算机程序,它由内核、驱动程序、接口库及外围组成,其中,内核是操作系统的核心,拥有访问受保护的内存空间和访问底层硬件设备的所有权限。当操作系统需要执行某个任务时,必然需要系统中相关进程在内核的控制下进行协作,既然是相互协作,就必然牵涉到进程间的数据交互,为了实现这个目的,跨进程通信技术开始"闪亮登场"。IPC,跨进程通信跨进程通信(IPC,Interprocess Communication)是一组编程接口,它允许在一个操作系统中不同进程之间传递或交换信息,其存储-转发方式通信过程大致为:假设有两个运行在用户空间的进程A、B,进程A要给进程B发送数据,那么进程A会通过系统调用copy_from_user将数据copy到内核空间,然后把内核空间通过系统调用copy_to_user将对应的数据copy到进程B即完成。下图为IPC通信模型:
虽然Android系统是基于Linux系统实现的,但它并没有使用上述的5种方式作为系统的进程间通信方式,主要是由这几种方式要么是开销过大,要么就是安全性低。因为Android系统作为一种嵌入式系统,设备资源相对有限,因此对相关的性能要求也非常高,过大的开销会严重影响系统的运行性能。另外,Android系统是开放式的,拥有众多的开发者平台,应用程序的来源也非常广泛,同时传统的IPC方式无法获得对方进程可靠的UID/PID进行身份校验,这会直接影响智能设备的安全。基于此,Android系统建立了一套新的IPC机制来满足系统中对较高的传输性能和安全性通信要求,这种Android特有的IPC机制就是Binder机制。Binder是基于OpenBinder实现的,OpenBinder由Google公司的Dianne Hackborn开发,旨在提供一个简单的进程间互相通讯的途径。Binder机制采用C/S通信模型,它使用Binder来作为Server服务对外的访问接入点和Client向Server发起服务请求的"地址",并且在进程间通信的过程中,数据传输只需拷贝一次,并且Client的身份验证标志(UID/PID)只能由Binder机制在内核中添加,因此具有安全性高、传输性能好等优势。与其他IPC机制不同,Binder使用了面向对象的思想来描述Server端的访问接入点和Client端的服务请求发起地址,具体来说就是Server端的访问接入点实质是位于Server进程中的一个Binder实体对象,该对象提供了一套方法用于向Client端提供各种服务;Client端的“地址”即为Binder实体对象的引用,Client将持有该引用向Server端发起服务请求。下图为Binder机制的C/S模型:
-
首先,系统中某个进程向Binder驱动发起申请为ServerManager进程,该进程将建立一张系统中相关Server进程“名字”及其“地址”的映射表(查询表);
-
其次,Server进程将自己“名字”和“地址”添加到ServerManager进程的查询表中;
-
最后,Client进程从ServerManager进程获取到远程Server进程的真实“地址”,即建立Binder通信完毕。
Binder机制原理前面谈论到大部分传统的IPC都是基于存储-转发的方式实现的,即进程A通过系统调用copy_from_user将数据从用户空间拷贝到内核空间,然后再通过系统调用copy_to_user将数据拷贝从内核空间拷贝到进程B,整个通信过程数据需要拷贝2次。但是Binder机制却不是这么做的,而是把所有的工作均交给Binder驱动来完成,并且Binder本质上只是一种底层通信方式和具体服务没有关系。为了提供具体服务(或称能力),Server必须提供一套接口函数以便Client通过远程访问使用各种服务,这里使用代理(Proxy)模式来实现,即将接口函数定义在一个抽象类中,Server和Client均以该抽象类为基类实现所有的接口函数,其中Server端是真正的功能实现并为每个函数进行一一编号以便Client精准调用,而Client端则是对这些函数远程调用请求的包装。此外,Server端还定义了一个Binder抽象类来处理来自Client的Binder请求数据包,其中最重要的成员函数是虚函数onTransact(),该函数将用于分析收到的数据包,调用相应的接口函数处理请求,并将最终的调用结果返回给Client。整个通信过程都是在Binder驱动的控制下完成的,并且Binder在Server中的实体是通过采用继承方式以接口类和Binder抽象类为基类构建的。接下来,我们借助Binder学习指南一文中的一张图来详细剖析下Binder机制通信过程,了解Binder驱动、ServerManager(SM)在整个通信过程中所起的作用。
![](https://img.haomeiwen.com/i11754331/b33635afbb4b81d0.gif)
- IIterface
IIterface是Android提供的一个接口,它表明远程Server对象具有什么样的能力,可理解为Server和Client契约,Binder本地对象和Binder代理对象均需实现该接口。Interface接口中只包含一个asBinder方法,该方法用于返回与该IInterface绑定的Binder本地对象。IIterface源码如下:
public interface IInterface{ // 返回与该IIterface绑定的Binder实体对象 public IBinder asBinder();}
- IBinder
代表了一种跨进程传输的能力,实现该接口就能将这个对象进行跨进程传递,IBinder负责数据传递。在跨进程数据流经驱动的时候,驱动会识别IBinder类型的数据,从而自动完成不同进程Binder本地对象以及Binder代理对象的转换。
public interface IBinder { // 代码省略 ... // 返回绑定在该Binder对象的IInterface有关的描述 public @Nullable String getInterfaceDescriptor() throws RemoteException; // 判断Binder通信链路是否断开 public boolean isBinderAlive(); // 获取descriptor对应的本地IIterface,如果返回为空,说明 // Client和Server归属于不同进程 public @Nullable IInterface queryLocalInterface(@NonNull String descriptor); // 代码省略 ... // 处理Binder请求 // code:要执行的函数编码 // data:函数参数 // reply:返回值 // flags:附加标志,暂时忽略 public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)throws RemoteException; // Binder链接死亡(断开)回调接口 public interface DeathRecipient { public void binderDied(); } // 注册Binder链接断开通知 // 即当Binder链接断开时,DeathRecipient接口的binderDied方法会被回调 public void linkToDeath(@NonNull DeathRecipient recipient, int flags) throws RemoteException; // 移除已注册的Binder链接断开通知 public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags);}
- Binder
Binder实体对象,位于Server进程中,它继承了IBinder,从而具有跨进程传输的能力,但在实际通信过程中,Binder实体对象并没有传输,传输的只是该对象的引用。
- Binder代理对象
远程进程Binder对象的代理,位于Client进程中,它持有IBinder引用,也可以理解拥有跨进程传输的能力。AIDL工作原理AIDL(Android Interface Definition Language),即Android接口定义语言,是Android系统为了便于开发具备跨进程通信的应用,专门提供的且用于自动生成Java层Binder通信框架的技术。AIDL的使用比较简单,我们只需要编写符合AIDL开发规范的源码文件,AS就会自动生成用于跨进程通信的相关文件。这里以Server端向Client提供"加法计算"服务(能力)为例,详细剖析Java层Binder通信框架原理。首先,我们在工程中创建一个以".aidl"为后缀的源文件,文件命名为IComputeInterface,并提供一个add方法;
package com.jiangdg.hellobinder;interface IComputeInterface { int add(int a,int b);}
其次,"Build->make project"工程后AS就会自动在"app/build/generated/…/com/jiangdg/hellobuilder"目录生成一个名为IComputeInterface.java接口文件。代码框架大致如下:
public interface IComputeInterface extends android.os.IInterface{ // Binder实体 public static abstract class Stub extends android.os.Binder implements com.jiangdg.hellobinder.IComputeInterface{ ... // Binder实体的代理对象 private static class Proxy implements com.jiangdg.hellobinder.IComputeInterface{ ... } } // 公共方法 public int add(int a, int b) throws android.os.RemoteException;}
从IComputeInterface.java源码我们可以大致看出,它主要包含三部分,即接口IComputeInterface,静态抽象类Stub、静态类Proxy,并由此构成Java层的Binder通信框架。接下来,我们就来分析它们之间有何关联以及起到的作用是什么?(1) IComputeInterface:公共接口IComputeInterface继承于接口IInterface,它包含一个add方法且抛出RemoteException异常,由此可知add方法应该是一个被远程访问的方法。根据Binder机制原理,我们自然容易明白IComputeInterface接口就是一个“契约”接口,它表明Server端能够像Client端提供哪些服务,也是Binder机制中的代理访问的实现基础,Server中的Binder实体和Client中的Binder实体的代理均需要实现它,其中,在Binder实体中为add方法真正的实现,在Binder实体的代理对象中只是对add方法的远程调用请求包装,在接下来的分析中可以验证这一点。
public interface IComputeInterface extends android.os.IInterface{ public int add(int a, int b) throws android.os.RemoteException;}
(2) IComputeInterface.Stub:Binder本地对象从 IComputeInterface的源码可知,Stub是IComputeInterface的一个静态抽象内部类,但是这不是关键的,也仅是AIDL中的生成代码的一种形式而已,真正重要的是Stub继承了Binder类和IComputeInterface接口,而Binder又继承于IBinder。根据Binder机制原理,我们就可以得出Stub就是位于Server端中的Binder实体或称Binder本地对象,它的源码如下:
public static abstract class Stub extends android.os.Binder implements com.jiangdg.hellobinder.IComputeInterface{ // 表明IComputeInterface是本地接口描述 private static final java.lang.String DESCRIPTOR = "com.jiangdg.hellobinder.IComputeInterface"; // 将Stub本身(即Binder实体对象)绑定到接口Interface public Stub(){ this.attachInterface(this, DESCRIPTOR); } // 将Binder实体对象转换为IInterface,即创建Binder实体的代理对象,如果需要的话 public static com.jiangdg.hellobinder.IComputeInterface asInterface(android.os.IBinder obj){ if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.jiangdg.hellobinder.IComputeInterface))){ return ((com.jiangdg.hellobinder.IComputeInterface)); } return new com.jiangdg.hellobinder.IComputeInterface.Stub.Proxy(obj); } // 返回Binder实体对象本身 @Override public android.os.IBinder asBinder(){ return this; } // 分析处理Binder请求数据包,根据code找到相应的节点 // 调用相应的方法处理请求 @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{ 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; } } return super.onTransact(code, data, reply, flags); } static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);}
接下来,我们着重分析下Stub类的工作原理,这里从asInterface方法入手,该方法主要的作用是判断Binder驱动传递过来的Binder对象obj类型,来决定是否想需要返回Binder对象的代理对象Proxy,即调用obj的queryLocalInterface方法判断传入的DESCRIPTOR(接口描述)是否与obj绑定的一致,如果一致,则表明Client与Server归属于同一个进程,直接将Binder本地对象返回给Client;如果不一致,则表明Client与Server不属于同一个进程,就需要将obj作为参数实例化一个Binder本地对象的代理Proxy给Client。onTransact方法用于分析处理Binder驱动发来的请求数据包,如果Client与Server属于同一个进程,当Client要访问Server中的add方法时,onTransact就会被直接调用,不再经历代理调用步骤。(3) IComputeInterface.Stub.ProxyProxy是Stub的一个静态内部类,同样也不是关键的,仅是AIDL中的生成代码的一种形式而已,真正重要的是Proxy继承了IComputeInterface接口,并持有一个IBinder对象的引用。根据Binder机制原理,我们就可以得出Proxy就是位于Client端中的Binder本地对象的代理。从Proxy的源码可知,它不仅持有远程Binder实体的引用,还重写了公共方法add,该方法将Client要访问远程方法的参数封装在Parcel对象中,然后调用远程Binder实体的transact方法发起跨进程调用。通过查看Android源码可知,transact方法的实现位于native层,它最终调用talkwithDriver函数将请求参数打包交给Binder驱动,Binder驱动识别后,最终会调用远程Server中Binder实体的onTransact方法,即Stub的onTransact方法进行处理。需要注意的是,在实际开发中,Stub中onTransact方法所调用的add方法将由我们自己实现,待add执行完毕会将结果填入Parcel中以便Binder驱动返回给Client。Proxy源码如下:
private static class Proxy implements com.jiangdg.hellobinder.IComputeInterface{ // 远程Binder实体对象 private android.os.IBinder mRemote; Proxy(android.os.IBinder remote){ mRemote = remote; } // 返回与代理对象对应的Binder实体 @Override public android.os.IBinder asBinder(){ return mRemote; } public java.lang.String getInterfaceDescriptor(){ return DESCRIPTOR; } // 对远程调用的封装 @Override 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); // 调用远程Binder实体的transact方法 // 开始处理访问请求 mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; }}
至此,关于对Binder机制的分析将告一段落,最后,我们采用Android Binder设计与实现中的一段原话为Binder机制作个总结:Binder模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中,形形色色的Binder对象及其星罗棋布的引用仿佛粘接各个应用程序的胶水,这也是Binder在英文的原意
网友评论