借助 AIDL 理解 Android Binder 机制——Binder 前因后果

AIDL 是 Android Interface Definition Language(Android 接口定义语言)的缩写,它是 Android 进程间通讯的接口语言。因为 Android 系统的内核是 Linux,它采用了进程隔离机制,使得不一样的应用程序运行在不一样的进程当中,有时候两个应用之间须要传递或者共享某些数据,就须要进行进程间的通讯讯。缓存

Android 进程间通讯的方式有不少种,好比 Messenger、文件(SharePreference)、AIDL、Socket 和 Content Provider 等,它们当中 Messenge、AIDL 和 Content Provider 的底层都是依赖于 Binder 机制去实现的。除此以外,Android 四大组件的启动和通讯的核心过程也是经过 Binder 机制去实现的,这里,咱们借助 AIDL 来了解 Binder 的实现机制。安全

在了解 AIDL 机制和用法以前,首先要了解几个概念,这对后续的深刻理解有较大的帮助。服务器

进程隔离

如下内容来自维基百科架构

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

Linux IPC 原理

因为 Linux 采用了虚拟地址空间技术,操做系统在逻辑上将虚拟内存分为用户空间(User Space)内核空间(Kernel Space),普通应用程序运行在用户空间,系统内核运行在内核空间,为了控制应用程序的访问范围、保证系统安全,用户空间只能经过系统调用的方式去访问内核空间。当进程执行系统调用而陷入内核代码的时候,该进程则进入了内核态,相比之下,进程在用户空间执行本身的代码的时候,则是处于用户态函数

因为进程 A 和进程 B 的虚拟地址不一样,所以它们之间是相互透明的,都觉得本身独享了系统的资源,固然也不能直接跟对方交互。可是,有些状况下有些进程不免会须要跟其余进程进行交互,这个交互过程就叫 IPC(Inter-Process Communication,进程间通讯)。IPC 的实质就是数据的交互,所以咱们这里将进行 IPC 过程当中的通讯调用方和被调用放分别称为数据发送方和数据接收方,IPC 通讯的过程以下:post

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

Linux IPC

经过以上过程,一次 IPC 就完成了,可是这种传统的 IPC 机制有两个问题:性能

  • 性能比较低:整个过程数据的传递须要经历发送方内存缓存区——内核缓存区——接收方内存缓存区的过程
  • 接收方进程事先不知道须要开辟多大的内存用于存放数据,所以须要开辟尽量大的空间或者事先调用 API 来解决这个问题,这两种方式不是浪费空间就是浪费时间。

Binder IPC 原理

为了克服 Linux 传统的 IPC 机制中的不足之处,Android 系统引入了 Binder 机制,从字面上看 Binder 是胶水的意思,在这里,Binder 的职责是在不一样的进程之间扮演一个桥梁的角色,让它们之间可以相互通讯。从上一小节内容能够了解到,进程间的通信少不了 Linux 内核的支持,而 Binder 并不属于内核的一部分,可是,得益于 Linux 的 LKM(Loadable Kernel Module) 机制:学习

模块是具备独立功能的程序,它能够被单独编译,但不能独立运行。它在运行时被连接到内核做为内核的一部分在内核空间运行网站

所以,Binder 做为这种模块存在于内核之中,也称为 Binder 驱动。回顾上一小节的内容,传统 Linux IPC 的过程须要经历两次数据拷贝,Binder 借助 Linux 的另外一个特性,只用一次数据拷贝,就能实现 IPC 过程,这就是内存映射

Binder IPC 机制中涉及到的内存映射经过 mmap() 来实现,mmap() 是操做系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间,映射关系创建后,用户对这块内存区域的修改能够直接反应到内核空间;反以内核空间对这段区域的修改也能直接反应到用户空间。

内存映射能减小数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正由于如此,内存映射可以提供对进程间通讯的支持。

Binder IPC 通讯过程以下:

  1. Binder 驱动在内核空间建立一个数据接收缓存区
  2. 而后在内核空间开辟一块内存缓存区并与数据接收缓存区创建映射关系,同时,创建数据接收缓存区数据接收方的内存缓存区的映射关系
  3. 数据发送方经过系统调用 copy_from_user 函数将数据从内存缓存区拷贝到内核缓存区,因为内核缓存区经过数据接收缓存区跟数据接收方的内存缓存区存在间接的映射关系,至关于将数据直接拷贝到了接收方的用户空间,这样便完成了一次 IPC 的过程。

Android IPC

Binder 通讯模型和通讯过程

在进行 Binder IPC 的时候,实际状况比上面介绍的要复杂,Binder 通信模型是基于 C/S 架构的,通讯调用方进程称为 Client 进程,被调用方称为 Server 进程,除此以外还须要 ServiceManager 和 Binder 驱动的参与,它们都是经过 open/mmap/iotl 等系统调用来访问设备文件 dev/binder 来实现 IPC 过程的。

Binder IPC module

其中,Client、Server 和 ServiceManager 运行在用户空间,Binder Driver 运行在内核空间,Client 和 Server 需由用户本身实现,ServiceManager 和 Binder Driver 则由系统提供。

Android Binder 设计与实现 文章中对 Client 和 Server 等角色有详细的描述:

Binder 驱动: Binder 驱动就如同路由器同样,是整个通讯的核心;驱动负责进程之间 Binder 通讯的创建,Binder 在进程之间的传递,Binder 引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。

ServiceManager 与实名 Binder: ServiceManager 和 DNS 相似,做用是将字符形式的 Binder 名字转化成 Client 中对该 Binder 的引用,使得 Client 可以经过 Binder 的名字得到对 Binder 实体的引用。注册了名字的 Binder 叫实名 Binder,就像网站同样除了除了有 IP 地址意外还有本身的网址。Server 建立了 Binder,并为它起一个字符形式,可读易记得名字,将这个 Binder 实体连同名字一块儿以数据包的形式经过 Binder 驱动发送给 ServiceManager ,通知 ServiceManager 注册一个名为“张三”的 Binder,它位于某个 Server 中。驱动为这个穿越进程边界的 Binder 建立位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager。ServiceManger 收到数据后从中取出名字和引用填入查找表。

细心的读者可能会发现,ServierManager 是一个进程,Server 是另外一个进程,Server 向 ServiceManager 中注册 Binder 必然涉及到进程间通讯。当前实现进程间通讯又要用到进程间通讯,这就好像蛋能够孵出鸡的前提倒是要先找只鸡下蛋!Binder 的实现比较巧妙,就是预先创造一只鸡来下蛋。ServiceManager 和其余进程一样采用 Bidner 通讯,ServiceManager 是 Server 端,有本身的 Binder 实体,其余进程都是 Client,须要经过这个 Binder 的引用来实现 Binder 的注册,查询和获取。ServiceManager 提供的 Binder 比较特殊,它没有名字也不须要注册。当一个进程使用 BINDERSETCONTEXT_MGR 命令将本身注册成 ServiceManager 时 Binder 驱动会自动为它建立 Binder 实体(这就是那只预先造好的那只鸡)。其次这个 Binder 实体的引用在全部 Client 中都固定为 0 而无需经过其它手段得到。也就是说,一个 Server 想要向 ServiceManager 注册本身的 Binder 就必须经过这个 0 号引用和 ServiceManager 的 Binder 通讯。类比互联网,0 号引用就比如是域名服务器的地址,你必须预先动态或者手工配置好。要注意的是,这里说的 Client 是相对于 ServiceManager 而言的,一个进程或者应用程序多是提供服务的 Server,但对于 ServiceManager 来讲它仍然是个 Client。

Client 得到实名 Binder 的引用: Server 向 ServiceManager 中注册了 Binder 之后, Client 就能经过名字得到 Binder 的引用了。Client 也利用保留的 0 号引用向 ServiceManager 请求访问某个 Binder: 我申请访问名字叫张三的 Binder 引用。ServiceManager 收到这个请求后从请求数据包中取出 Binder 名称,在查找表里找到对应的条目,取出对应的 Binder 引用做为回复发送给发起请求的 Client。从面向对象的角度看,Server 中的 Binder 实体如今有两个引用:一个位于 ServiceManager 中,一个位于发起请求的 Client 中。若是接下来有更多的 Client 请求该 Binder,系统中就会有更多的引用指向该 Binder ,就像 Java 中一个对象有多个引用同样。

所以,ServiceManager 是 Binder IPC 通讯过程的核心,是上下文的管理者,Binder 服务端必须先向 ServerManager 注册才可以为客户端提供服务,Binder 客户端在与服务端通讯以前须要从 ServerManager 中查找并获取 Binder 服务端的引用。Binder IPC 过程能够总结成如下步骤:

  1. 某个进程使用 BINDER_SET_CONTEXT_MGR 命令经过 Binder 驱动将本身注册成 ServiceManager,负责管理全部的 Service
  2. 各个 Server 经过 Binder 驱动向 ServiceManager 注册 Binder 实体,代表本身能够对外提供服务,这时 Binder 驱动会为这个 Binder 建立位于内核中的实体节点以及 ServiceManager 对该节点的引用,并将名字和该引用打包给 ServiceManager,ServiceManager 接收到数据包后将数据包中的名字和引用填入查找表中
  3. Client 经过上面 Server 的名字在 Binder 驱动的帮助下从 ServiceManager 中获取到该 Server 对应的 Binder 引用对象,因为该引用对象一样具备 Server 的能力,所以 Client 能够经过这个引用与真实的 Server 进行交互

仍是universus 老师的图:

Binder Role

总结

进程隔离虽然使操做系统的安全性和应用程序的稳定性获得了提高,但同时也给 IPC 带来了必定的难度,Android 系统巧妙地应用了 Binder 机制,使得系统得于在存储空间和硬件性能等有限的移动设备上可以流畅地运行。关于 Binder 在应用层的使用和分析,请看下一篇文章内容:借助 AIDL 理解 Android Binder 机制——AIDL 的使用和原理分析

参考文章

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

Binder学习指南

若是你对文章内容有疑问或者有不一样意见,欢迎留言,咱们一同探讨。

相关文章
相关标签/搜索