跨进程通信—Binder机制

一、前言

    在 Android 系统中,当我们深挖底层原理时,经常会听到 Binder 通信,当我们去学习 Activity 启动机制时,又或者学习 AMS 时,还是 ServiceAIDL 底层原理,都离不开 Binder ,因此 Binder 是非常重要的。
在这里提供以下阅读小技巧来加深一下记忆:

  • 这个说法的依据是什么?
  • 怎么以自己的方式去解释这个概念?
  • 怎么在自己的项目中应用这个技巧?
  • 这个概念的具体代码实现是怎样的?
  • 这个实现存在哪些问题?

二、什么是 Binder?

    Binder 中文的翻译为 粘合剂,指的是连接两个不同的进程。首先要搞清楚 Binder 是个什么东西,对于 Binder 是什么的问题,应该采用不同的角度来解答。
在我的前一篇文章 AIDL的使用与浅析 文末说 AIDL 底层其实也是通过 Binder 来实现的,因此在进程通信的层次来说的话,Binder 是进程间通信的一种方式。
不单单只有进程这一层次而已,这里借用 《Android 开发艺术探索》里的一段话来总结一下:

    1. 从机制模型角度来说:Binder 是一种 Android 中实现跨进程通信的方式
    1. 从模型结构、组成来说:Binder 是一种虚拟的物理设备驱动(Binder 驱动)
    1. 从Android 代码角度来说:Binder 就是一个类,将 Binder 机制模型以代码的形式呈现出来。


      什么是 Binder

三、为什么选择 Binder?

    Android 系统是基于 Linux 内核的,Linux 已经提供了管道、消息队列、共享内存和 Socket 等 IPC 机制。那为什么 Android 还要提供 Binder 来实现 IPC 呢?主要是基于性能、稳定性和安全性几方面的原因。

     Binder 共享内存 Socket
性能 需要拷贝一次 无需拷贝 需要拷贝两次
特点 基于C/S架构/易用性高 控制复杂、易用性差 基于C/S架构作为通用接口,传输效率低,开销大
安全性 为每个APP分配UID、支持实名匿名 依赖上层协议、访问接入点开放、不安全 依赖上层协议、访问接入点开放、不安全

下面具体说明一下具体优势:

  • 性能:
        Socket:作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。
    消息队列和管道:采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。
    共享内存:虽然无需拷贝,但控制复杂,难以使用。(在多进程情况下需要进行加锁等复杂操作)
    Binder 只需要一次数据拷贝,性能上仅次于共享内存。
  • 特点:
        Binder 基于 C/S 架构,客户端(Client)有什么需求就丢给服务端(Server)去完成,架构清晰、职责明确又相互独立,自然稳定性更好。共享内存虽然无需拷贝,但是控制复杂,难以使用。从稳定性的角度讲,Binder 机制是优于内存共享的。
  • 安全性:
        Android 作为一个开放性的平台,市场上有各类海量的应用供用户选择安装,因此安全性对于 Android 平台而言极其重要。作为用户当然不希望我们下载的 APP 偷偷读取我的通信录,上传我的隐私数据,后台偷跑流量、消耗手机电量。传统的 IPC 没有任何安全措施,完全依赖上层协议来确保。首先传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份。Android 为每个安装好的 APP 分配了自己的 UID,故而进程的 UID 是鉴别进程身份的重要标志。传统的 IPC 只能由用户在数据包中填入 UID/PID,但这样不可靠,容易被恶意程序利用。可靠的身份标识只有由 IPC 机制在内核中添加。其次传统的 IPC 访问接入点是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。同时 Binder 既支持实名 Binder,又支持匿名 Binder,安全性高。

四、Linux 下传统的进程间通信原理

对于 Linux 进程间的理解可以参考这篇文章写的很详细 Android Binder 原理解析 以下是对部分内容的摘抄

4.1 基本概念介绍:

    这里我们先从 Linux 中进程间通信涉及的一些基本概念开始介绍,然后逐步展开,向大家说明传统的进程间通信的原理:


Linux 内核

在开始讲解之前,我们先来介绍几个概念:

4.1.1 进程隔离:

    简单的说就是操作系统中,进程与进程间内存是不共享的。两个进程就像两个平行的世界,A 进程没法直接访问 B 进程的数据,这就是进程隔离的通俗解释。A 进程和 B 进程之间要进行数据交互就得采用特殊的通信机制:进程间通信(IPC)。

4.1.2 进程空间划分:用户空间(User Space) / 内核空间(Kernel Space):

现在操作系统都是采用的虚拟存储器,对于 32 位系统而言,它的寻址空间(虚拟存储空间)就是 2 的 32 次方,也就是 4GB。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也可以访问底层硬件设备的权限。为了保护用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间(User Space)和内核空间(Kernel Space)。针对 Linux 操作系统而言,将最高的 1GB 字节供内核使用,称为内核空间;较低的 3GB 字节供各进程使用,称为用户空间。
    简单的说就是,内核空间(Kernel)是系统内核运行的空间,用户空间(User Space)是用户程序运行的空间。为了保证安全性,它们之间是隔离的。

进程空间 / 内核空间

4.1.3 系统调用:用户态与内核态

    虽然从逻辑上进行了用户空间和内核空间的划分,但不可避免的用户空间需要访问内核资源,比如文件操作、访问网络等等。为了突破隔离限制,就需要借助系统调用来实现。系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。

Linux 使用两级保护机制:0 级供系统内核使用,3 级供用户程序使用。
    当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码的时候,我们称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。

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

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

五、Binder 源码解析

    在前一篇文章 AIDL的使用与浅析讲了源码的几个重要方法

你可能感兴趣的:(跨进程通信—Binder机制)