Android Binder实现浅析-Binder驱动

  • Post author:
  • Post category:其他




简介

Android是如何实现跨进程通信的,大家熟悉的Binder是什么,怎么设计的,进程间的数据如何发送接收的。本文将以及解析,并对

Binder驱动实现、Native层实现、Java层实现

三块做一个总结分析。



Binder学习思路

  1. Binder与传统IPC的区别
  2. Binder驱动的内部设计、数据结构
  3. Binder驱动与应用程序进程(C/S)之间的通信过程
  4. Android应用程序通过Binder驱动进行通信的流程
  5. Android开发人员如何使用Binder通信(AIDL、Java层架构)



基础知识理解

  1. Unix内核和应用程序进程所使用的物理内存是分开的,内核使用1G的物理内存,其他应用程序有各自的3G物理内存(32位操作系统)
  2. 因为内核和应用程序的物理内存是分开的,所以两者之间传递数据需要进行数据拷贝
  3. 内存映射(mmap)可以将两个虚拟内存地址空间(不同进程)映射到同一物理内存段上。实现多进程(或者内核与进程)之间公用一块内存,减少数据拷贝次数
  4. Unix驱动程序是一个运行在内核态(使用内核对应的物理内存)的程序
  5. Binder也是一种IPC的实现方式,其与传统的Unix IPC有一定的差别(使用了mmap)



理解Binder驱动的存在

因为要实现跨进程通信,那么,数据是如何传输的,怎么组织的。两个进程之间是如何知道对方的标识(引用)的,这一系列问题,都由Binder驱动解决,每个进程需要为其他进程提供服务(API调用),都需要向Binder驱动注册,其他进程才能知道自己的数据传向哪里。这里大家先忽略ServiceManager的特殊身份。只讨论Binder驱动的角色定位即可。

这样看来,其实Binder驱动就是一个多个进程之间的中枢神经,支撑起了Android中进程间通信,它内部的设计,与应用程序进程中的业务,不存在任何耦合关系,只负责实现进程间数据通信。可以用如下图来理解Binder驱动与应用程序进程之间的关系。

Binder驱动的作用

当然,Android里的Binder架构应该还有ServiceManager这个系统服务。



ServiceManager的存在

ServiceManager下文简称SM,是一个Android操作系统提供的一个系统进程。那么为什么要单独提他呢,因为这个进程里,记录了所有Binder实体(提供服务的Binder实现对象)的信息。

也就是说,

SM是用来给应用程序查找其他应用程序的数据中心与校验中心,保障进程间通信的安全新,合法性。

SM是系统服务,在系统启动后,SM便启动,并执行以下事情:

  1. 打开Binder驱动
  2. 将自己注册为Binder驱动的大管家(其他进程根据引用编号0可以找到SM对应的Binder实体)
  3. 进入循环,不断从Binder驱动中读取消息(无消息被阻塞)
  4. 读取到消息之后处理消息
  5. 不断循环,永不退出

SM处理的消息类型有:


  1. 注册

    Binder实体对象

  2. 查询

    Binder实体对象,以引用编号的形式返回给查询进程

SM的功能比较简单,就是注册和查询,是第一个注册到Binder驱动中的Binder实体,引用编号0(只能注册一次)

注册Binder实体信息到SM的时候,请求数据中需要写到Binder实体的

描述信息

,之后进行查询的时候就是根据

描述信息

来获取到对应的Binder应用编号。

到这里,我们可以看出,其实整个Binder架构就是一个Client,Server,DNS的结构,当然Binder驱动就扮演了一个路由器的角色。

这个结构的前提,就是DNS需要提前注册。也就是说SM进程需要第一个注册到Binder驱动中,而且,Client和Server都知道SM的引用编号(0),能够直接通过SM获取其他进程提供的Binder引用编号。



Binder驱动启动过程



打开
  1. 每个需要通过Binder通信的进程都需要打开

    /dev/binder

    驱动一次(至多一次)
  2. 打开Binder驱动之后,内核会调用驱动程序的binder_open方法,该方法内部将会创建binder_proc结构体,内部存储了进程信息以及UID信息。


内存映射
  1. 使用mmap对

    /dev/binder

    进行内存映射操作
  2. 在mmap调用之后,内核会调用驱动程序的binder_mmap方法,该方法内部会为进程创建binder_buffer结构体,也就是为进程创建缓冲区,用于接收数据。并且这块内存缓冲区对应有两个虚拟内存地址区间,一个是内核的虚拟空间,一个是进程用户空间的虚拟空间。此块缓冲区是一个只读的区域,防止用户空间对其进行修改。


动作执行者

对于应用程序进程来说,打开驱动和内存映射动作由Native类ProcessState完成,该类为单利,在构造方法中进行,先打开,再执行内存映射。



Binder与共享内存之间的区别

为什么与

共享内存

进行对比(性能),是因为

共享内存管

是unix中最快的一种IPC机制。

共享内存为什么快,是因为共享内存相当于是将

两个进程的虚拟地址空间指向了一块物理内存

,两个进程对该内存区域的修改,能够直接反应到对方进程中,也就是

不需要对数据进行拷贝



内存映射

前面说到,Binder是通过

mmap

来实现的,理论上,

mmap

也可以让两个进程映射到同一段物理内存区域(文件)上。但是Binder没有这样实现,如果这样的话,和共享内存就一样了。那Binder又是如何实现的呢。

首先,Binder有驱动程序,所有数据传输和接收,都是通过Binder驱动来操作的。这就带来一个问题,Binder驱动是运行在内核态的,那么数据在使用Binder驱动传输时,是需要在

内核内存空间



用户内存空间

进行拷贝操作的。

试想下,A进程与B进程进行通信,A进程给B进程发送数据data,按照上面的分析,数据data需要先从A进程的用户空间拷贝到Binder驱动的内核空间,再通过Binder驱动写入到(具体实现后面说)B进程的Binder驱动内核空间,最后从Binder驱动再拷贝的B进程的用户空间。如此一来,数据进行了两次拷贝。

其实,Binder驱动内部并不需要两次数据的拷贝,原因在于Binder将

内核内存空间



用户内存空间

进行了

内存映射

操作,具体如下图

Binder驱动数据传输

首先,我们从

数据接收进程

看,内核与用户内存空间,通过

mmap

映射到了同一块

物理内存

上。也就是说对该块

物理内存

的修改,将会体现到

数据接收进程

的用户空间和内核空间。

再看

数据发送进程

,左边的数据发送进程,只是将内核的内存空间映射到了

物理内存

上。

接着,当

数据发送进程

需要向

数据接收进程

传递数据时,数据只需要从

数据发送进程

的用户内存空间拷贝到

数据发送进程

的内核内存空间,此时,因为

数据发送进程

的内核内存空间与

物理内存

进行了映射,而

数据接收进程

的用户内存空间与内核内存空间同时都映射到了同一块

物理内存

上,所以此次拷贝,直接将

数据发送进程

的用户空间数据,拷贝到了

数据接收进程

的用户内存空间。

通过上面的分析,也就能理解,为什么说

Binder传输数据时需要拷贝1次数据,共享内存不需要拷贝数据



Binder的实现架构

完成对Binder跨进程通信底层IPC实现分析之后,需要思考,Android如何让两个进程建立联系(如何找到通信进程),那就需要一个系统进程,所有应用程序都知道它,并能联系到它,从这个系统进程那边,能够查找到(通过Service名字符串)需要通讯的进程。

最终,Android采用了

Client



Server



ServiceManager

的实现架构,其中

Client

需要从

ServiceManager

中找到

Server

,然后

Client



Server

之间即可进行通信

那么什么进程能够在

ServiceManager

中注册呢,就是在Android操作系统中注册过(APP清单文件中的Service)的那部分服务才能注册,到这,也就能理解Android为什么采用这种架构模式了,在安全上又进一步约束。



Binder驱动

首先要知道Binder驱动是运行在内核态下,内核态的内存是所有进程共享的。

  • 任务一:存储所有进程的Binder信息(引用编号,Server端的虚拟内存地址)
  • 任务二:进程间数据传递



Binder是什么

Binder是什么,需要从多方面解释,不同环境中,其代表的是不一样的东西。



Binder在Server中的表述

Binder在Server中代表的是具体的实现,

简称Binder实体



Binder在Client中的表述

Binder的具体实现应该是在Server进程,也就是说Client进程是无法拿到该实现对象的地址信息的。

那么Binder在Client中代表的仅仅是一个

引用(驱动给的)编号

,Client能够通过该编号向远端Server发送数据。



Binder在驱动中的表述

驱动,是Binder架构在最核心的一部分,驱动需要做的事情很多

  • 所有Server端的Binder实体,需要在驱动中注册
  • Client端获取Binder时,需要为Client创建Binder引用,并把引用编号信息记录在驱动中
  • 维护各个Client中的引用于Binder实体之间的映射关系
  • 通过引用编号找到对应实体
  • 创建Server端的Binder实体
  • etc…

Binder在驱动中的表述,需要从Server和Client两个维度理解



Binder实体(Server端)在驱动中的表述

Binder实体需要在驱动中进行注册,注册时,驱动需要在内核中为Binder实体创建一个结构体

binder_node


该结构体中存储的

主要数据

  • Server端Binder实体对象的内存地址
  • Server端Binder实体在所有实体链表中的节点结构体

说明:每个Server进程都对应有一个链表,用来存储所有的Binder实体节点,以Binder实体对象的内存地址为索引进行查找。



Binder引用(Client端)在驱动中的表述

Binder引用在驱动中以

binder_ref

结构体的形式存在。改结构体中存储的

主要数据

为:

  • Binder实体在驱动中的结构体引用
  • Binder实体在驱动中的引用号(编号)
  • Binder引用在进程链表中的节点(以编号以及实体地址为索引的两个链表节点)

说明:每个Client进程都对应有两个链表,一个是以Binder实体在驱动中的结构体地址为索引建立的链表,一个是以Binder实体在驱动中的引用号为索引建立的链表。



Binder在传输数据中的表述

虽然Binder实体和Binder引用都在驱动中有不同的结构体来标识,但是Client和Server在于Binder进行通信时,并不是通过传递这两个结构体来代表不同的Binder的,而是通过另一个统一的结构体

flat_binder_object

来代表本次通信对应的Binder。

既然使用的是同一个结构体,那么这个结构体中应该有的内容:

  • Binder类型(实体,引用)
  • Binder实体的内存地址(类型为实体时用)
  • Binder引用的编号(类型为引用时用)

其中Binder类型有以下几种:

  • BINDER_TYPE_BINDER:表示传递的是Binder实体,并且指向该实体的引用都是强类型;
  • BINDER_TYPE_WEAK_BINDER:表示传递的是Binder实体,并且指向该实体的引用都是弱类型;
  • BINDER_TYPE_HANDLE:表示传递的是Binder强类型的引用
  • BINDER_TYPE_WEAK_HANDLE:表示传递的是Binder弱类型的引用
  • BINDER_TYPE_FD:表示传递的是文件形式的Binder

那么

flat_binder_object

里的内容填充方式具体是怎样的呢,比如Server将Binder传递给Client,Server发送的

flat_binder_object

,类型应该是

BINDER_TYPE_BINDER

,此时,驱动将会在内核中为Server进程创建对应的

binder_node

结构,并且将

flat_binder_object

中的

Binder实体的内存地址

保存起来。接着驱动需要在内核中为Client进程创建一个

binder_ref

结构,因为Server传过来的

Binder实体的内存地址

在Client进程是无效的,所以驱动需要为Client进程创建一个Binder对应的引用编号,并将此编号存入

binder_ref

结构中。同时,需要将

flat_binder_object

中的类型改成

BINDER_TYPE_HANDLE

,以及存储引用编号。

当Client需要使用Server传递过来的Binder的时候,向驱动传递的数据包中,就需要用到Binder的引用编号,驱动将会对引用编号进行校验,这样就能在安全性上得到保障。



Binder表述总结

当一个Server进程创建了一个Binder实体,之后,这个实体在各个环境中的表述情况为

  1. Server进程中的Binder称为Binder实体,其应该要继承BBinder类(Native类)
  2. 其在Binder驱动中,以

    binder_node

    表述
  3. 当Server进程的Binder服务需要被Client进程所使用时,Binder驱动会创建一个

    binder_ref

    结构体,这也就是Server中创建的Binder实体在Client进程中的表述(存储引用编号)
  4. 在Client的用户空间中,需要创建一个Binder代理类,该类继承BpBinder类,Client进程通过该代理类与Server端的Binder实体进行通信

    Binder的表述

    (图片摘自:

    《Android系统源码情景分析》

它们的交互过程可以划分为五个步骤,如下所示。

  1. 运行在Client进程中的Binder代理对象通过Binder驱动程序向运行在Server进程中的Binder本地对象发出一个进程间通信请求,Binder驱动程序接着就根据Client进程传递过来的Binder代理对象的句柄值来找到对应的Binder引用对象。
  2. Binder驱动程序根据前面找到的Binder引用对象找到对应的Binder实体对象,并且创建一个事务(binder_transaction)来描述该次进程间通信过程。
  3. Binder驱动程序根据前面找到的Binder实体对象来找到运行在Server进程中的Binder本地对象,并且将Client进程传递过来的通信数据发送给它处理。
  4. Binder本地对象处理完成Client进程的通信请求之后,就将通信结果返回给Binder驱动程序,Binder驱动程序接着就找到前面所创建的一个事务。
  5. Binder驱动程序根据前面找到的事务的相关属性来找到发出通信请求的Client进程,并且通知Client进程将通信结果返回给对应的Binder代理对象处理。

.

从这个过程就可以看出,Binder代理对象依赖于Binder引用对象,而Binder引用对象又依赖于Binder实体对象,最后,Binder实体对象又依赖于Binder本地对象。

.

摘自:罗升阳. Android系统源代码情景分析(第三版)


参考连接:


《Android系统源码情景分析》



《深入理解Android内核设计思想》



写给 Android 应用工程师的 Binder 原理剖析



Android Binder设计与实现 – 设计篇



版权声明:本文为a740169405原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。