“我正在参加「掘金·启航计划」”
前言
Binder对于Android系统来说非常重要,毫不夸张的说:无Binder就无Android,Binder是Android系统的任督二脉。正如其名Binder其名“粘合剂”一般,Binder是系统间各个组件的桥梁。贯穿整个Android系统。
理解Binder对于理解整个Android系统有着非常重要的作用,
我们都知道,Android是在Linux基础上构建的,如果有人问你Android相比较Linux有什么明显的特点,回答肯定是Android的Binder是相较于Linux最大的特点。
Android系统的四大组件(activity、service、content provider、broadcast receiver)、ActivityManagerService(简称:AMS)、PacageManagerService(简称:PMS)、BatterManager等等系统服务无一不和Binder挂钩,很多人对Binder的理解只是通过aidl进行跨进程通信。至于怎么灵活使用Binder,内部是怎么执行到驱动都不太懂,作为一名老Android来说很惭愧,所以打算重学基础,理解FrameWork,Binder是必须要迈出的一步。
为了理解Binder我们需要先澄清一些概念。
- 什么是跨进程通信(IPC)?
- 为什么Android非得需要跨进程通信(IPC),
- Android又是怎么做到跨进程通信(IPC)的?
- 到底什么是是Binder,作用是什么?
这些问题的背后都与 Binder 有莫大的关系,要弄懂上面这些问题理解 Bidner 通信机制是必须的。
Linux下传统的进程间通信原理
了解 Linux IPC 相关的概念和原理有助于我们理解 Binder 通信原理。因此,在介绍 Binder 跨进程通信原理之前,我们先聊聊 Linux 系统下传统的进程间通信是如何实现。
这里我们先从 Linux 中进程间通信涉及的一些基本概念开始介绍,然后逐步展开,向大家说明传统的进程间通信的原理。
上图展示了 Liunx 中跨进程通信涉及到的一些基本概念:
- 进程隔离
- 进程空间划分:用户空间(User Space)/内核空间(Kernel Space)
- 系统调用:用户态/内核态
1. 进程隔离
为了保证 安全性 & 独立性,一个进程 不能直接操作或者访问另一个进程,即Android
的进程是相互独立、隔离的。
譬如两个进程就像两个平行的世界,A 进程没法直接访问 B 进程的数据,这就是进程隔离的通俗解释。A 进程和 B 进程之间要进行数据交互就得采用特殊的通信机制:进程间通信(IPC)。
2. 进程空间
进程空间划分为:用户空间(User Space) / 内核空间(Kernel Space)
现在操作系统都是采用的 虚拟存储器
,对于 32 位系统而言,它的寻址空间(虚拟存储空间)就是 2 的 32 次方,也就是 4GB。操作系统的核心是内核,独立于普通的应用程序,内核是计算机硬件与其进程之间的核心接口。它负责两者之间的通信,还要尽可能高效地管理资源。
可以这样理解 : 内核就像是一个为高管(硬件)服务的个人助理。助理的工作就是将员工(用户)的消息和请求(进程)转交给高管,记住存放的内容和位置(内存),并确定在任何特定的时间谁可以拜访高管、会面时间有多长,也可以保证高管的人身安全。
在操作系统层面,为了保护用户进程不能直接操作内核,保证内核的安全,操作系统从逻辑上将虚拟空间划分为用户空间(User Space)和内核空间(Kernel Space)。针对 Linux 操作系统而言,将最高的 1GB 字节供内核使用,称为内核空间;较低的 3GB 字节供各进程使用,称为用户空间。
总结来说就是:内核空间(Kernel Space)是系统内核运行的空间,用户空间(User Space)是用户程序运行的空间。为了保证安全性,它们之间是绝对隔离的。
3. 系统调用
虽然从逻辑上对进程空间进行了用户空间和内核空间的划分,但不可避免的用户空间需要访问内核资源,比如文件操作、访问网络等等。为了突破隔离限制,就需要借助 系统调用 来实现。系统调用是用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核的控制下进行的,避免了用户程序对系统资源的越权访问,提升了系统安全性和稳定性。
用户态与内核态
Linux 使用两级保护机制:0 级供系统内核使用,3 级供用户程序使用。
1> 当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)
。 此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。
2> 当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)
。此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。
系统调用主要通过如下两个函数来实现:
copy_from_user() //将数据从用户空间拷贝到内核空间
copy_to_user() //将数据从内核空间拷贝到用户空间
4. Linux 跨进程通信( IPC )
理解了上面的几个概念,我们再来看看传统的 IPC 方式中,进程之间是如何实现通信的。
通常的做法是(如下图所示):
1:发送进程 通过 系统调用, 将需要发送的数据拷贝到 Linux 进程的内核空间的缓存区中(即通过 copy_from_user()
进行一次数据拷贝)。
2:内核服务程序被唤醒,唤醒接收进程的接收线程,通过 系统调用
将数据发送到接收进程的 用户空间 中(即通过 copy_to_user()
进行第二次数据拷贝),最终及完成了数据发送。
这种传统的 IPC 通信方式有两个问题:
- 效率低下,一次数据传递需要经历 2 次数据拷贝:内存缓存区 --> 内核缓存区 --> 内存缓存区。
- 接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用 API 接收消息头来获取消息体的大小,这两种做法前者浪费空间后者费时间。
Binder 跨进程通信机制
Binder通信采用C/S架构,从组件视角来说,包含 Client、Server、ServiceManager 以及 binder驱动 ,其中ServiceManager用于管理系统中的各种服务。架构图如下所示:
首先是要注册一个 ServiceManager,server端创建实名binder,向SMG注册自己可以提供的服务,以及该实名binder的标签,smg会在svcinfo 链表中缓存该server提供的binder信息,当client需要使用该服务时,只需要向smg中查询服务,获取server端binder的引用就可以了,这其中所有的通讯细节,全部需要binder驱动来实现。
binder驱动为应用层提供了open()
,mmap()
,poll()
,ioctl()
等标准的文件操作【注1】,open()
负责打开驱动,mmap()
负责对binder
做内核空间向用户空间的地址映射,ioctl()
负责binder协议的通信。
打开一次binder通讯,大致分为以下流程:
- 1 调用
open()
方法打开binder
驱动。 - 2 调用
mmap()
方法申请一块内存用来接受通信中的数据,并进行内存映射。 - 3 调用
ioctl()
方法 开启binder通讯。
勉强看完还是云里雾里,一大堆名词,一头雾水。完全看不懂,我想说的是: Binder机制终究不是三言两语就能解释清楚的,我们要一点点的啃,本篇我们先大概了解一下,后边的几篇我们将一步步慢慢的揭开Binder的神秘面纱。
Binder进程间到底是怎么通信的,如何在Android中实现 C/S Binder 通信。
什么是aidl?为什么要有aidl?aidl是怎样生成java代码的?
关键字
in
、out
、inout
是什么?binder 的 linkToDeath、messager、epoll等等。
参考资料: