请介绍什么是Binder机制⭐⭐⭐⭐⭐
请介绍Binder机制流程 ⭐⭐⭐⭐
Binder机制需要多少次内存拷贝 ⭐⭐⭐
Android有很多跨进程通信方法,为何选择Binder?⭐⭐⭐
目录
1、Android中的跨进程方法(IPC)
1.1 Android中的跨进程方法
1.2 为何要使用Binder
2、传统的Linux跨进程通讯
3、Binder的通讯原理
4、Binder通信模型
4.1 服务端、客户端、ServiceManager、Binder驱动
4.1.1 Binder驱动
4.1.2 ServiceManager
4.1.3 服务端向ServiceManager注册
4.1.4 客户端获取服务端Binder引用
4.2 Binder通讯中的代理模式
4.3 好多Binder分不清?
4.4 Binder通讯流程
5、Binder源码分析(外部链接)
1、Android中的跨进程方法(IPC)
Binder是Android中的一种跨进程通信机制,采用服务端/客户端结构,主要包含服务端、客户端、ServiceManager、Binder驱动4大部分。我们先看看Android中都有哪些跨进程的方法。
1.1 Android中的跨进程方法
共享内存:通过共享缓冲区直接映射到各个进程的虚拟地址空间,速度快。但实现比较复杂,进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决;
消息队列:提供了一种从一个进程向另一个进程发送一个数据块的方法,但需要进行2次数据拷贝,不适合频繁或者数据量大的情况;
管道:分为有名/无名管道,在创建时分配一个page大小的内存,缓存区大小比较有限;
信号量:一般作为一种锁机制,用于进程/线程同步;
信号:更适合作为进程中断控制,而非数据交换;
Socket/LocalSocket:允许位于同一主机(计算机)或使用网络连接起来的不同主机上的应用程序之间交换数据;
Binder:Binder是Android中的一种跨进程通信机制;
匿名共享内存:在Android系统中,提供了独特的匿名共享内存子系统Ashmem(Anonymous Shared Memory),它利用了Linux的 tmpfs文件系统(一种可以基于RAM或是SWAP的高速文件系统)来实现不同进程间的内存共享。有两个特点:
能够辅助内存管理系统来有效地管理不再使用的内存块;
通过Binder进程间通信机制来实现进程间的内存共享;
File:通过文件进行多进程通信用法简单,但不适合高并发情况;
ContentProvider:Android四大组件之一,常用于进程间数据共享,特别是一对多的情况,不过受限于AIDL;
Bundle:很常见也很常用,但只能传输Bundle支持的数据类型,同样不适合大量数据的传输;
面试官很喜欢问Android中有哪些跨进程方法,如果你可以答出这么多,一定可以加分
1.2 为何要使用Binder
众所周知,Android系统基于Linux系统,因此Linux系统的IPC方法:共享内存、消息队列、管道、信号量,在Android系统都可以使用。1.1小节就列出了10+种方法,那么为何还需要Binder?
使用Binder的理由可以总结为性能、安全、稳定几个方面:
性能:管道、消息队列、Socket都需要2次数据拷贝(即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区),而Binder只要1次(可见第3节分析),且Binder相对Socket方法也更加高效,Socket主要用于跨网络的进程间通讯,传输的效率比较低。当然共享内存1次数据拷贝都不需要,因此就性能而言,共享内存高于Binder;
安全:Android系统为每个应用都分配各自的UID以鉴别不同进程。Binder会验证权限,鉴定UID/PID来验证身份,保证了进程通信的安全性;
稳定:共享内存1次数据拷贝都不需要,但实现的方法比较复杂,并且需要做好进程同步,容易出现死锁或者资源竞争,稳定性差。而Binder基于C/S架构,客户端和服务端彼此独立,稳定性强。
因此,在Android系统的IPC方式,一般情况下都推荐使用Binder。当然,在Android源码里,上述其他IPC方法也会使用到。接下来让我们先看看传统的Linux跨进程通讯原理。
2、传统的Linux跨进程通讯
Android是基于Linux的,以32位系统来说,在每一个Linux进程都有3G虚拟内存空间,称之为用户空间,以及1G的内核空间。其中,不同进程的用户空间是相互隔离不可直接通讯的,但是内核空间却是共享的。以进程A发送数据到进程B为例,需要经历如下步骤:
进程A的内核空间开辟一块内核缓存区,通过copy_from_user()系统调用,将在用户空间的数据拷贝到内核缓存区;
进程B的内核空间也开辟了一块内核缓存区,因为不同进程的内核空间共享的,此时进程B就可以通过copy_to_user()系统调用将内核缓存区的数据拷贝到自己的用户空间;
如此就完成了一次进程间通讯,上面说到的管道、消息队列都是基于这种方式,因此有两个缺点:
经历2次数据拷贝,效率较低;
进程B接收的内核缓存区无法确定开辟多大的空间合适,太大自然损耗空间,太小则可能出现拷贝不完整,因此要么开辟尽可能大的空间,要么先获取进程A发送的数据大小再开辟对应大小的空间。这两种做法要么浪费空间,要么浪费时间。
3、Binder的通讯原理
了解Binder通讯原理需要知道两个概念:
Binder驱动:Linux有一个叫“动态内核可加载模块”的机制,因此Android系统就可以通过动态添加这个模块,让该模块链接到内核作为内核的一部分,实现用户进程通过这个内核模块作为通讯桥梁。这个模块就是“Binder驱动”。
内存映射:内存映射即经典的mmap()方法,该方法可以将进程的用户空间的一块用户内存区域A映射到内核空间B,映射后,用户内存区域A的修改都会直接反应到内核空间B,反之亦然。
知道这两个概念后,以进程A发送数据到进程B为例,一次完整的Binder通讯的步骤如下:
Binder驱动链接到内核空间后,开辟出一个数据接收缓存区1;
进程A在内核空间开辟一块内核缓存区2,接着将数据接收缓存区1和进程A的内核缓存区2做内存映射,同时再将数据接收缓存区1和进程B的用户空间中的用户内存区域3也做内存映射。如此一来,进程A的内核缓存区2的修改也会同步反映到程B的用户空间中的用户内存区域3中;
进程A通过copy_from_user()系统调用,将在用户空间的数据拷贝到内核缓存区2中,此时这些数据就会同步反映到B的用户空间中的用户内存区域3中。
这样便完成了一次进程间通讯,并只有1次数据拷贝。
4、Binder通信模型
4.1 服务端、客户端、ServiceManager、Binder驱动
文章一开头就说过,Binder基于服务端/客户端架构,主要包括服务端、客户端、ServiceManager、Binder驱动这4个部分。以进程A与进程B通讯为例,进程B就是客户端,进程A为服务端。其中服务端、客户端、ServiceManager运行在用户空间,只有Binder驱动运行在内核空间。ServiceManager和Binder驱动是系统提供的,服务端和客户端则由应用程序来实现。
4.1.1 Binder驱动
Binder驱动在系统里对应/dev/binder文件,负责进程间Binder通讯的建立。是服务端、客户端、ServiceManager这3者之间的桥梁。Binder跨进程传递、Binder引用计数管理、数据包跨进程传递等一系列操作都需要Binder提供支持。
4.1.2 ServiceManager
ServiceManager作用很强大,在Binder通讯中,主要作用管理服务端和客户端。系统中会有一个进程使用BINDERSETCONTEXT_MGR 命令将自己注册成 ServiceManager。此时,Binder驱动会为该ServiceManager进程在内核空间创建对应的Binder实体,这个实体可以在其他的进程通过第0号Binder引用获得。那么,其他进程都可以通过第0号引用和ServiceManager的Binder通讯有什么作用呢?
4.1.3 服务端向ServiceManager注册
该案例中,进程B作为服务端,可以通过第0号引用获得ServiceManager的Binder引用,通过该Binder引用和ServiceManager通讯。进程B会先创建自己的Binder实体,并起个容易记得的名字,如“进程B”,将这个Binder实体和名字一起打包,通过第0号引用通知ServiceManager注册一个名为“进程B”的Binder。此时,会由Binder驱动为进程B创建位于内核空间的Binder实体,以及ServiceManager对该Binder实体的引用,ServiceManager可以通过“进程B”这个名字找到新创建的进程B的Binder实体。
有一个叫法问题需要统一下,在进程A和进程B通讯的案例中,进程B作为服务端。但是在进程B通过第0号引用向ServiceManager注册时,进程B其实是作为“客户端”,ServiceManager是“服务端”。也就是一个进程,比如进程B可能是提供服务的服务端,但至少对于ServiceManager这个进程来说,进程B仍然是一个“客户端“。
4.1.4 客户端获取服务端Binder引用
作为服务端的进程B已经向ServiceManager注册好,此时作为客户端的进程A也可以通过第0号引用,向ServiceManager请求访问名字叫做“进程B”的Binder引用。ServiceManager通过客户端发送过来的名字,查到对应的进程B的Binder引用回复给进程A。进程A就可以通过该Binder引用和进程B通讯了。
到此为止,Binder驱动为作为服务端的进程B创建了位于内核空间的Binder实体,同时ServiceManager拥有该Binder实体的引用,作为客户端的进程A也拥有该Binder实体的引用。如果接下来有进程C也作为客户端请求进程B的Binder,则最后进程C也会拥有进程B的Binder实体的引用。
4.2 Binder通讯中的代理模式
上面说到作为客户端的进程A通过Binder驱动获取到了作为服务端的进程B的Binder引用,这个Binder引用更准确来说应该称为BinderProxy,也就是进程A获取到的是进程B的Binder代理对象,通过该Binder代理对象,进程A可以调用进程B提供的方法。
当然,因为进程A和B毕竟存在进程边界。所以进程A获取到的进程B的Binder代理对象并非可以直接使用进程B提供的方法。进程A只是调用方法时,把请求参数打包成Parcel对象,通过Binder代理对象发给Binder驱动,Binder驱动看到这个Binder代理对象是进程B的Binder代理,就会通知进程B解包Parcel对象,使用传入的请求参数,调用对应的方法,最后把进程B执行完的结果返回给进程A。但对于进程A来说,就像是直接调用了进程B的方法。
4.3 好多Binder分不清?
在上面说的服务端、客户端、ServiceManager、Binder驱动这4个部分,他们的代表都是“Binder”,Binder一词出现多次,可能有部分同学觉得有点乱,现在统一理一遍:
通常意义:通常我们说“Binder”,就是指Android里面的一种跨进程通讯机制;
服务端进程:对服务端进程来说,“Binder”指服务端进程创建的自己的Binder本地对象;
客户单进程:对客户端进程来说,“Binder”指客户端进程从ServiceManager拿到的服务端进程里的Binder本地对象,在客户端进程的一个Binder代理对象;
传输过程:在跨进程传输过程中,“Binder”是可以进行跨进程传递的对象;Binder驱动会对具有跨进程传递能力的对象做特殊处理,会自动完成代理对象和本地对象的转换。
4.4 Binder通讯流程
综上,服务端、客户端、ServiceManager、Binder驱动之间的通讯过程大致如下:
系统会有一个进程使用BINDERSETCONTEXT_MGR 命令将自己注册成 ServiceManager;
欲提供服务的进程作为服务端进程,先创建自己的Binder实体,再向ServiceManager注册该Binder实体,表明可以对外提供服务。Binder驱动会为服务端进程的Binder创建位于内核空间的Binder实体,同时,ServiceManager会拥有该内核空间的Binder实体的引用;
客户端通过名字在Binder驱动的帮助下,从ServiceManager获取到服务端内核空间的Binder实体的引用,即服务端的Binder代理,通过这个代理就可以实现和服务端的通讯。
如下图所示:
看一个跨进程调用系统服务的demo:
//获取WindowManager服务引用
WindowManager wm = (WindowManager) getSystemService(getApplication().WINDOW_SERVICE); //1
//布局参数layoutParams相关设置略...
View view = LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null);
//添加view
wm.addView(view, layoutParams);
系统开机流程中,会提前注册一系列系统服务,如WindowManager,一个用于管理窗口的系统服务。我们在[注释1]通过传入名字,从ServiceManager获取到WindowManager这个服务的本地代理,也就是wm变量,通过wm代理就可以远程调用WindowManager服务进程里提供的addView()方法。
为进一步了解Binder,请继续观看下一节AIDL分析。
5、Binder源码分析(外部链接)
本文不涉及源码分析,因为笔者也是还在学习源码中,不过推荐网上找到的一个比较好的系列: