Android Binder实现浅析-Binder驱动

简介

Android是如何实现跨进程通讯的,你们熟悉的Binder是什么,怎么设计的,进程间的数据如何发送接收的。本文将以及解析,并对Binder驱动实现、Native层实现、Java层实现三块作一个总结分析。node

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驱动与应用程序进程之间的关系。数据结构

image

固然,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实体对象,以引用编号的形式放回给查询进程

注册Binder实体信息到SM的时候,请求数据中须要写到Binder实体的描述信息,以后进行查询的时候就是根据描述信息来获取到对应的Binder应用编号。3d

到这里,咱们能够看出,其实整个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机制。

共享内存为何快,是由于共享内存至关因而将两个进程的虚拟地址空间指向了一块物理内存,两个进程对该内存区域的修改,可以直接反应到对方进程中,也就是不须要对数据进行拷贝。

image

前面说到,Binder是经过mmap来实现的,理论上,mmap也可让两个进程映射到同一段物理内存区域(文件)上。可是Binder没有这样实现,若是这样的话,和共享内存就同样了。那Binder又是如何实现的呢。

首先,Binder有驱动程序,全部数据传输和接收,都是经过Binder驱动来操做的。这就带来一个问题,Binder驱动是运行在内核态的,那么数据在使用Binder驱动传输时,是须要在内核内存空间与用户内存空间进行拷贝操做的。

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

其实,Binder驱动内部并不须要两次数据的拷贝,缘由在于Binder将内核内存空间与用户内存空间进行了内存映射操做,具体以下图

image

首先,咱们从数据接收进程看,内核与用户内存空间,经过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架构在最核心的一部分,驱动须要作的事情不少

  1. 全部Server端的Binder实体,须要在驱动中注册
  2. Client端获取Binder时,须要为Client建立Binder引用,并把引用编号信息记录在驱动中
  3. 维护各个Client中的引用于Binder实体之间的映射关系
  4. 经过引用编号找到对应实体
  5. 建立Server端的Binder实体
  6. etc…

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实体进行通讯

image

相关文章
相关标签/搜索