在从Android 6.0源码的角度剖析Activity的启动过程一文(https://blog.csdn.net/AndrExpert/article/details/81488503)中,我们了解到Activity的启动过程最终由系统服务ActivityManagerService完成,ActivityManagerServer是继承于Binder且运行在系统进程中,Activity的启动实质是一次基于Binder机制的跨进程通信。除此之外,Android系统还为应用程序提供了各种系统服务,比如多媒体播放、音视频获取、读取传感器数据等,它们之间的交互都是由Binder机制实现的。那么,所谓的Binder究竟是什么?实际上,Binder并不是一个确切的概念,它在不同的层次表述大不相同,比如通常意义下,Binder指Android系统中特有的一种跨进程通信方式;从用户空间的角度来说,对于Server进程,Binder指Binder本地对象,而对于Client进程,Binder指Binder代理对象;从内核空间的角度来说,Binder指Binder驱动;从传输通信的角度来说,Binder是可以跨进程传递的对象。
作者 | 蒋东国
责编 | 屠敏
出品 | CSDN博客
Binder基本原理
IPC与Binder简介
进程是程序的实体,它是程序的一次运行活动,同时也是操作系统资源分配和调度的基本单位。在操作系统中运行着许许多多的进程,为了保证系统的有序运行和进程间互不干扰,操作系统引入了进程隔离的概念来确保不同进程之间相互独立。进程隔离使用了虚拟地址空间技术,该技术通过为不同的进程分配不同的虚拟地址,使得对于每个进程来说都以为自己独享了整个系统,完全不知道其他进程的存在,这样就避免了进程间错误的相互写入数据而导致进程无法正常运行。然而,虽然进程隔离能够确保每个进程的数据安全,不被恶意破坏,但毕竟操作系统是一个有机的的统一整体,就像人体样,虽然人体的各个器官也是相互独立,但是若要完成某一个行为,就需要在大脑的控制下对相关器官进行调配,同时器官之间也会相互传递信号,操作系统亦是如此。操作系统是管理计算机硬件与软件资源的计算机程序,它由内核、驱动程序、接口库及外围组成,其中,内核是操作系统的核心,拥有访问受保护的内存空间和访问底层硬件设备的所有权限。当操作系统需要执行某个任务时,必然需要系统中相关进程在内核的控制下进行协作,既然是相互协作,就必然牵涉到进程间的数据交互,为了实现这个目的,跨进程通信技术开始"闪亮登场"。
IPC,跨进程通信
跨进程通信(IPC,Interprocess Communication)是一组编程接口,它允许在一个操作系统中不同进程之间传递或交换信息,其存储-转发方式通信过程大致为:假设有两个运行在用户空间的进程A、B,进程A要给进程B发送数据,那么进程A会通过系统调用copy_from_user将数据copy到内核空间,然后把内核空间通过系统调用copy_to_user将对应的数据copy到进程B即完成。下图为IPC通信模型:
用户空间和内核空间是人们从逻辑上抽离出来的概念,旨在区分操作系统中普通的应用程序和内核。内核是操作系统的核心,它拥有访问受保护内存空间和底层硬件设备的所有权限,维持着整个操作系统的正常运行。为了保护内核不受破坏,普通的应用程序被授予有限的资源访问权限,如果普通的应用程序需要访问受限的资源,就需要通过系统调用通过内核来访问这些被保护的资源。用户空间访问内核空间通过系统调用实现,用户空间访问用户空间则需要通过内核模块/驱动来实现。
Android系统是基于Linux内核实现的,自然支持Linux系统中的IPC方式,这些方式包括管道、System V IPC(包括消息队列/共享内存/信号灯)和socket。其中,管道是一种半双工的通信方式,数据只能单向流通且只能在具有父子进程关系的进程间使用;System V IPC是Linux系统为了弥补管道在进程间通信的不足所引入,它包括消息队列、信号灯和共享内存三种进程间通信机制,它们共享通用的认证方式,即进程在使用某种类型的IPC资源以前,必须通过系统调用传递一个唯一的引用标识符到内核来访问这些资源;Socket套接字是一种通用的接口,用于跨网络的进程间通信和本机上进程间的低速通信,并且支持Client-Server的通信方式。
下表为上述5种IPC方式区别:
(1) C/S结构:Client-Server结构是一种网络架构,它把客户端与服务器区分开来。客户端发送请求到服务器,服务器接收并处理请求,然后返回结果给客户端。在Android系统中,大部分系统服务都是通过这种架构为应用程序提供服务,从而让应用程序拥有丰富的功能。
(2) 存储-转发方式:数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,因此整个过程需要拷贝两次。
Binder简介
虽然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机制的底层代码由C实现,但面向对象思想的引入将进程间通信转化为通过对某个Binder对象的引用并调用该对象的方法,而其独特之处在于Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,而它的引用却遍布于系统的各个进程之中。
Binder通信框架
基于Binder机制的进程间通信,其通信框架主要涉及四个角色,即Server进程、Client进程、ServerManager进程以及Binder驱动,其中,Server、Client和ServerManager运行在用户空间,Binder驱动运行在内核空间。Binder驱动是Binder通信框架的核心,它工作于内核空间,主要负责进程间Binder通信的建立、Binder在进程之间的传递、Binder引用计数管理以及数据包在进程之间的传递和交互等一系列底层支持;Server进程用于向Client进程提供远程服务,该进程会创建一个Binder实体(对象),并为其取一个字符串形式的名字,当该Binder实体被Binder驱动在ServerManger进行实名注册后,我们又称这个Binder实体为“实名Binder”,它将担负起向Client提供具体的远程服务;Client进程即为我们的APP,它将通过远程Binder实体的引用访问远程Server,获取相关的服务,这里为什么不是直接通过Binder实体访问,我们在后面的Binder机制原理再详述;ServerManager是一个系统进程,它管理着一张“查询表”,该表记录了Server进程中Binder实体名字到Client中对该Binder实体引用的对应关系,以便使Client能够通过Binder名字获得对Server中Binder实体的引用。Binder通信框架结构图如下:
在Binder机制通信模型中,Server、Client、ServerManager以及Binder驱动的关系有点类似于互联网,它们分别对应于互联网中的服务器、客户端、域名服务器(DNS)以及路由器,其中,服务器用于向客户端提供服务且对外接入点为IP地址;客户端用于向服务器发起请求服务,且发起"地址"通常为域名;域名服务器提供远程服务器的IP地址与其域名映射关系,便于客户端能够通过域名直接访问服务器;路由器用于网络管理、数据处理等,是互联网络的枢纽。Binder通信框架流程图如下:
大致过程为:
首先,系统中某个进程向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)在整个通信过程中所起的作用。
为了深入理解基于Binder机制的进程间通信原理,这里假设Server进程中有个Object对象(即Binder实体),它提供一个add方法供远程调用,Client进程将通过Binder机制的方式访问Server进程中Object对象的add方法,具体的通信过程如下:
Server在SM中注册实名Binder
在Server进程中有一个Object对象(即Binder实体,下述均以Binder实体描述),它提供一个add方法,为了Client能够找到自己并与之通信,Server进程为Binder实体创建了一个字符形式的名字,然后再将Binder实体及其名字以数据包的形式通过Binder驱动发送给ServerManager进行注册,其注册过程为:
首先,Binder驱动接收到Server发过来的数据包后,会取出数据包中的Binder实体及其名字,并为该Binder实体创建位于内核中的实体节点,并生成与其名字对应的引用;
然后,Binder驱动将Binder的名字和新建的引用打包传递给ServerManager,ServerManager收到数据包后从中取出名字和引用填入一张查找表中,这张表就像一个“通讯录”且是系统唯一的,它记录了系统中各种Server的名字(Binder实体字符形式名字)和地址(Binder实体的引用),而被注册的这个Binder实体也称为实名Binder。
Client从SM获得实名Binder的引用
Client进程要想访问Server进程的Binder实体的add方法,会将要访问Binder实体的名字以数据包的形式发送给Binder驱动,Binder驱动取出名字查询ServerManager中的查询表即可获得Binder实体及其引用。但是Binder驱动并没有将真正的Binder实体返回给Client,而是“照着”Binder实体的样子仿造一个一模一样的对象作为代理返回给Client,这个对象又被称之为代理对象ProxyBinder,它持有Binder实体的引用,且拥有与Binder实体完全相同能力(即方法),只是这些能力只是个“空壳”,真正的具体实现还是在Server进程的Binder实体中。由于驱动返回的代理对象(ProxyBinder)与Server进程中的Binder实体如此相似,给人的感觉好像是直接把Server进程Binder实体(对象)传递到了Client进程,因此,我们可以说Binder对象是可以跨进程传递的对象,而实际上Binder对象并没有传递,传递的仅仅是Binder对象的引用,它将通过代理对象来承载。也就是说,Server进程中Binder对象指Binder实体(也称Binder本地对象),Client进程中Binder对象指的是Binder代理对象。在Binder对象进行跨进程传递的时候,Binder驱动会自动完成这两种类型的转换。
需要注意的是,Binder驱动返回一个Binder实体的代理对象给Client是基于Client与Server归属于不同进程而言的,如果Client和Server归属于同一个进程,Binder驱动将直接将Server进程的Binder实体返回给Client。由于本文主要是考虑Client与Server归属于不同进程情况,因此待Client获得代理对象ProxyBinder的那一刻,基于Binder机制的Client与Server远程通信链路建立完毕。
Client与Server跨进程通信
在Binder驱动返回一个Binder的代理对象给Client进程后,Client进程就可以通过该代理对象与远程Server进程进行通信。Binder代理对象继承了Server提供的公共接口类并实现公共函数(注:并不是真正的实现,真正的实现在Server进程的Binder实体中),是对远程函数调用的包装。接下来,我们分析下Client访问Server中方法通信过程:(1) Client进程首先会将函数参数和Binder实体的引用以数据包的形式进行打包,然后将数据包发送给Binder驱动向指定Server发送请求,此后Client进程进入挂起状态,以等待返回值;(2) Binder驱动收到Client进程发送过来的数据包后,取出Binder实体的引用,获得目的Server并将其唤醒,再将数据包发送给它处理。这里需要提下的是,由于该引用本来就是Binder驱动创建并交给ServerManager注册用的,因此Binder驱动自然很容易就能够通过该引用找到能够接收数据包的Server和获得指向Binder实体对应的内存空间;(3) Server进程收到Binder驱动发送过来的Binder请求数据包后,Server进程会利用之前开辟好线程池中的线程来处理该请求,通过调用Binder实体中的onTransact()函数,对收到的数据包进行分析,即取出数据包中请求的函数接口编码,case-by-case地解析code值,待解析成功后,再从数据包中取出函数参数并调用相应的接口函数处理请求,然后Server进程会将调用结果发送给Binder驱动;(4) Binder驱动收到Server进程返回的调用结果后,就会唤醒处于等待中的Client进程,并将结果返回给它,至此,一次跨进程通信完毕。
Java层Binder框架解析
在Android系统中,Binder框架由C/C++底层和Java上层构成,其中C/C++底层提供功能实现,Java上层为应用进程间的通信提供接口。由于C/C++层底层实现极其复杂,本文暂不涉及这部分内容,本节将详细剖析Java层部分。根据Binder机制原理,Binder框架Java层部分主要包含四部分,即公共接口(IIterface)、Binder接口(IBinder)、Binder实体(或称Binder本地对象)以及Binder代理对象(BinderProxy),它们各自的作用如下:
IIterface是Android提供的一个接口,它表明远程Server对象具有什么样的能力,可理解为Server和Client契约,Binder本地对象和Binder代理对象均需实现该接口。Interface接口中只包含一个asBinder方法,该方法用于返回与该IInterface绑定的Binder本地对象。IIterface源码如下:
public interface IInterface
{
// 返回与该IIterface绑定的Binder实体对象
public IBinder asBinder();
}
代表了一种跨进程传输的能力,实现该接口就能将这个对象进行跨进程传递,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实体对象,位于Server进程中,它继承了IBinder,从而具有跨进程传输的能力,但在实际通信过程中,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.Proxy
Proxy是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在英文的原意。
版权声明:本文为 CSDN 博主「无名之辈FTER」的原创文章。
扫描下方二维码,查看博主精彩分享!
热 文 推 荐
☞
点击阅读原文,查看博主原文。