Android Binder 初步探究(基本原理)

前言

本文可以视为 Binder 学习指南 和 为什么Android 要采用 BInder 作为 IPC 机制 两篇文章的学习笔记,因此文章仅为笔者个人复习使用 ,同时希望能给 对以上两篇文章有所了解的朋友 提供参考。

一、概述

1.Binder的含义

binder 是 Android 系统实现IPC(跨进程通信)的机制,它沟通和联系了各个组件,是Android系统最为重要的成员之一。

2.进程隔离

进程:
进程是操作系统中的一个重要概念,基本的 CS 课程都会讲到,不同进程之间的数据是不共享的(内存隔离),那么进程之间要进行数据传递,就需要有一套对应的系统机制了。

进程隔离:
进程隔离使用了虚拟地址空间(这个概念就不引申了),A 进程和B进程的虚拟地址不同,以此避免 A进程的数据写入B进程。

3.用户空间 与 内核空间:

内核:
Linux Kernel ,它是系统运行的核心,独立于普通的应用程序。它可以访问受到保护的内存空间,同时也有权限访问所有硬件资源。

用户:
这里的用户就是上层的普通应用程序的意思

为什么区分用户空间 与 内核空间?
从内核的设定可以看出,它是非常重要的,也拥有极高的权限,因此不可以让上层应用程序随意的调用和访问它,所以需要一套保护机制,告诉应用程序,你只可以访问哪些特定的资源。所以就从逻辑上抽象出用户空间和内核空间,将它们隔离开来。

4.用户态 与 内核态

系统调用
虽然说抽象出了 用户空间 和 内核空间将它们隔离开,但是不可避免的,应用程序会有需要访问内核的时候,例如访问文件、使用网络。这时候,就有了系统调用的概念。
系统调用是用户空间访问内核空间的唯一入口接口,通过这种方式,所有资源的访问都会在内核的控制之下了,安全性自然大大增加。

用户态:
当在执行上层应用程序自己的代码的时候,就称之处于用户态,这时候进程的特权等级最低(3级)

内核态:
当通过系统调用,执行内核代码的时候,就称之处于内核态,这时候进程的特权等级最高(0级)。只有在满足一定特权等级的时候,CPU才能执行对应的特权指令。

5.内核模块 与 binder驱动:

用户空间与用户空间通信
用户空间与用户空间之间想要通信,第一时间想到的当然是让内核提供支持,毕竟不同的用户空间都可以访问同一个内核空间。
linux内核中的socket和管道都是解决用户空间之间通信问题的,但是Binder并不是Linux内核的一部分啊,这时候就用到了Linux的动态可加载内核模块机制(LKM)

内核模块
在Linux中,模块不能单独运行,但是它可以单独编译,内核模块就是在运行时再 连入 链接到内核中,作为内核的一部分 ,在内核空间中运行。
在Android系统里,为了解决用户进程之间通信问题而引入的内核模块就是:binder驱动。

binder 驱动:
驱动就是操作硬件的接口,为了实现binder通信的过程,binder使用了一种“硬件”,因此这个模块被叫做binder驱动。

二、为什么是 Binder

1.简单描述

性能更好:
在移动设备上(尤其是早期的Android设备,硬件水平太差了),性能毫无疑问是非常重要的一项指标,binder机制比起传统的socket和管道等有更好的性能:Binder数据拷贝只需要一次(匿名共享内存),而管道、消息队列、Socket都需要2次。

安全性更高:
传统的跨进程通信机制,通常都没有针对通信的两端做身份上的校验(例如socket的IP地址完全可以伪造),而Android系统作为一个开放的生态,必然对安全性有更高的要求。
binder机制从协议上就 ~~设置了 ~~ 支持了对通信双方的身份校验,提高了安全性。这也是Android权限模型的基础

2.详细解释(暂且留空,等仔细看完gityuan大佬那个回答再来补充)

三、Binder 通信模型

1.通信过程中的角色

通信的双方:
Binder通信的双方一般称为:Server端 和 Client端,可以类比于网络通信中的服务端和客户端,实际上它们往往就是两个用户进程

通信录:
就像打电话时候需要的电话簿一样,通信录记录了 Server 端的地址信息,这样 Client 端才能找到对应的 Server 端,并向它发送消息。

通信录如何知道 Server 端的信息呢?
这就要求Server端自己要去通信录里面注册了,Binder通信的通信录工作,是由 ServiceManager 来完成的,也可以简称为 SM(不要想歪了,这是失眠的意思

基站:
同样以打电话来类比,一次通信的过程,当然少不了基站的支持,只有依靠它才能真正实现在通信双方之间传递消息
Binder通信的基站工作,是由Binder驱动来完成的

来人,给公子上图
Android Binder 初步探究(基本原理)_第1张图片

2.一次基本的通信过程

  1. 首先是建立通信录(SM):一个进程申请成为ServiceManager,Binder驱动同意之后,SM进程就开始工作了,管理 Service 。原文中特意说明这里是Service,不是Server,我个人理解意思是说一次通信的 Client 进程,可能再跟另一个进程通信时,它又是 Sever 了,也就是一个进程会扮演 Client 和 Server 两个角色。
  2. 在通信录中注册(Server -> SM):如前文所说,SM也没办法凭空知道所有Server的信息,所以需要各个Server 自己在 SM 中注册,告知 SM 自己的名字和对应的地址信息
  3. 开始通信(Client -> SM -> Server):终于到了发送消息的这一步了,Client 想跟某个 Server 通信,就会先去 SM 中查询对应 Server 的信息,然后通过 binder 驱动,与对应的 Server 联系上,开始打电话了(通信是一来一回的,这里用发消息来类比就不合适了)。

四、Binder 机制跨进程原理

1.有关于通信

常规的进程间通信方式:
对于两个进程 A 和 B 来说,内核可以访问它们两个的数据,最常见的跨进程通信方式,当然就是利用内核。
例如 A 要向 B 发送一段数据,那么 A 就可以将数据拷贝到内核空间,然后内核再将数据拷贝到 B。
前面说到,用户空间操控内核空间需要通过系统调用,刚好Linux也提供了对应的系统调用:copy_from_user, copy_to_user。

Binder 机制跨进程通信的定义:
通信这个概念其实是比较宽泛的
Binder 机制实现跨进程通信,其实是指 Client 进程可以访问 Server 进程中的对象及其方法,这当然也是通信的一种方式
Binder 并没有用上述的常规的进程间通信方式

2.Binder 机制

隐藏步骤:一个进程申请成为ServiceManager,也就是SM进程

  1. Server 端向 SM 注册:Server 端要向 SM 注册,告知 SM 自己的信息和自己有什么能力(提供什么方法)。例如:进程名是 Zhangsan ,拥有一个 Object 对象,它有个 add 方法。

  2. Client 端从 SM 查询:Client 端想要访问 Zhangsan的这个 Object 对象,就需要联系 SM 查询,让SM给它返回对应的对象,但是注意,这里返回的对象不是原对象。

  3. Binder 驱动对查询的中间处理:
    数据在内核空间传递的时候,会经过 Binder 驱动,Binder 驱动判断到这是一个跨进程的调用,它不会将真正的 Object 对象返回给 A ,而是会返回一个 ObjectProxy 对象。
    这个对象的方法和 Object 对象一模一样,它也有 add 方法,但是它不具备真正 Object 对象所拥有的能力,也就是它是一个代理对象,是假的。

  4. Client 端调用:Client 拿到 ObjectProxy 对象之后,并不会感知到它和 Object 对象不一样,它还是照常当作在调用 Object 的 add 方法。

  5. Binder 驱动对调用的中间处理:
    这个调用当然又要经过 Binder 驱动,Binder 驱动 通过查表判断到 这是 ObjectProxy 对象的调用,就会去调用真正的 Object 对象的 add 方法,并且将得到的结果,通过 ObjectProxy.add 方法返回给进程 A。由此就完成了一次跨进程的调用,而 A 进程的调用方在此过程中其实是完全无感知的。

  6. 隐藏的内容 – Server 与 SM 的通信:
    上述过程其实省略了很多细节,例如 Server 进程与 SM 大多数时候肯定也不是同一个进程的,那么 Server 进程向 SM 注册的过程,本身也是一次跨进程通信,自然也是利用了 Binder 机制
    SM 中持有的 Object 对象,自然也不是真正的 Object 对象,其实它也是一个 代理对象,是一个 ObjectProxy 。
    Server 进程的本地对象仅有一个,只有这个是有真正的能力的,其他进程所拥有的全部都是它的代理

  7. 总结:Client 进程持有的只是 Server 端的代理,代理对象协助Binder驱动完成了跨进程通信。

五、Binder 的实质含义

1.不同角色的Binder(不那么重要,算是重复的叙述)

通信机制:
通常来说,binder 指的是它所代表的 IPC 通信机制,经常说的 AIDL 其实就是 Binder 机制实现 IPC 通信。

Server进程:
对于Server进程来说,Binder 指的是 Binder 本地对象,它是拥有实际能力的对象

Client进程:
对于Client进程来说,Binder 指的是 Binder 代理对象,它只是 Binder 本地对象的一个远程代理
当然,Client进程并不知道这些,它可以讲 Binder 代理对象当作本地对象来使用

传输过程:
对于这一整个传输过程来说,Binder 指的是这种能跨进程传输的特殊对象
每次跨进程传输,经过Binder驱动时,驱动会自动完成 本地对象 和 代理对象的转换

2.面向对象的思想

通信过程 -> 方法调用:
binder 机制把一个IPC通信的过程,变成了对 Binder 对象的方法调用
对于使用者来说,它完全不需要关心细节,只需要知道 Binder 对象即可,这就是抽象

跨进程引用对象:
在上述机制之下,不同进程可以持有同一个“对象”的引用,并且引用还可以传递,可以像访问一个普通的Java对象一样,去访问位于另一个进程的 binder 对象

binder 模糊了进程的边界,淡化了进程间通信的过程,不同的进程的代码,像是实际运行在同一个面向对象的程序中一样

3.驱动里的Binder

两种类型的转换
上述说到,在IPC过程中,本地对象 和 代理对象需要相互转换,这是由Binder驱动来完成的

两种类型的数据结构
要能做到这一点,Binder 驱动中需要存放所有的跨进程 Binder 对象的相关信息
其中,本地对象是一个叫 binder_node 的数据结构,代理对象则是由 binder_ref 来代表的,后者也被称为binder 引用或是 binder句柄。

六、解析 Java层的Binder

IInterface:
IInterface 是一个接口,代表的是 远程server端对象所具有的能力

IXXX:
假设这个是根据我们写的AIDL文件生成的接口
它继承了 IInterface
放在aidl这个场景,就是声明这个需要跨进程传输的对象,它有哪些方法可供调用

IBinder:
IBinder 是一个接口,代表 跨进程传输 的能力,它的这种能力是底层Binder驱动支持的
在跨进程传输过程中,驱动会自动识别它的真实身份(本地对象,代理对象),从而自动转换

Binder:
Binder 继承自 IBinder,它实际就是 Binder本地对象(这里就能理解上面Binder实质含义所说的了

BinderProxy:
BinderProxy 也继承自 IBinder,它实际就是 Binder代理对象
它原先应该是 Binder 的一个内部类,现在应该是独立出来了

Stub:
Stub 是IXXX内部的一个抽象的静态内部类
它继承自 Binder 类 (继承|is a),表明它是一个具有跨进程传输能力的 Binder 对象
它实现了 IXXX 接口,但是对应的方法,并没有实现,因此真正使用的时候,我们需要继承它然后实现接口对应的方法
通过上面的 继承+实现,将AIDL中定义的方法,转变为了一个具有跨进程能力的类

Proxy:
Proxy 是IXXX内部的一个静态内部类
它持有一个外部传入的IBinder对象(组合|has a ),其实就是BinderProxy ,这里注意区分的是用了组合的形式

它也实现了 IXXX 接口,同时实现了对应的方法,外部对它的方法调用,它会转调给持有的BinderProxy 对象。

这是一个很典型的代理类,为什么要用组合而不是继承呢?

  • 文中说了一些理由,例如它不必要继承,毕竟java是单继承模式,其次它确实不是一个真正的binder,如果继承了,返回会造成混乱。
  • 除了上述原因之外,我个人是觉得,它是一个代理类,它实现了 IXXX 接口 ,是方便 Client 无感知的访问 BinderProxy。而实际对BinderProxy的访问是需要通过 transact 方法的,这中间的转调需要准备和处理相关的参数,有一个适配的过程。
  • Proxy 相当于一个适配器,所以这里是用组合更好。

Default:
这是一个新增的东西,顾名思义,是提供 IXXX 接口的一个默认实现
当BinderProxy调用失败(IPC通信失败)的时候,就会转调这个默认的实现
使用者可以继承它,通过 setDefaultImpl 方法注入实例,自定义一些默认行为

七、联系实际 — AIDL 过程分析

以如下所示的AIDL文件为例,说明一次跨进程调用的过程

// Declare any non-default types here with import statements

interface ICompute {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    int add(int a,int b);
}

1.Service

bindService -> onServiceConnected
onServiceConnected 回调里,方法参数中会拿到一个远程的 service

ComputeImpl -> asInterface
ComputeImpl 继承自 ICompute.stub,asInterface是一个静态的方法

可以看到它的内部实现,它会去判断(调用 queryLocalInterface 查找),传入的对象是一个binder本地对象,还是一个binder代理对象

  • 如果找到,说明Client 和 Server 就在同一个进程,那么这个传入的对象就是 binder 本地对象了
  • 如果没找到,说明这是一个 BinderProxy 对象,那么就会构造一个 Proxy 对象,然后返回
//bindService
private void customBindService() {
        mBind = false;
        Intent intent = new Intent(this, ComputeService.class);
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                Log.d(TAG, "onServiceConnected: ComputeService");
                mComputer = ComputeImpl.asInterface(iBinder);
                mBind = true;
            }

            @Override
            public void onServiceDisconnected(ComponentName componentName) {
                Log.d(TAG, "onServiceDisconnected: ComputeService");
                mComputer = null;
                mBind = false;

            }
        }, Context.BIND_AUTO_CREATE);
 }


//ComputeImpl -> asInterface
public static com.magic.tulensa.ICompute asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof com.magic.tulensa.ICompute))) {
        return ((com.magic.tulensa.ICompute) iin);
    }
    return new com.magic.tulensa.ICompute.Stub.Proxy(obj);
}

2.Proxy

上面讲到,如果是跨进程调用,那么Client实际拿到的是一个Proxy对象
所以下面讲一下 Proxy 对象是如何去调用真正的 ComputeImpl 的方法的

Proxy -> add
构造了两个 Parcel 对象,分别取去承接方法需要的入参和返回值
调用 BinderProxy 的transact 方法
如果调用失败,则调用 default 实现

BinderProxy -> transact
这是一个本地方法,它的实现在native层,具体来说在 android_util_Binder.cpp
它会调用 talkWithDrive 方法,这个方法又会通过系统调用 ioctl,Client 进程进入内核态执行代码,调用 add 方法的线程会挂起等待返回结果
而binder驱动完成一系列的操作之后,会唤醒 server 进程,调用 server 进程中对应的 binder 对象的 onTransact 方法

Binder(ComputeImpl) ->onTransact
每个aidl 方法都有一个编号,onTransact 方法是根据这个编号来做匹配的
同样是利用两个 Parcel 对象,分别取去承接方法需要的入参和返回值
这个方法会将结果返回给驱动,驱动唤醒client进程里面挂起等待的add方法的线程

一次跨进程调用就结束啦

//Proxy -> add
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);
        boolean _status = mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
        if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().add(a, b);
        }
        _reply.readException();
        _result = _reply.readInt();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}


//BinderProxy -> transact
public native boolean transact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException;


//Binder -> onTransact
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_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;
        }
        default: {
            return super.onTransact(code, data, reply, flags);
        }
    }
}

八、联系实际 — 系统源码

IActivityManager(android/app/IActivityManager.java)
这个实际就是上面所说的 IXXX,根据AIDL文件自动生成的接口
它里面也包含了 Default 、Proxy 等类


ActivityManagerService
它继承自 IActivityManager.Stub ,所以它就是 binder 本地对象了


ApplicationThread
继承自IApplication.stub,AMS通过它,反向调用目标进程的相关方法(四大组件的生命周期回调)

public class ActivityManagerService extends IActivityManager.Stub
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, 
                   ActivityManagerGlobalLock {
        //省略
}

九、总结

总览

Binder 的重要性不言而喻,本文系统的梳理了Binder相关的基础概念

包括IPC通信的含义,实现原理,binder 的含义,binder 通信过程中的角色及对应的类,aidl 通信过程的分析等等

但是这些都还是最基本的概念,要想深入理解Binder,需要去源码里面钻研

重点

三、Binder 通信模型
五、Binder 的实质含义 – 2、面向对象的思想
六、解析JAVA层Binder

拓展

Binder设计与实现

十、引用

《Android开发艺术探索》第二章:IPC机制
Binder 学习指南
为什么Android 要采用 BInder 作为 IPC 机制

你可能感兴趣的:(android,android,经验分享)