Android Binder通信原理--02:Binder入门篇

2023-10-14  本文已影响0人  DarcyZhou

本文转载自:Android10.0 Binder通信原理(二)-Binder入门篇

本文基于Android 10.0源码分析

1.摘要

  本节主要来讲解Android 10.0 Binder的设计原理,如何设计一个Binder通信。

2.概述

  在Android应用、Android系统开发的时候,相信很多人都听过Binder的概念,而且无意间就用到了Binder机制。例如:写一个应用打开手电筒功能,某个应用启动服务等。
  这些动作都涉及到一个概念:进程间通信。Android中的每个应用都是独立的进程,都有自己虚拟内存,两个进程之间不能互相访问数据。
  在Android中,应用进程间互相访问数据,常用的通信方式就Binder。从前一节,我们知道从Android 8.0 开始,Binder机制,被拆分成了Binder(System分区进程间通信)、HwBinder(支持System/Vendor分区进程间通信)、VndBinder(Vendor分区进程间通信)。
  现在我们先单独分析一下Binder的机制,HwBinder和VndBinder留到后面慢慢分析。

3.Binder通信模型

  下图中涉及到Binder模型的4类角色:Binder驱动,ServiceManager,Server和Client。Binder机制的目的是实现IPC(Inter-Process Communication),即Client和Server之间的通信。
  其中Server,Client,ServiceManager运行于用户空间,Binder驱动运行于内核空间。这四个角色的关系和互联网类似:Server是服务器,Client是客户终端,ServiceManager是域名服务器(DNS),驱动是路由器。

Binder2-1.png

4.概念理解

(1)IPC (进程间通信:Inter-process communication)
  IPC属于通信机制,Android中常用的IPC通信:管道、共享内存、消息队列、信号量、socket、binder。

(2)RPC(远程过程调用Remote Procedure call)

(3)代理模式
  为其他对象提供代理对象,以控制对这个对象的访问。
  由于进程隔离的存在,一个进程内部的对象对另外一个进程来说没有任何意义。另外如果是代理对象的话,它可以存在各个进程内,就好比咱们的AMS和PMS。

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

(5)内核空间

  可以访问受保护的内存空间,有访问底层硬件设备的所有权限。

(6)用户空间
  相对于内核空间,上层应用程序所运行的空间以及Native层进程运行的空间就是用户空间,用户空间访问内核空间的唯一方式就是系统调用。

(7)系统调用/内核态/用户态
  从逻辑上看,内核空间和用户空间是独立的,那么用户空间总要有办法调用内核空间,唯一的调用方式就是系统调用(System Call),通过这个统一入口接口,所有的资源访问都是在内核的控制下执行,以免导致对用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。
  当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。

5.什么是Binder?

  Binder的英文意思是粘结剂,把两个不相关的进程粘在一起,让两个进程可以进行数据交互。 比如我们写一个应用,打开手电筒功能,那么要把打开这个动作,发给管理服务,这个发送的过程就是通过Binder机制来实现。

6.为什么要用Binder?

  Android使用的Linux内核拥有着非常多的跨进程通信机制,比如管道、共享内存、消息队列、信号量、socket等;为什么还需要单独搞一个Binder出来呢?

  主要有两点:性能和安全。

(1)高效
  在移动设备上,广泛地使用跨进程通信肯定对通信机制本身提出了严格的要求;Binder相对于传统的Socket方式,更加高效。
  IPC 内存拷贝次数:

Binder2-2.png

(2)安全
  传统的进程通信方式对于通信双方的身份并没有做出严格的验证,接收方无法获得对方进程可靠的UID/PID(用户ID/进程ID),只有在上层协议上进行架设。
  比如Socket通信ip地址是客户端手动填入的,都可以进行伪造;而Binder机制为每个进程分配了UID/PID来作为鉴别身份的标示,从协议本身就支持对通信双方做身份校检,因而大大提升了安全性。

(3)可以很好的实现Client-Server(CS)架构
  对于Android系统,Google想提供一套基于Client-Server的通信方式。当Client需要获取某Server的服务时,只需要Client向Server发送相应的请求,Server收到请求之后进行处理,处理完毕再将反馈内容发送给Client。
  但是,目前Linux支持的"传统的管道/消息队列/共享内存/信号量/Socket等"IPC通信手段中,只有Socket是Client-Server的通信方式。但是,Socket主要用于网络间通信以及本机中进程间的低速通信,它的传输效率太低。

7.如何设计一个Binder?

  在理解Binder架构前,我们来考虑下,如果是你,该如何设计一个Binder的进程间通信机制。要实现一个IPC通信那么需要几个核心要素:

(1)发起端:肯定包括发起端所从属的进程,以及实际执行传输动作的线程;

(2)接收端:接收发送端的数据;

(3)待传输的数;

(4)内存映射,内核态。

  首先先画一个最简单的IPC通信图:

Binder2-3.png

进程Process1和进程Process2通过IPC通信机制进行通信。再进行扩展调整,把IPC机制换成Binder机制,那么就变成如下的图形:

Binder2-4.png

由于Android存在进程隔离,那么两个进程之间是不能直接传输数据的,Process1需要得到Process2的代理,Process2需要一个实体。

Binder2-5.png

为了实现RPC,我们的代理都是提供接口,称为“接口代理”,实体需要提供“接口实体”,如下图所示:

Binder2-6.png

我们把代理改成BpBinder,实体改成BBinder,接口代理改成BpInterface,接口实现体改成BnInterface。我们都知道两个进程的数据共享,需要陷入内核态,那就需要一个驱动设备“/dev/binder”,同时需要一个守护进行来进行service管理,我们成为ServiceManager。进一步演变为:

Binder2-7.png

假如我们想要把通过Process1的微信信息发送给Process2的微信,我们需要做下面几步:

(1)Process2在腾讯服务器中进行注册(包括微信名称、当前活动的IP地址等);
(2)Process1从朋友列表中中查找到 Process2的名称,这就是Process2的别名:"service_name";
(3)Process1 编写消息消息内容,点击发送。 这里的消息内容就是IPC数据;
(4)数据会发送到腾讯的服务器,服务器理解为Binder驱动;
(5)服务器从数据库中解析出IPC数据,找到Process2信息,转到Process2注册的地址, 数据库理解为ServiceManager;
(6)把数据发给Process2,完成Process1和Process2的通信。

  我们可以简单的把上面的顺序内容进行转换:

(1)Binder驱动:腾讯服务器;
(2)数据库:ServiceManager;
(3)service_name:Process2的微信名称;
(4)IPC数据:Process1 发送的微信消息。

  Native C/C++和内核进行通信需要通过系统调用,ServiecManager的主要用来对Service管理,提供了add\find\list等操作。Native进程的数据直接可以通过系统调用陷入内核态,进入图像转换,变为如下:

Binder2-8.png

上面列举的是Native C/C++空间的进程进行Binder通信机制,那么JAVA层是如何通信的呢,Native层的Binder提供的是libbinder.so,那么从JAVA到Native,需要经过JNI、Framework层的封装,JNI层的命名通常为android_util_xxx,我们这里是binder机制,那么JNI层的文件为android_util_binder,同时Native的BBinder不能直接传给JAVA层,在JNI里面转换了一个JavaBBinder对象。

  Framework层给应用层提供时,其实提供的也是一个代理,我们也称之为BinderProxy。在JAVA侧要对应一个Binder的实体,称之为Binder。JAVA侧的服务进行也需要一个管理者,类似于Native,创建了JAVA的ServiceManager,那么设计如下:

Binder2-9.png

8.Binder的通信原理

  Binder通信采用C/S架构,从组件视角来说,包含Client、Server、ServiceManager以及Binder驱动,其中 ServiceManager用于管理系统中的各种服务。
  Binder在framework层进行了封装,通过JNI技术调用Native(C/C++)层的Binder架构。Binder在Native层以 ioctl的方式与Binder驱动通讯。

  Binder通信流程如下:

(1)首先服务端需要向ServiceManager进行服务注册,ServiceManager有一个全局的service列表svcinfo,用来缓存所有服务的handle和name。

(2)客户端与服务端通信,需要拿到服务端的对象,由于进程隔离,客户端拿到的其实是服务端的代理,也可以理解为引用。客户端通过ServiceManager从svcinfo中查找服务,ServiceManager返回服务的代理。

(3)拿到服务对象后,我们需要向服务发送请求,实现我们需要的功能。通过BinderProxy将我们的请求参数发送给内核,通过共享内存的方式使用内核方法copy_from_user()将我们的参数先拷贝到内核空间,这时我们的客户端进入等待状态。然后Binder驱动向服务端的todo队列里面插入一条事务,执行完之后把执行结果通过copy_to_user()将内核的结果拷贝到用户空间(这里只是执行了拷贝命令,并没有拷贝数据,binder只进行一次拷贝),唤醒等待的客户端并把结果响应回来,这样就完成了一次通讯。

  在这里其实会存在一个问题,Client和Server之间通信是称为进程间通信,使用了Binder机制,那么Server和ServiceManager之间通信也叫进程间通信,Client和Server之间还会用到ServiceManager,也就是说Binder进程间通信通过Binder进程间通信来完成,这就好比是孵出鸡前提却是要找只鸡来孵蛋,这是怎么实现的呢?

Binder2-10.png

9.Binder的内存管理

  ServiceManager启动后,会通过系统调用mmap向内核空间申请128K的内存,用户进程会通过mmap向内核申请(1M-8K)的内存空间。这里用户空间mmap (1M-8K)的空间,为什么要减去8K,而不是直接用1M?

  Android的git commit记录:

Modify the binder to request 1M - 2 pages instead of 1M. The backing store in the kernel requires a guard page, so 1M allocations fragment memory very badly. Subtracting a couple of pages so that they fit in a power of two allows the kernel to make more efficient use of its virtual address space.

大致的意思是:kernel的“backing store”需要一个保护页,这使得1M用来分配碎片内存时变得很差,所以这里减去两页来提高效率,因为减去一页就变成了奇数。

系统定义:BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2) = (1M- sysconf(_SC_PAGE_SIZE) * 2)

  ARM32内存占用分配:

Binder2-11.png

但随着现在的硬件发展越来越迅速,应用程序的运算也越来越复杂,占用空间越来越大,原有的4G虚拟内存已经不能满足用户的需求,因此,现在的Android基本都是用64位的内存机制。

  ARM64内存占用分配:

Binder2-12.png

用户地址空间(服务端-数据接收端)和内核地址空间都映射到同一块物理地址空间。
  Client(数据发送端)先从自己的用户进程空间把IPC数据通过copy_from_user()拷贝到内核空间。而Server端(数据接收端)与内核共享数据(mmap到同一块物理内存),不再需要拷贝数据,而是通过内存地址空间的偏移量,即可获悉内存地址,整个过程只发生一次内存拷贝。

Binder2-13.png

10.Binder中各角色之间关系

  下图展示了Binder中各个角色之间的关系:

Binder2-14.png

(1)Binder实体

(2)Binder引用/代理

(3)远程服务
  Server都是以服务的形式注册到ServiceManager中进行管理的。如果将Server本身看作是"本地服务"的话,那么Client中的"远程服务"就是本地服务的代理。远程服务就是本地服务的一个代理,通过该远程服务Client就能和Server进行通信。

(4)ServiceManager守护进程
  ServiceManager是用户空间的一个守护进程。当该应用程序启动时,它会和Binder驱动进行通信,告诉Binder驱动它是服务管理者;对Binder驱动而言,它则会新建ServiceManager对应的Binder实体,并将该Binder实体设为全局变量。

11.源码路径

(1)binder驱动

/kernel/msm-4.9/drivers/android/*

(2)servicemanager

  /frameworks/native/cmds/servicemanager/*

(3)libbinder

  /frameworks/native/libs/binder/*

(4)****JAVA****层

  /frameworks/base/core/java/android/os/*

(5)源码下载

https://github.com/LineageOS
上一篇 下一篇

猜你喜欢

热点阅读