Android中Binder学习

关于Binder,我们需要知道,为什么需要跨进程通信(IPC),怎么做到跨进程通信?为什么是Binder?由于Android是基于Linux内核的,因此有些知识需要我们先了解:进程隔离进程隔离是为保护操作系统中进程互不干犹而设计的一组不同硬件和软件的技术。这个技术是为了避免进程A写入进程B的情况发生。进程的隔离实现,使用了虚拟地址空间。进程A的虚拟地址和进程B的虚拟地址不同,这样就放置进程A...
摘要由CSDN通过智能技术生成

关于Binder,我们需要知道,为什么需要跨进程通信(IPC),怎么做到跨进程通信?为什么是Binder?

由于Android是基于Linux内核的,因此有些知识需要我们先了解:

进程隔离

进程隔离是为保护操作系统中进程互不干犹而设计的一组不同硬件和软件的技术。这个技术是为了避免进程A写入进程B的情况发生。进程的隔离实现,使用了虚拟地址空间。进程A的虚拟地址和进程B的虚拟地址不同,这样就放置进程A将数据信息写入进程B。

ps:虚拟地址就是逻辑地址。

操作系统的不同进程之间,数据不共享,对于每个进程来说,它都以为自己独享了整个系统,完全不知道其他进程的存在,因此一个进程需要个另一个进程通信,需要某种系统机制才能完成。

用户空间/内核空间

Linux Kernel 是操作系统的核心,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。

对于Kernel这么一个高安全级别的东西,显然是不容许其他的应用随便调用或访问的,所以需要对Kernel提供一定的保护机制,这个保护机制用来告诉那些应用程序,只可以访问某些许可的资源,不许可的资源是拒绝访问的,也是就把Kernel和上层应用程序抽象的隔离开,分别称为内核空间和用户控件。

系统调用/内核态/用户态

虽然从逻辑上抽离出用户空间和内核空间,但是不可避免的是,有一些用户空间需要访问内核的资源,比如应用程序访问文件,网络等,怎么办呢?
用户空间访问内核空间的唯一方式就是系统调用;通过这个统一入口接口,所有的资源访问都是在内核的控制下执行,以免导致用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。

当一个任务(进程)执行系统调用而进入内核代码中执行时,我们就称进程处于内核运行行态(或简称为内核态),此时处理器处于特权级最高的内核代码中执行。当进程在执行用户自己的代码时,就称其处于用户运行状态,即此时处理器在特权级最低的用户代码中运行。
处理器在特权等级最高的时候才能执行那些特权CPU指令。

内核模块/驱动

通过系统调用,用户控件可以访问内核空间,那么如果一个用户空间想与另一个用户空间进行通信,怎么办呢?那么肯定是通过操作系统内核了,通过添加操作系统内核的支持;传统的Linux通信机制,比如Socket,管道等都是内核支持的。
Binder并不是Linux内核的一部分,那么它是怎么做到访问内核空间的呢?Linux的动态可加载内核模块机制解决了这个问题;
模块是具有独立功能的程序,它可以被单独被编译,但是不能独立运行。它在运行时被连接到内核作为内核的哟部分在内核空间运行。这样,Android系统可以通过天际一个内核模块运行在内核空间,用户进程之间的通信就通过这个模块作为桥梁。

在Android系统中,这个运行在内核空间,负责各个用户进程通过Binder通信的内核模块叫做BInder驱动。

为什么使用Binder?

Android使用的Linux内核拥有着非常多的跨进程通信机制,比如管道,System V,Socket等;为什么还需要单独搞一个Binder出来呢?主要有两点,性能和安全。在移动设备上,广泛地使用跨进程通信肯定对通信机制本身提出了严格的要求;Binder相对出传统的Socket方式,更加高效;另外,传统的进程通信方式对于通信双方的身份并没有做出严格的验证,只有在上层协议上进行架设;比如Socket通信ip地址是客户端手动填入的,都可以进行伪造;而Binder机制从协议本身就支持对通信双方做身份校检,因而大大提升了安全性。这个也是Android权限模型的基础。

Binder相较于传统IPC来说更适合于Android系统,具体原因的包括如下三点:

  1. Binder本身是C/S架构的,这一点更符合Android系统的架构
  2. 性能上更有优势:管道,消息队列,Socket的通讯都需要两次数据拷贝,而Binder只需要一次。要知道,对于系统底层的IPC形式,少一次数据拷贝,对整体性能的影响是非常之大的
  3. 安全性更好:传统IPC形式,无法得到对方的身份标识(UID/GID),而在使用Binder IPC时,这些身份标示是跟随调用过程而自动传递的。Server端很容易就可以知道Client端的身份,非常便于做安全检查
Binder的架构

在这里插入图片描述

  • Binder通信采用C/S架构,从组建视角来讲,包含Client,Server,ServiceManager以及Binder驱动,其中ServiceManager用于管理系统中各种Service。
  • Binder在framwork层进行了封装,通过JNI技术调用Native层的Binder架构。
  • Binder在Native层以ioctl的方式与Binder驱动通信。
Binder的通信模型

两个运行在用户空间的进程要完成通信,必须借助内核的帮助,这个运行在内核里面的程序叫做Binder驱动,它的功能类似于基站;ServiceManager(简称SM)的功能类似于通信录。

在这里插入图片描述

通信的步骤:

  1. ServiceManager建立:首先有一个进程向驱动提出申请SM,驱动同意后,SM进程负责管理Service
  2. 各个Service向SM注册:每个Server端进程启动后,向SM报告,我是XXX,如果要找我就返回0x1234(类比)。其他Server进程也是如此;这样SM就建立了一张表,对应着各个Server的名字和地址。
  3. Client想要与Server通信,首先询问SM,请告诉我如何联系zhangsan,SM收到后给他一个号码0x1234;Client收到之后,开心滴用这个号码拨通了Server的电话,于是就开始通信了。

那么Binder驱动干什么去了呢?这里Client与SM的通信,以及Client与Server的通信,都会经过驱动,驱动在背后默默无闻,但是做着最重要的工作。

Binder机制跨进程原理

由上面的知识,我们可以知道,两个进程想要通信,肯定要通过内核进行中转,要知道,内核是可以访问所有进程的数据的,我们直接可以想到,加入进程A,B,进程A要给进程B发送消息,那么可以将A的数据复制一份到内核,内核再将对应的数据复制到B。

但是Binder不是这样做的。Binder驱动为我们做了一切。

假设Client进程想要调用Server进程的Object对象的一个add方法,
首先,Server进程向SM注册:告诉自己是谁,有什么能力,正这个场景中,Server告诉SM,它叫zhangsan,它有一个object对象,可以执行add操作;于是SM就建立了一张表,zhangsan这个名字就对应了这个Server。

然后Client向SM查询:我需要联系一个名字叫zhangsan的进程里面的Object对象;这个时候,内核里面的驱动不会给Client进程返回一个真正的Object对象,而是返回一个看起来跟Object一模一样的代理对象objectProxy,这个objectProxy也有有个add方法,但是这个add方法没有Servier进程里面Object对象中的add方法那个能力;objectProxy的add只是一个傀儡,它唯一做的事情就是把参数包装起来转发给Binder驱动。

驱动收到这个消息,发现是这个objectProxy;一查表就明白了:我之前用objectProxy替换了object发送给Client了,它真正应该要访问的是object对象的add方法;于是Binder驱动通知Server进程,调用你的object对象的add方法,然后把结果发给我,Sever进程收到这个消息,照做之后将结果返回驱动,驱动然后把结果返回给Client进程;于是整个过程就完成了。

通过这个,我们可以知道,Binder跨进程传输并不是真的把一个对象传输到了另外一个进程;传输过程好像是Binder跨进程穿越的时候,它在一个进程留下了一个真身,在另外一个进程幻化出一个影子(这个影子可以很多个);Client进程的操作其实是对于影子的操作,影子利用Binder驱动最终让真身完成操作。

对于Binder的访问,如果是在同一个进程中,那么直接返回原始的Binder实体,如果是在不同进程,那么就给它一个代理影子;

另外我们为了简化整个流程,隐藏了SM这一部分驱动进行的操作;实际上,由于SM与Server通常不在一个进程,Server进程向SM注册的过程也是跨进程通信,驱动也会对这个过程进行暗箱操作:SM中存在的Server端的对象实际上也是代理对象,后面Client向SM查询的时候,驱动会给Client返回另外一个代理对象。Sever进程的本地对象仅有一个,其他进程所拥有的全部都是它的代理。

一句话总结就是:Client进程只不过是持有了Server端的代理;代理对象协助驱动完成了跨进程通信。

BInder的面对对象

Binder使用Client-Server通信方式:一个进程作为Server提供如视频/音频解码,视频捕获,地址本查询,网络连接等服务;多个进程作为Client向Server发起服务请求,获得所需要的服务。对Binder而言,Binder可以看成Server提供的实现某个特定服务的访问接入点,Client通过这个‘地址’向Server发送请求来使用该服务;对Client而言,Binder可以看成是通向Server的入口,要想和某个Server通信,必须先建立这个入口并获取这个入口。

与其他IPC不同,Binder使用了面对对象的思想来描述访问接入点的Binder及其在Client中的入口:Binder是一个实体位于Server中的对象,该对象提供了一套用以实现对服务的请求,就像类的成员函数。在Client看来,通过Binder‘指针’调用其提供的方法和通过指针调用其他任何本地对象的方法并没有什么区别,尽管前者的实体位于远端的Server中,而后者的实体位于本地内存中。从通信的角度来看,Client中的Binder也可以看做是Server Binder的‘代理’,在本地代表远端Server为Client提供服务。

面向对象思想的引入将进程间通信转化为通过对某个Binder对象的引用调用该对象的方法,而其独特之处在于Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,而它的引用却遍布于系统的各个进程之中。最诱人的是,这个引用和java里引用一样既可以是强类型,也可以是弱类型,而且可以从一个进程传给其它进程,让大家都能访问同一Server,就象将一个对象或引用赋值给另一个引用一样。Binder模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中。形形色色的Binder对象以及星罗棋布的引用仿佛粘接各个应用程序的胶水,这也是Binder在英文里的原意。

理解Java层的Binder
IBinder/IInterface/Binder/BinderProxy/Stub

我们在使用AIDL接口的时候,经常会接触到这些类,那么每个类代表的是什么呢?

  • IBinder:是一个接口,它代表了一种跨进程传输的能力;只要实现了这个接口,就能将这个对象进行跨进程传递,这是驱动底层支持的;在跨进程数据流经驱动的时候,驱动会失败IBinder类型的数据,从而自动完成不同进程Binder本地对象以及Binder代理对象的转换。
  • IBinder负责数据传输,那么client与server端的调用契约(这里不用接口避免混淆)呢?这里的IInterface代表的就是远程server对象具有什么能力。具体来说,就是aidl里面的接口。
  • Java层的Binder类,代表的其实就是Binder本地对象。BinderProxy类是Binder类的一个内部类,它代表远程进程的Binder对象的本地代理;这两个类都继承自IBinder,因为都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder驱动会自动完成这两个对象的转换。
  • 在使用AIDL的时候,编译工具会给我们生成一个Stub的静态内部类;这个类继承了Binder, 说明它是一个Binder本地对象,它实现了IInterface接口,表明它具有远程Server承诺给Client的能力;Stub是一个抽象类,具体的IInterface的相关实现需要我们手动完成,这里使用了策略模式。
AIDL过程分析

首先定义一个简单的AIDL接口:

interface ICalculate {
   
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
   int add(int first,int second);
   int sub(int first,int second);
}

经过编译后,会在app/build/generated/source/aidl下生成一个ICalculate.java的Interface

public interface ICalculate extends android.os.IInterface{
   
	/** Local-side IPC implementation stub class. */
	public static abstract class Stub extends android.os.Binder implements 
	com.example.asus1.remoteservice.ICalculate{
   
		private static final java.lang.String DESCRIPTOR = 	
					"com.example.asus1.remoteservice.ICalculate";
		/** Construct the stub at attach it to the interface. */
		public Stub(){
   
		this.attachInterface(this, DESCRIPTOR);
		}
		/**
 		* Cast an IBinder object into an com.example.asus1.remoteservice.ICalculate interface,
 		* generating a proxy if needed.
 		*/
		public static com.example.asus1.remoteservice.ICalculate asInterface(android.os.IBinder obj){
   
		if ((obj==null)) {
   
			return null;
		}
		android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
		if (((iin!=null)&&(iin instanceof com.example.asus1.remoteservice.ICalculate))) {
   
			return ((com.example.asus1.remoteservice.ICalculate)iin);
		}
		return new com.example.asus1.remoteservice.ICalculate.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
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值