概念
什么是Binder
如果我得同事问了我一句,“Binder是什么啊”,我得脑海中可能出现这样几个答案
- Android系统中进程间的通信机制(IPC)
- 也是一个Android系统中处于Linux内核层的驱动设备
- 在Android开发中,如果继承了
Binder.java
类,实现IBinder
接口,可以获得跨进程通信的能力
Android中的多进程
那么Android系统中什么时候会用到多进程呢。我们在平时Android开发中,会创建出一些独立进程,如:WebView、大图浏览、音视频播放、推送等相关功能,同时Android系统服务中的打电话,闹钟等功能也是多进程的实现。如果你写的android应用实现了多进程,那么你的应用可以开辟更多的内存(每一个进程可以向虚拟机申请到的使用内存是有限的,多个进程可以获得更多的内存),可以获得风险隔离(把一些风险操作统一放在一个进程中,即使这个进程崩溃了,也不会影响到主进程)的特性,这些都是多进程的优势。
Linux中的进程间通信
Linux中已有的进程间通信手段包括
管道(Pipe)及有名管道(named pipe)
:在创建时分配1个page大小的内存空间,缓存区大小比较有限信号(Signal)和跟踪(Trace)
:不适合用于信息交换,更适用于进程中断控制,如非法内存访问、杀死某个进程等共享内存(Share Memory)
:无需信息拷贝,共享缓冲区直接附加到进程虚拟地址空间,速度快;但进程间的同步问题操作系统无法实现,必须靠各进程利用同步工具解决套接字(Socket)
:作为更通用的接口,传输效率低,主要作用于本机上不同进程间的低速通信或者跨网络的进程间通信报文(Message)队列(消息队列)
:信息拷贝两次,额外的CPU消耗;不适合频繁或者信息量大的通信信号量(Semaphore)
:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段
既然Android是基于Linux内核的操作系统,那为什么google不直接采用以上几种进程的IPC方案却选择了Binder呢?我们来从以下几个角度对Binder和其他几个IPC方案来进行对比分析
-
从性能的角度:
Socket
作为一款通用接口,传输效率低、开销大,主要用于本机上不同进程间的低速通信或者跨网络的进程间通信。消息队列和管道采用存储-转发方式,即数据先从发送方的缓存区到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方的缓存区,至少经历了两次拷贝过程
;Binder只需要一次拷贝
;共享内存无需拷贝
。Binder在性能上仅次于共享内存 -
从稳定性的角度:Binder是基于
C/S架构
的(客户端Client
和服务端Server
组成的架构,客户端有什么需求,直接发给服务端去完成),架构清晰,客户端和服务端相对独立,稳定性较好。Socket
同样支持C/S架构
。而管道/消息队列/共享内存/信号量
都不支持,虽然可以在这些底层机制上架设一套协议来实现Client-Server通信,但这样增加了系统的复杂性,在手机这种条件复杂,资源稀缺的环境下可靠性也难以保证 -
从安全性角度:传统的Linux IPC的接收方无法获得对方进程可靠的
UID/PID
,从而无法鉴别对方身份;而Android作为一个开放的开源体系,拥有非常多的开发平台,APP来源广,因此手机的安全显得至关重要。传统的Linux IPC无任何保护措施,完全由上层协议来确保,只能由用户在数据包里填入UID/PID
,可靠的身份标识只有由IPC机制本身在内核中添加,而且传统的IPC访问的接入点是开放的,无法建立私有通道。Android为每个安装好的应用程序分配了自己的UID
,因而进程的UID
是鉴别进程身份的重要标识,在C/S架构
中,Android系统中对外只暴露客户端,客户端将任务发送给服务端,服务端会根据权限控制策略,判断UID/PID
是否满足访问权限,目前权限控制很多时候是通过弹出权限申请弹框,让用户选择是否允许。同时 Binder既支持实名 Binder
(比如系统服务),又支持匿名 Binder
(自己创建的服务),Binder的安全性更高Binder 共享内存 Socket 性能 需要拷贝一次 无需拷贝 需要拷贝两次 稳定性 基于C/S架构,易用性高 ,稳定性好 控制复杂,易用性差 ,稳定性差 基于C/S架构,作为一款通用接口,传输效率低,开销大 ,稳定性好 安全性 为每个APP分配UID,同时支持实名和匿名,安全性高 依赖上层协议,访问接入点是开放的,不安全 依赖上层协议,访问接入点是开放的,不安全 基于上述原因,可知Binder是Android系统上层进程间通信的不二之选
结构
进程间是如何通信的
两个进程A和B,进程A是无法之间操作或调用进程B中的代码的,因为Android系统中不同进程间是内存隔离的。内存被操作系统划分为两块,用户空间和内核空间。用户空间是用户程序代码运行的地方,内核空间是内核代码运行的地方。为了安全,他们是互相隔离的,即用户的程序崩溃了,也不会影响到内核。
CPU具有不同的操作模式,代表不同的级别,不同的级别具有不同的功能,在较低的级别中将禁止某些操作。Linux系统设计时利用了这种硬件特性,使用了两个级别,最高级别和最低级别,内核运行在最高级别(
内核态R0
),这个级别可以进行所有操作,而应用程序运行在较低级别(用户态R3
),在这个级别,处理器控制着对硬件的直接访问以及对内存的非授权访问。
当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态
)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。
当进程在执行用户自己的代码的时候,我们称其处于用户运行态(用户态
)。此时处理器在特权级最低的(3级)用户代码中运行。
内核态和用户态有自己的内存映射,即自己的地址空间。不同进程的用户空间申请的虚拟内存,映射到独属进程的物理内存上面,是隔离的。内核空间申请的虚拟内存映射到同一块物理内存上,是共享的。用户空间的应用程序,如果想要请求系统服务,比如操作一个物理设备,或者映射一段设备空间的地址到用户空间,就必须通过系统调用来(操作系统提供给用户空间的接口函数)实现,而系统调用主要通过如下两个函数
copy_from_user() //将数据从用户空间拷贝到内核空间
copy_to_user() //将数据从内核空间拷贝到用户空间
传统IPC机制传输数据
传统的IPC机制传输数据相当于传呼机,进程A想给进程B发消息,需要先把消息告诉传呼中心,然后传呼中心再把信息发送到进程B的传呼机上面,这条信息发送了两次,也就是所谓的两次拷贝(只是举例,方便理解)。
Binder机制传输数据
Binder机制传输数据相当于寄快递,进程A想把包裹邮寄给进程B,需要先把包裹交给快递公司,然后快递公司把包裹放到旗下的快递柜里面,进程B直接在快递柜里面取出包裹。这个包裹就相当于数据,只是在进程A交给快递公司时发生了一次拷贝(只是举例,方便理解)。在这个过程中,快递柜这部分空间是快递公司和进程B共享的。
想了解Binder机制传输数据的原理,首先要先了解一个概念:内存映射
Linux通过将一个虚拟内存区域与一个磁盘(物理内存)上的对象关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射(memory mapping
)
MMAP
的原理是这样的:
1.线程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域。
先在用户空间调用库函数mmap,并在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续虚拟地址作为内存虚拟映射区域,对此区域初始化并插入进程的虚拟地址区域链表或树中。
2.系统在内核空间调用内核函数mmap
,实现文件物理地址和进程虚拟地址之间的一一映射关系。
3.进程发起对这片映射空间的访问
进程读写操作访问虚拟地址,查询页表,发现这一段地址并不在内存的物理页面上,因为虽然建立了映射关系,但是还没有将文件从磁盘移到内存中。由此发生缺页中断,内核请求从磁盘调入页面。调页过程先在交换缓存空间(swap cache
)中查找,若没有则通过nopage
函数把缺失页从磁盘调入内存。之后进程会对其做读写操作,若写操作改变了页面内容,一段时间后系统会自动回写脏页面到磁盘中。(修改过的脏页面不会立即更新到文件中,可以调用msync()
来强制同步,写入文件)
Binder IPC 机制中涉及到的内存映射通过mmap()
来实现,mmap()
是操作系统中一种内存映射的方法。映射关系建立后,用户对这块用户空间的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。这样一来,从发送方用户空间拷贝到内核空间缓存区的数据,就相当于直接拷贝到了接收方用户空间的数据缓存区,从而减少了一次数据拷贝。
Binder框架结构
1.Binder驱动
Linux 把所有的硬件访问都抽象为对文件的读写、设置,这一"抽象"的具体实现就是驱动程序。驱动程序充当硬件和软件之间的枢纽,提供了一套标准化的调用,并将这些调用映射为实际硬件设备相关的操作,对应用程序来说隐藏了设备工作的细节。
Linux 驱动设备分为三类,分别是字符设备、块设备和网络设备。字符设备就是能够像字节流文件一样被访问的设备。对字符设备进行读/写操作时,实际硬件的I/O
操作一般也紧接着发生。字符设备驱动程序通常都会实现 open
、close
、read
和 write
系统调用,比如显示屏、键盘、串口、LCD、LED 等。
块设备指通过传输数据块(一般为512b
或1k
)来访问的设备,比如硬盘、SD卡、U盘、光盘等。
网络设备是能够和其他主机交换数据的设备,比如网卡、蓝牙等设备。
字符设备中有一个比较特殊的 misc 杂项设备
,设备号为 10,可以自动生成设备节点。Android 的Ashmem
、Binder
都属于 misc 杂项设备
。
Binder 并不是 Linux 系统内核的一部分,得益于 Linux 的动态内核可加载模块(Loadable Kernel Module,LKM
)的机制;模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信
对 Binder 机制来说,Binder驱动
是 IPC 通信的路由器,负责实现不同进程间的数据交互,是 Binder 机制的核心;对 Linux 系统来说,Binder驱动
是一个字符驱动设备,运行在内核空间,向上层提供 /dev/binder
设备节点及 open
、mmap
、ioctl
等系统调用
2.ServiceManager
Service Manager
充当的是Binder驱动的守护进程,类似于TCP通信中的DNS地位。我们会把相关的Binder注册到里面,最后会通过Service Manager
这个服务去查找Binder的代理端。而实际上这个Service Manager
在Andrioid
Binder体系中,承当了Android系统中第一个注册进入Binder的服务
3.Clinet
本地端,Binder在整个IPC进程通信中,发出请求的一端。相当于C/S架构
中的客户端
的概念
4.Server
代理端,Binder在整个IPC进程通信中,响应请求的一端。相当于C/S架构
中的服务端
的概念