Android - 从浅到懂理解 Binder

背景

在插件化使用时,进程间通讯使用了AIDL进行跨进程通讯,而AIDL底层的实现是使用Binder机制。
在深刻了解了AIDL以后,咱们还须要再深刻学习Binderhtml

为何须要跨进程通讯(IPC)

一个进程通常是对应一个App,你不会但愿别的进程(App)可以垂手可得的能操做你的App吧,因此你的App只能访问App内部的数据。
可是有些场景是须要经过一个App去操做另外一个App的,好比:从App中调用系统的文件管理,实现文件读写。好比从App中读取手机通讯录的联系人信息。这种状况就须要实现进程间通讯了。web

为何是Binder?

Android 使用的 Linux 内核拥有着很是多的跨进程通讯机制,好比:管道,消息队列,共享内存,System V,Socket等;segmentfault

那么Android系统中的Binder究竟有何过人之处呢?缓存

上述的进程间通讯存在的问题:安全

  • Socket 做为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通讯和本机上进程间的低速通讯。
  • 消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,而后再从内核缓存区拷贝到接收方缓存区至少有两次拷贝过程。
  • 共享内存虽然无需拷贝,但控制复杂,难以使用。
  • 传统IPC没有任何安全措施,彻底依赖上层协议来确保。

Binder 的优点是:性能、稳定、安全。网络

  • 性能
    | IPC方式 | 数据拷贝次数|
    | ---- | ---- |
    | 共享内存 | 0 |
    | Binder | 1 |
    | Socket/管道/消息队列 | 2 |架构

  • 稳定
    Binder是基于C/S架构。经过客户端(Client)给服务端(Server)发送指令而服务端根据指令返回数据的方式实现。
    职责明确且互相独立,所以不易出错稳定性高。svg

  • 安全
    传统Linux IPC的接收方没法得到发送方进程可靠的UID/PID,从而没法鉴别对方身份;
    而Android做为一个开源系统,拥有很是多的开发平台,App来源甚广,所以手机的安全显得额外重要;
    对于普通用户,毫不但愿从商店下载的App能偷窥隐私数据、后台形成手机耗电等等问题。函数

Android为每一个安装好的应用程序分配了本身的UID,故进程的UID是鉴别进程身份的重要标志
Binder通讯能够得到通讯进程的UID,有了UID就能够鉴别进程的身份。
同时 Binder 支持实名 Binder, 保证了安全性。性能

在分析性能时,谈到了App数据缓存区内核缓存区
App数据缓存区用于进程间数据隔离,而内核缓存区的数据是能够共享的。

为了进一步了解进程间通讯,咱们还须要去了解

  • 用户空间/内核空间
  • 内核态/用户态
  • 内核模块/驱动

用户空间/内核空间

内核空间(Kernel Space)是系统内核运行的空间
用户空间(User Space)是用户程序运行的空间。

为了保证安全性,它们之间是隔离的。可是有的时候用户空间是须要去访问内核空间的。
好比:文件读写操做。

而用户空间访问内核空间的惟一方式就是系统调用

系统调用:内核态/用户态

Linux 使用两级保护机制:0 级供系统内核使用,3 级供用户程序使用。

经过系统调用这个统一入口接口,全部的资源访问都是在内核的控制下执行,以避免致使对用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。

当一个进程执行系统调用而陷入内核代码中执行时,咱们就称进程处于内核态。此时处理器处于特权级最高的(0级)内核代码中执行
当进程在执行用户本身的代码时,则称其处于用户态。此时处理器在特权级最低的(3级)用户代码中运行。

系统调用主要经过以下两个函数来实现:

  • copy_from_user() //将数据从用户空间拷贝到内核空间
  • copy_to_user() //将数据从内核空间拷贝到用户空间

传统的IPC就是使用上述两个系统调用的方法来实现进程间通讯 。

传统 IPC 的原理

  1. 消息发送方将要发送的数据存放在用户的内存缓存区中,经过系统调用进入内核态。
  2. 内核程序在内核空间开辟一块内核缓存区,操做系统调用copy_from_user() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中。
  3. 接收方进程在本身的用户空间开辟一块内存缓存区,而后内核程序调用copy_to_user() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区

这样数据发送方进程和数据接收方进程就完成了一次数据传输,也就是进程间通讯。

内核模块 / “驱动”

经过系统调用,用户空间能够访问内核空间,
那么若是一个用户空间想与另一个用户空间进行通讯怎么办呢?
很天然想到的是让操做系统内核添加支持;

传统的Linux通讯机制,好比Socket, 管道等都是内核支持的;
可是Binder并非Linux内核的一部分,它是怎么作到访问内核空间的呢?

Linux的动态可加载内核模块(Loadable Kernel Module,LKM)机制解决了这个问题;
该模块是具备独立功能的程序,它能够被单独编译,但不能独立运行。
它在运行时被连接到内核做为内核的一部分在内核空间运行。

Android 系统经过添加一个内核模块运行在内核空间,用户进程之间经过这个模块做为桥梁,就能够完成通讯。
Android系统中,这个运行在内核空间的,负责各个用户进程经过 Binder通讯的内核模块叫作Binder驱动;

TIP:

驱动程序通常指的是设备驱动程序(Device Driver),是一种可使计算机和设备通讯的特殊程序。
至关于硬件的接口,操做系统只有经过这个接口,才能控制硬件设备的工做;

驱动就是操做硬件的接口,为了支持Binder通讯过程,Android经过软件层面实现的Binder驱动,它相似于硬件接口用于和内核交互。
所以这个模块被称之为驱动。

前面说到了Binder的数据拷贝只有一次,而传统的IPC除了共享文件外都是最少两次数据拷贝
那么Binder驱动是如何实现的呢?

Binder IPC 机制实现原理

有点深奥,晦涩难懂。之后慢慢啃

Binder IPC 机制中实现数据拷贝仅一次的原理是用到了内存映射。数据拷贝是在

内存映射:

首先映射是指创建一种关系,而这里的内存映射顾名思义就是将用户空间的一块内存区域映射到内核空间。在映射的过程当中数据并无拷贝,只是双方创建了连接。这个连接在物理上是不存在,只是逻辑上存在。也就是说这个联系咱们看不见摸不着,只是咱们代码上给它们进行了关联。

映射关系创建后,用户对这块内存区域的修改能够直接反应到内核空间;反以内核空间对这段区域的修改也能直接反应到用户空间。

而咱们如何在代码上进行关联呢?

答案是经过操做系统调用 mmap() 方法来实现,可是 mmap() 一般是用在有物理介质的文件系统上的。

Binder 并不存在物理介质,所以mmap() 方法 并非为了在物理介质和用户空间之间创建映射,
mmap() 方法会返回一个指针ptr,该指针指向逻辑地址中的空间,这时候尚未数据拷贝。

要实现诗句拷贝须要将逻辑地址转换为物理地址,这个过程须要经过MMU(MemoryManagementUnit 内存管理单元)实现。

因为第一次数据通讯双方还没创建映射,MMU在地址映射表中是没法找到与指针ptr相对应的物理地址的,也就是MMU失败,将产生一个缺页中断,
缺页中断的中断响应函数会在swap 分区中寻找相对应的页面,
若是找不到(也就是该文件历来没有被读入内存的状况),则会经过mmap()创建的映射关系,从硬盘上将文件读取到物理内存中。

TIP:

swap 分区一般被称为交换分区,这是一块特殊的硬盘空间,即当实际内存不够用的时候,操做系统会从内存中取出一部分暂时不用的数据,放在交换分区中,从而为当前运行的程序腾出足够的内存空间。

因此数据拷贝是经过缺页中断机制将用户数据写入内存中。

一次完整的Binder IPC 通讯过程一般是这样:

  • 首先 Binder 驱动在内核空间建立一个数据接收缓存区;
  • 接着在内核空间开辟一块内核缓存区,创建发送方进程和接收方进程对内科缓存区的映射关系;
  • 发送方进程经过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,因为内核缓存区和接收进程的用户空间存在内存映射,所以也就至关于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通讯。

总结

BinderAndroid系统提供的进程间通讯的一种方式。

之因此提供Binder是由于传统的IPC机制存在一些问题

  • 性能:传统的IPC机制,如:Socket,管道,消息队列在通讯时数据会经历两次拷贝。而Binder仅一次。
  • 稳定:Binder基于C/S架构模式,代码结构清晰不易出错。
  • 安全:Android是开源系统,众多软件鱼龙混杂。传统的IPC机制,通讯双方不能鉴别身份。而Binder提供了UID用于标识进程,进而能鉴别通讯双方。

传统的IPC通讯机制原理是用到了系统调用的两个方法

  • copy_from_user() : 将数据从用户空间拷贝到内核空间
  • copy_to_user() : 将数据从内核空间拷贝到用户空间

实现过程是:

  1. 发送方将数据存入用户的内存缓存区
  2. 接收方在在本身进程开辟用户的内存缓存区
  3. 内核空间开辟内核缓存区
  4. 发送方从用户态进入内核态,并经过系统调用copy_from_user()将发送方的内存缓存区的数据拷贝放入内核缓存区,而后经过copy_to_user()将数据拷贝到接收方的内存缓存区

这就是传统IPC机制通讯须要两次数据拷贝的问题。

Binder仅须要一次数据拷贝。
它的原理是用到了内存映射和系统调用 mmap() 方法

  • 经过内存映射的方式实现发送方-内核-接收方之间的对应关系。
  • 再经过系统调用 mmap() 方法返回Binder驱动中的逻辑地址,而逻辑地址要和物理地址转换须要经过MMU
  • MMU在链接逻辑地址和物理地址的时候会调用缺页中断方法swap 分区中寻找相对应的数据。若是没找到,说明数据不存在,则须要进行数据拷贝数据拷贝使用的仍是系统调用 copy_from_user()方法。
  • 因为存在映射关系,因此数据拷贝一次便可实现发送方和接收方的进程通讯。

参考

本文同步分享在 博客“_龙衣”(CSDN)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索