Binder解析
前言
本文参考Carson_Ho的《图文详解 Android Binder跨进程通信的原理》。
1 什么是Binder?
说到Binder,有些人可能会说Binder是Android在解决进程通信间通信的技术,是一个类实现了Ibinder接口,是一个虚拟设备驱动等等,其实这些说法都没有错,如果把这些说法归结起来可以有以下的定义:
Binder定义.png
可以这样说:站在不同的角度上,Binder有不同的定义。
2知识铺垫
在Linux中将一个进程空间分为两部分:用户空间和内核控件。
用户空间:用户空间的数据不可以共享
内核空间:内核空间的数据可以共享
那么一个进程中的两部部分如何进行通信呢?其实是有copy_from_user和copy_to_user来实现的。
copy_from_user:将用户空间的数据拷贝到内核空间
copy_to_user:将内核空间中的数据拷贝到用户空间
那么Linux中的进程间通信的原理可以由下图来表示
示意图.png
这里需要注意的是:所有的进程共用一个内核空间
2.1 进程隔离和IPC
进程间通信.png进程隔离:为了保证进程的独立性&安全性,Android不允许进程之间直接相互访问或通信,保证了了每一个是相互隔离,独立的。
IPC:进程间通信
那么IPC是如何实现的呢?
进程间的通信原理如图所示,
1.发送进程通过系统调用方法copy_from_user将需要传递的数据拷贝到内核空间的缓存区中。
2.内核服务程序唤醒接收进程的接收线程,通过系统调用(copy_to_user)将内核缓存区中的数据拷贝到接收进程的用户空间。
3.拷贝完成,实现了进程间的通信。
这里要说明的有以下两点:
1.进程间无法直接进行数据的交互,数据的交互需要通过内核空间进行。
2.Linux的进程间通信调用了两次数据拷贝的系统调用。
3.接收数据的缓冲区大小由接收进程决定。
缺点:
1.接收进程的接收缓冲区大小不定。
2.效率低下,单向数据通信需要进行两次数据拷贝(用户->内核->用户)
2.2 Binder模型
Android为了解决Linux中进程间通信中的缺点,提出了Binder模型机制,该模型机制的原理如下图所示:
Binder机制模型.png
Binder模型属于C/S模式,各部分的说明如下:
角色 | 说明 | 备注 |
---|---|---|
Client进程 | 使用服务的进程 | Andorid客户端 |
Server进程 | 提供服务的进程 | 服务器端 |
ServiceManager | 管理服务进程的注册和查询(将客户端提供的Bidner字符串转换为相应服务的Bidner引用) | 相当于一个DNS服务器 |
Binder驱动 | 虚拟的设备驱动,将Client进程,Server进程和ServiceManager进程联系起来 具体作用:1传递进程服务消息(服务进程注册,客户进程获取服务)2.传递进程间需要传递的数据,通过内存映射3.实现线程控制:采用binder连接池,并由binder驱动自身管理 | binder驱动持有所有服务进程在内核进程中的binder实体,在客户进程使用时将对应的binder字符串转换为binder引用。 |
在Binder模型中的重要角色就是Binder驱动,它将其余的各个部分联系起来,形成了Bidner跨进程通信模型。那么Binder驱动是如何工作的呢?
2.2.1 Binder驱动
定义:
Binder驱动示意图.png
核心原理:
binder驱动核心原理png.png
过程说明:
1创建内核空间的数据缓冲区
2并根据需要映射的接收进程的信息通过地址映射实现内核缓冲区和接收进程用户空间地址映射到同一个接收缓冲区(这时只是进行接收缓冲区的创建和完成地址映射,并没有进行数据的读写操作)
3.发送进程通过copy_from_user将需要传输的数据发送到内核缓冲区中
4由于内核缓冲区和接收进程用户空间地址映射到了同一个接收缓冲区,相当于将信息发送到了接收进程用户地址空间。即实现了进程间通信。
优点:
1.传输效率高,单项通信数据拷贝次数少(1次),内核空间与用户空间通过共享对象直接交互。
2.为接收进程创建了大小不确定的接收缓冲区。
2.2.2Binder模型工作过程说明
步骤 | 过程说明 | 备注 |
---|---|---|
服务进程注册服务 | 1.服务进程向binder驱动发送注册服务请求2.binder驱动将请求转发到serviceManager3.serviceManager进程添加服务进程(注册服务进程) | 此时serviceManager拥有了服务进程的信息 |
客户进程获取服务 | 1.客户进程向binder驱动发送获取服务请求2.binder驱动将请求转发给serviceManager3.serviceManager查找客户进程需要的服务进程信息4.serviceManager将找到的服务进程信息通过binder驱动传递给客户进程。 | 此时客户进程与服务进程建立了联系 |
客户进程使用服务(1) | binder驱动为跨进程调用做准备(系统调用mmap) | 1.binder驱动创建接收缓冲区2.通过serviceManager提供的服务进程信息地址映射将服务进程用户空间地址与内核缓冲区映射到同一个接收缓冲区(即Binder驱动创建的接收缓冲区) |
客户进程使用服务(2) | 客户进程将参数传递到服务进程 | 1.服务进程调用copy_from_user将数据传递到内核缓冲区(当前进程被挂起)2.内核缓冲区与服务进程用户空间地址映射到同一个接收缓冲区,相当于参数被传递到了服务进程的用户空间3.binder驱动通知服务进程进行解包 |
客户进程使用服务(3) | 服务进程根据客户进程要求调用目标方法 | 1.收到binder解包通知后,server从binder线程池中取出线程,进行解包和方法调用2.将方法结果写到自己的共享内存中 |
客户进程使用服务(4) | 服务进程将目标方法的结果返回给客户进程 | 1.将结果写入映射的用户空间地址指向的内存区域中2.由于内核缓冲区与服务进程用户空间地址通过地址映射映射到同一个接收缓冲区,相当于目标方法的结果传递到了内核缓冲区,3.Binder驱动通知客户进程取出结果4.客户进程通过系统调用copy_to_user将内核缓冲区的目标结果拷贝到客户进程用户空间 |
优点:
1.传输效率高,单项通信数据拷贝次数少(1次),内核空间与用户空间通过共享对象直接交互。
2.为接收进程创建了大小不确定的接收缓冲区。
2.3额外说明:
(1)客户进程,服务进程,ServiceManager进程均属于用户空间不能直接相互通信。Binder驱动属于内核空间,可以进行用户空间/内核空间交互。
(2)Binder和ServiceManager属于Android基础架构不需要开发者去实现,开发者需要关心的是自定义客户进程和服务进程,借助基础架构就能实现跨进程通信。
(3)服务进程会创建多个线程来处理Binder请求(默认16个),Binder模型的线程管理 采用Binder驱动的线程池,并由Binder驱动自身进行管理(不是服务进程进行管理)
Binder机制 在Android中的具体实现原理
Binder机制在Android中主要依靠Binder类进行实现,该类实现了IBinder接口。
实例说明:Client进程 需要调用 Server进程的加法函数(将整数a和b相加)
Client进程提供相加的数字a和b给服务进程
Server进程负责将两个数相加并将结果返回给Client进程
1.Server进程注册服务
过程描述
Server进程 通过Binder驱动 向 Service Manager进程 注册服务
代码实现
Server进程 创建 一个 Binder 对象
该Binder对象是Server进程在Binder驱动中的存在形式
该Binder对象保存着Server进程和ServiceManager的信息(保存在内核空间中)
当客户进程获取服务时,通过该Binder找到对应的服务进程
以下代码是系统根据AIDL文件生成的接口代码:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: D:\\code\\BinderServerDemo\\app\\src\\main\\aidl\\com\\zhqy\\binderserverdemo\\AddInterface.aidl
*/
package com.zhqy.binderserverdemo;
// Declare any non-default types here with import statements
public interface AddInterface extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.zhqy.binderserverdemo.AddInterface {
private static final java.lang.String DESCRIPTOR = "com.zhqy.binderserverdemo.AddInterface";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.zhqy.binderserverdemo.AddInterface interface,
* generating a proxy if needed.
*/
public static com.zhqy.binderserverdemo.AddInterface asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.zhqy.binderserverdemo.AddInterface))) {
return ((com.zhqy.binderserverdemo.AddInterface) iin);
}
return new com.zhqy.binderserverdemo.AddInterface.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 {
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);
}
private static class Proxy implements com.zhqy.binderserverdemo.AddInterface {
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 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);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public int add(int a, int b) throws android.os.RemoteException;
}
下面对相关方法进行说明:
DESCRIPTOR:Binder的唯一标识,一般用类名进行标识
asInterface(android.os.IBinder obj):用于将Binder对象转换成客户进程需要的AIDL接口类型的对象,该方法区分所在的进程,如果服务进程和客户进程在同一个进程中则返回服务端Stub对象本身,否则返回的是服务进程Stub的代理类Stub.proxy对象。
asBinder:返回Binder对象。
onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags):这个方法在运行在服务进程的binder线程池中,当客户进程发起获取服务时,获取服务的请求会由系统底层封装后交给此方法执行。其中code表示调用的是哪一个目标方法,data为执行该方法所需要的参数(如果有的话),reply为执行完该目标方法时,如果有返回值的话将返回值写入reply。返回值表示该方法是否调用成功,如果返回false则客户进程调用失败。
注册服务后,Binder驱动持有 Server进程创建的Binder实体。
proxy#add(int a, int b):该方法会在客户进程中调用,当客户端获取服务时,首先会创建输入行Parcel对象_data和输出项_reply和返回值对象_result,将远程过程调用(RPC)所需要的参数写入_data中,然后执行transact方法进行远程过程调用(客户进程被挂起),服务进程执行onTransact方法,当服务进程返回目标方法执行的结果后,唤醒客户进程的transact方法,将服务进程返回的执行结果写入_result,最后返回_result。
2.客户进程获取服务
过程:客户进程在使用服务进程的目标方法时,须通过Binder驱动从ServiceManager中获取服务进程信息
过程描述 | 代码实现 | 说明 |
---|---|---|
客户进程向Binder驱动发送获取服务请求,并写入需要获得的服务名称 | 从客户进程组件中执行bindService | |
Binder驱动将客户进程发送的请求转发到ServiceManager,ServiceManager查找客户进程需要的服务进程的Bidner实体的引用信息返回给binder驱动 | 服务进程中的onBind执行并返回binder的代理对象--BinderProxy | 该代理对象类即上面代码所定义的代理类 |
Binder驱动将代理对象返回给客户进程 | 客户进程调用ServiceConnection中的onServiceConnected接收代理对象转化的AIDL接口对象 | 客户进程获得是服务进程binder类的代理类对象,并不是真正的服务进程的binder对象,客户进程通过操作该代理对象通过binder驱动最终由服务进程的bidner对象执行操作 |
此时服务进程与客户进程建立了联系
3.客户进程调用服务
当客户进程与服务进程建立起联系后,客户进程通过操作服务进程的代理对象类BinderProxy通过binder驱动来执行服务进程中的相关方法
过程描述 | 代码描述 | 说明 |
---|---|---|
客户进程提供目标方法的参数通过Binder驱动发送到服务进程进行解包 | 客户进程执行代理对象类的目标方法(add(int a,int b)),首先创建输出型parcel对象—_data,输入型parcel对象_reply,结果_result(类型为AIDL允许的类型),将目标方法需要的参数屑放入_data并执行transact进行远程过程调用(RPC),客户进程被挂起。Binder驱动通知服务进程进行解包 | |
服务进程接收Bidner驱动的解包通知进行解包并执行目标方法 | 服务进程首先从进程池中取出Binder对象并调用onTransact,首先根据code判定需要执行那个方法,再从_data中取出目标方法需要的参数(如果有的话),然后执行该目标方法,将目标方法的返回值写入_result | |
服务进程将目标方法的返回值返回给客户进程 | 当目标方法执行完后,会返回true通知binder驱动方法调用成功,这是binder驱动唤醒客户进程,从代理对象中获取目标方法的执行结果并写入_result,最终将_result进行返回。这时客户进程就获取到了服务进程目标方法执行的结果。 |
整个过程如下图所示:
流程图.png