Android Binder 原理换个姿势就顿悟了(图文版)

·  阅读 4901

前言

IPC 系列文章:
建议按顺序阅读。

Android IPC 之Service 还可以这么理解
Android IPC 之Binder基础
Android IPC 之Binder应用
Android IPC 之AIDL应用(上)
Android IPC 之AIDL应用(下)
Android IPC 之Messenger 原理及应用
Android IPC 之服务端回调
Android IPC 之获取服务(IBinder)
Android Binder 原理换个姿势就顿悟了(图文版)

Binder机制可谓是Android 知识体系里的重中之重,作为偏底层的基础组件,平时我们很少关注它,而它却是无处不在,也是Android 面试易考察的点之一。网上很多文章,要么知识点比较陈旧,要么源码贴一堆,要么没有成体系地分析,导致读者一知半解,似是而非。
本篇将从流程上将Binder通信过一遍,尽量多用图展示。
通过本篇文章,你将了解到:

  1. Binder的作用
  2. 进程与Binder驱动如何通信
  3. ServiceManager进程的作用
  4. 进程添加服务到ServiceManager的流程
  5. 进程从ServiceManager获取服务的流程
  6. Binder服务端数据接收
  7. Binder 通信全流程图

1. Binder的作用

先看Linux下进程地址映射关系:

image.png

我们知道,对象调用本身就是地址空间的访问。
如上,进程之间各自访问各自的内存地址,它们之间无法直接访问对方的地址,也就是说微信不能直接调用支付宝提供的接口。而内核具有访问其它进程地址空间的权限,因此微信可以将消息发送给内核,让内核帮忙转发给支付宝,这种方式叫做:存储/转发方式。
由此衍生的几种IPC(进程间通信)如:管道、消息队列、socket等,而Android 上采用了新的机制: Binder,相比传统的方式,Binder只需要一次数据拷贝,并且Binder更安全。

Binder机制是Android 里用来做IPC的主要方式。

2. 进程与Binder驱动如何通信

既然得要内核进行消息中转,那么Binder驱动得运行在内核空间,而事实上也确实如此,Binder驱动加载后在内核空间运行,进程只需要和Binder驱动取得联系,通过Binder驱动联系另一个进程,那么一次消息的传送过程就可以实现了。

image.png

内核提供提供一系列的系统调用接口给用户进程使用,当用户进程想要访问内核时,只需要调用对应的接口,此时代码就会从用户空间切换到内核空间执行。
常见的系统调用函数如:open/read/write/ioctl/close/mmap/fork 等。
与Binder驱动通信分两步:

  1. 打开Binder驱动:open("/dev/binder", O_RDWR | O_CLOEXEC)
  2. 通过ioctl 与Binder驱动进行数据通信:ioctl(mDriverFD, BINDER_WRITE_READ, &bwr)
    bwr 为读写数据结构

3. ServiceManager进程的作用

Binder Client、Binder Server、ServiceManager关系

为方便起见,ServiceManager简称SM。
Binder 设计为C/S架构,C为Client(客户端),S为Server(服务端),Server端提供接口(服务)给Client端使用,而这个服务是以Binder引用的形式提供的。
由之前的知识可知,C和S是不同的进程,那么C如何拿到S的Binder引用呢?
你可能会说,当然是SM了,S先将Binder引用存放在SM里,当C需要的时候向SM查询即可。
这么看似乎讲得通了,那问题又来了,SM也是一个单独的进程,那S、C如何与SM进行通信呢?这就陷入了先有鸡还是先有蛋的死循环了。
实际上C、S、SM之间都是依靠Binder通信,只是SM作为特殊的Binder(handle=0)提前放入了Binder驱动里,当C、S想要获取SM的Binder引用,只需要获取handle=0的Binder即可。
这么说没有太直观的印象,我们一步步剖析。

ServiceManager注册进Binder

image.png

SM 注册进Binder驱动后就会等待来自Binder驱动的消息,这里列出了两个最常见的处理消息的Case:

  1. 其它进程添加服务到SM里
  2. 其它进程向SM查询服务

SM里维护着一个链表,链表的元素是结构体:

image.png

主要记录的是name和handle字段。
当SM收到添加服务的指令后,从Binder驱动里取出handle和name,并构造结构体插入到链表。
当SM收到查询服务的指令后,从Binder驱动里取出name,并找到链表里相同的name,找到后取出handle,最后写入到Binder驱动。

4. 进程添加服务到ServiceManager的流程

其它进程找到SM

现在SM已经翘首以盼其它进程的请求了,接着来看看如何添加一个服务到SM里。
以Java层添加服务为例,我们选择振动服务作为切入点分析。

image.png

在system_server 进程里构造振动服务(VibratorService继承自Binder),并添加到SM里。

image.png

可以看出,分两步:

  1. 先找到ServiceManager
  2. 往ServiceManager里添加服务

getIServiceManager()继续往下:

image.png

BinderInternal.getContextObject() 是native方法,后续流程较多,我们用图表示。

image.png

寻找ServiceManager的过程涉及到Java层和Native层,主要的重点在Native层查找 ServiceManager对应的BpBinder对象,没有找到的话则创建新的并存入缓存里以备下次直接获取。

  1. ProcessState里维护了一个单例,每个进程只有一个ProcessState对象,创建ProcessState时候就会去打开Binder驱动,同时会设置Binder线程池里线程个数等其它参数
  2. Native层构造BpBinder(handle=0表示该BpBinder是ServiceManager在客户端的引用),再构造BinderProxyNativeData持有BpBinder。
  3. 构造BinderProxy对象并持有BinderProxyNativeData,也就是间接持有BpBinder
  4. 最后构造了ServiceManagerProxy对象,它实现了IServiceManager接口,它的成员变量mRemote指向了BinderProxy

可以看出,获取ServiceManager的过程并不是真正去获取ServiceManager的Binder对象,而是获取它在当前进程的代理:BpBinder

添加服务到ServiceManager

既然找到了SM的Binder代理,接下来看看如何使用它给SM添加服务。

    #ServiceManagerNative.ServiceManagerProxy
    public void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
            throws RemoteException {
        //构造Parcel
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        data.writeString(name);
        //写入Binder
        data.writeStrongBinder(service);
        data.writeInt(allowIsolated ? 1 : 0);
        data.writeInt(dumpPriority);
        //通过BinderProxy发送
        mRemote.transact(ADD_SERVICE_TRANSACTION, data, reply, 0);
        reply.recycle();
        data.recycle();
    }
复制代码

image.png

其中IPCThreadState与线程相关,不同的线程会维护一个单例。
由此可见,最终还是通过BpBinder发送消息,进而发送到Binder驱动。
此时驱动收到的信息包括不限于:

  1. 服务的名字
  2. ServiceManager的handle
  3. BBinder对象指针

驱动建立服务handle和BBinder对象指针的映射关系,并将服务的名字和服务的handle传递给ServiceManager(通过ServiceManager handle查找)。
ServiceManager拿到消息后建立映射关系,等待其它进程的请求。
至此,进程添加服务到ServiceManager过程已经分析完毕,用图表示如下:

image.png

BBinder作用

Java层传递的是Binder对象,如何与Native的BBinder关联起来呢?
重点在:

Parcel.writeStrongBinder(Binder)
复制代码

image.png

也即是说Server端的Java Binder对象在Native层的代表是BBinder。
Binder驱动记录了BBinder的地址,当有消息过来时通过找到BBinder对象进而找到Java层的Binder对象,最终调用Binder.onTransact()。

5. 进程从ServiceManager获取服务的流程

其它进程找到SM

振动服务添加完成后,某些进程想要获取振动服务进行振动,比如微信收到消息后需要振动用以提示用户。
接着来看看如何获取振动服务。

    private void vibrate() {
        //获取振动服务
        Vibrator vibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
        //开始振动
        vibrator.vibrate(1000);
    }
复制代码

与添加服务类似,想要获取服务先要找到SM,找SM的过程上边分析过了,此处不再细说。

从ServiceManager获取服务

    #ServiceManagerNative.ServiceManagerProxy
    public IBinder getService(String name) throws RemoteException {
        //构造Parcel
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        //写入名字
        data.writeString(name);
        //通过BinderProxy发送
        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
        IBinder binder = reply.readStrongBinder();
        reply.recycle();
        data.recycle();
        return binder;
    }
复制代码

image.png

由此可见,最终还是通过BpBinder发送消息,进而发送到Binder驱动。
此时驱动收到的信息包括不限于:

  1. 服务的名字
  2. ServiceManager的handle

Binder驱动收到消息后,找到SM,并将服务的名字传给SM,SM从自己维护的链表里找到服务名相同的节点,最终取出该服务的handle,发送给Binder驱动。
用图表示如下:

image.png

对比添加服务流程和获取服务流程,两者前半部分都很相似,都是先拿到SM的BpBinder引用,然后写入驱动,最后由SM进程处理。只是对于获取服务流程来说,还需要将查询的结果(handle)写入驱动返回给调用方(对应图上红色部分)。

到这,大家可能会有疑惑了:"handle是整形值,而微信获取的振动服务是一个Binder对象,这两者是怎么结合起来的呢?"

handle转换为Binder对象

handle表示的即是Binder服务端在客户端的索引句柄,只要客户端拿到了handle,它就能通过Binder驱动调用到服务端。

        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
        IBinder binder = reply.readStrongBinder();
复制代码

再回过头看看获取服务的代码,当微信进程将查询命令发给Binder驱动后就等待驱动回复的结果,SM查询到结果后将handle写入驱动,而后微信进程从驱动将结果读出并将结果存入reply字段。
最后通过reply拿到Binder引用,也就是说重点在reply.readStrongBinder()方法。
直接看图:

image.png

如上,通过驱动返回的handle构造BpBinder,最终封装为Java层的BinderProxy。

至此,获取服务流程就结束了,用图展示简化的流程

image.png

6. Binder服务端数据接收

微信进程拿到振动服务(在system_server进程里)的Binder(BinderProxy)后,就可以调用振动方法了,而后指令发送给驱动,驱动通过振动服务的handle找到对应的服务BBinder指针,从而调用服务的接收方法。
微信进程发送指令给Binder驱动前面已经分析过,重点来看看system_server进程是如何接收并处理指令的。

image.png

system_server进程启动的时候就会开启Binder线程池,并等待驱动数据到来。
当system_server进程添加振动服务到SM时,会将Java层的Binder转为Native层的BBinder,并将BBinder对象指针写入Binder驱动。
当微信进程调用system_server接口时:

  1. 微信进程调用BpBinder.transact()将handle和数据写入Binder驱动
  2. Binder驱动根据handle找到system_server进程
  3. system_server进程从驱动拿到数据,并取出BBinder指针,最终调用到system_server进程Java层的Binder.onTransact()

如此一来,微信成功调用了振动服务,也就是说一次Client到Server端的通信就完成了。

7. Binder 通信全流程图

纵观Binder机制设计,最核心的点是handle。

  1. 通过handle构造Client端的BpBinder(Native层),与此对应的是Java层的BinderProxy
  2. 通过handle,驱动找到Server端进程,进而调用BBinder(Native层),与此对应的是Java层的Binder
  3. 通过handle的一系列中转,Client.transact()成功调用了Server.onTransact(),一次Binder通信就过程就完成了

最后,用一张图总结Binder机制的全过程:

image.png

以上就是整个Binder机制的梳理过程,此间省略了Binder驱动里的映射逻辑,可以将Binder驱动当做一个黑盒,而更重要的是Binder客户端和服务端是如何进行映射的。
Binder流程比较绕,尤其是IPCThreadStsate作为客户端的发送和服务端的数据接收的实体,需要区分不同的场景。
当然,jni基础知识必不可少。

本文基于Android 10
限于篇幅并没有一步步列出源码,对源码细节有疑惑之处欢迎留言讨论。

您若喜欢,请点赞、关注、收藏,您的鼓励是我前进的动力

持续更新中,和我一起步步为营系统、深入学习Android/Kotlin

1、Android各种Context的前世今生
2、Android DecorView 必知必会
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分发全套服务
6、Android invalidate/postInvalidate/requestLayout 彻底厘清
7、Android Window 如何确定大小/onMeasure()多次执行原因
8、Android事件驱动Handler-Message-Looper解析
9、Android 键盘一招搞定
10、Android 各种坐标彻底明了
11、Android Activity/Window/View 的background
12、Android Activity创建到View的显示过
13、Android IPC 系列
14、Android 存储系列
15、Java 并发系列不再疑惑
16、Java 线程池系列
17、Android Jetpack 前置基础系列
18、Android Jetpack 易学易懂系列
19、Kotlin 轻松入门系列
20、Kotlin 协程系列全面解读

分类:
Android
收藏成功!
已添加到「」, 点击更改