先上一张Binder 的工作流程图。(如果不清晰,可以复制图片链接到浏览器或
保存到本地查看,我经常都是这样看图的哈)
一开始上手,陌生的东西比较多,But,其实并不复杂。喔,流程图是用ProcessOn
画的。很棒的在线画图工具。
出发前预备子弹我们知道进程之间,虚拟地址不同,是不能直接通信的,这是
一种保护机制。打开任务管理器,查看一下N 多的进程,试想一下如果这些进
程直接通信会带来什么后果?
而用户空间可以通过System calls(系统回调)与内核空间通信的,如果在内核
空间中有一个模块,能够完成数据的转发,那么是不是两个进程就可以通信了
呢?如下图:
上面提到一些用户空间、内核空间的概念,用户空间也能大概猜到是什么东西,
而内核空间,就知道它是很底层的东西好了。而模块呢,可以简单的理解为实现
一个功能的程序或一个硬件电路等,比如玩单片机的时候,会有红外线模块,蓝
牙模块,wifi 模块等。这些概念的东西搜索一下百科知道就好。
Binder 驱动
Binder 驱动运行在内核空间,它就是那个内核模块了。Binder 驱动很重要,承
担了进程间通信的数据转发等。一提到驱动,也是比较熟悉,你插个U 盘,需
要驱动吧。而Binder 驱动也差不多,虽然名字取得很好,功能还很强大。但也
不是什么神奇的东西。
Binder 跨进程通信模型
Binder 的通信模型有4 个角色:Binder Client、Binder Server、Binder Driver
(Binder 驱动)、ServiceManager。
想象一个情景:我到北京旅行,要给高中同学寄一张明信片,明信片肯定要写上
地址吧,不然怎么寄给对方呢?那么我怎么拿到这个地址呢,很简单,翻一下毕
业相册就好了。而这个记录着同学们通信地址的毕业相册,就相当与一个通讯录。
在Binder 的通信模型中扮演的是ServiceManager 的角色。好,现在已经有了通
信地址了,那么就找到邮局寄出去就好了。过几天同学就高高兴兴的收到了明信
片。那么这个邮局在Binder 通信模型中扮演的是Binder 驱动的角色,而作为寄
信人的我就是Binder Client,收信人同学就是Binder Server。
先上一张图来描述上面的那个情景:
可以看到,ServiceManager、Binder Client、Binder Server 处于不同的进程,他
们三个都在用户空间,而Binder 驱动在内核空间。(我是特意把Binder 驱动画
的比较大的,因为Binder 驱动的作用最大)
那先来简述一下这个通信模型:
首先是有一个ServiceManager,刚开始这个通讯录是空白的,然后Server 进程
向ServiceManager 注册一个映射关系表,比如徐同学把自己的地址广东省广州
市xx 区写进通讯录,那么就形成了一张表:
之后Client 进程想要和Server 进程通信,首先向ServiceManager 查询地址,
ServiceManager 收到查询的请求之后,返回查询结果给Client。
注意到这里不管是Server 进程注册,还是Client 查询,都是经过Binder 驱动的,
这也真是Binder 驱动的作用所在,先不急,下面的原理会分析到。
这时候我就拿着地址就开始寄明信片咯。当我把明信片放扔进邮筒,之后的工作
就是由邮局去完成了,也就是Binder 驱动去完成通信的转发。
Binder 通信原理
从寄明信片的例子中,邮递员从邮筒取出明信片,然后跨越千山万水将明信片送
达。从这点我们也能想到,其实Binder 驱动完成的工作是很重要的。
我们来还原一个Binder 跨进程通信的过程。案例:Client 进程调用Server 进程
的computer 对象的add 方法。
接下来的内容你可能需要知道代理模式才能更好的理解,不过没学习过代理模式
也没关系,可以先读下去,然后在去补一下代理模式,再回来看这篇文章。思路
会清晰很多。
1. Server 进程向ServiceManager 注册,告诉ServiceManager
我是谁,我有什么,我能做什么。就好比徐同学(Server 进程)有
一台笔记本(computer 对象),这台笔记本有个add 方法。这时
映射关系表就生成了。
2. Client 进程向ServiceManager 查询,我要调用Server 进程的
computer 对象的add 方法,可以看到这个过程经过Binder 驱动,
这时候Binder 驱动就开始发挥他的作用了。当向ServiceManager
查询完毕,是返回一个computer 对象给Client 进程吗?其实不然,
Binder 驱动将computer 对象转换成了computerProxy 对象,并转
发给了Client 进程,因此,Client 进程拿到的并不是真实的
computer 对象,而是一个代理对象,即computerProxy 对象。很
容易理解这个computerProxy 对象也是有add 方法,(如果连add
方法都没有,岂不是欺骗了Client?),但是这个add 方法只是对
参数进行一些包装而已。
3. 当Client 进程调用add 方法,这个消息发送给Binder 驱动,
这时驱动发现,原来是computerProxy,那么Client 进程应该是需
要调用computer 对象的add 方法的,这时驱动通知Server 进程,
调用你的computer 对象的add 方法,将结果给我。然后Server
进程就将计算结果发送给驱动,驱动再转发给Client 进程,这时
Client 进程还蒙在了鼓里,他以为自己调用的是真实的computer
对象的add 方法,其实他只是调用了代理而已。不过Client 最终
还是拿到了计算结果。
好了,一个通信过程就完成了。我们发现,其实Binder 驱动就是一个中转。
总结
再来梳理总结一下:当Client 进程向ServiceManager 查询Server 进程(我要调
用你的某个对象的某个方法了),这个过程也是一个跨进程通信的过程,也经过
了Binder 驱动,这时Binder 驱动发挥它的作用,来了个狸猫换太子,将Server
进程中的真实对象转换成代理对象,返回这个代理对象给Client 进程。Client
进程拿到了这个代理对象,然后调用这个代理对象的方法,Binder 驱动继续发挥
他的使命,它会通知Server 进程执行计算工作,将Server 进程中的真实对象执行的结果返回给了Client 进程,这样Client 进程还是如愿的得到了自己想要。跨
进程通信完毕
Linux 自带多种进程通信方式, 为什么Android 都没采用二偏偏使用Binder 通信
面试官: Linux 自带多种进程通信方式,为什么Android 都没采用
而偏偏使用Binder 通信
心理分析:面试官绝对不会现场让你分析binder 的源码,源码又
深又广,没有面试官会傻到让你分析binder 所有的机制。他会问
你为什么不采用linux。从这个地方会看出你对binder 有多深。
这道题往往是面试Android 高级的必考题。所以小伙伴们需要格外
的注意
求职者:应该从linux 自带的进程通信说起。然后各个击破指
出在Android 这种特殊系统上的不足。最后引入binder 的优势
Linux 现有的所有进程间IPC 方式:
1.管道:
什么是管道:
管道可用于具有亲缘关系进程间的通信,管道是由内核管理的一个缓冲
区,
相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这
个进程会向管道中放入信息。
管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一
个缓冲区不需要很大,它被设计成为环形的数据结构,
以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程
会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放
入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的
时候,管道也自动消失。
缺点: 在创建时分配一个管道时,缓存区大小比较有限;并不适合Android
大量的进程通信
2.消息队列:
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方
法。每个数据块都被认为含有一个类型,接收进程可以独立地接收含有
不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻
塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的
限制。
缺点: 信息复制两次,额外的CPU 消耗;不合适频繁或信息量大的通信;
3.共享内存:
什么是共享内存:
顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存。
共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效
的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以
将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共
享内存中的地址无须复制,共享缓冲区直接付附加到进程虚拟地址空间,速度快;
缺点: 通信需要设计复杂的机制保证各个进程通讯有效性。进程间的同步
问题操作系统无法实现,必须各进程利用同步工具解决; 安全问题比较
突出,如果Android 采用Binder 无异于将每个App 放在一个内存中,这
样是非常不安全的
4.套接字:作为更通用的接口,传输效率低,主要用于不通机器或跨网络的通信;
5.信号量:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访
问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
6.信号: 不适用于信息交换,更适用于进程中断控制,比如非法内存访问,杀死
某个进程等;
接下来正面回答这个问题,从5 个角度来展开对Binder 的分析:
(1) 从性能的角度数据拷贝次数:Binder 数据拷贝只需要一次,而管道、消息
队列、Socket 都需要2 次,但共享内存方式一次内存拷贝都不需要;从性能角
度看,Binder 性能仅次于共享内存。
(2) 从稳定性的角度Binder 是基于C/S 架构的,简单解释下C/S 架构,是指客
户端(Client)和服务端(Server)组成的架构,Client 端有什么需求,直接发送给
Server 端去完成,架构清晰明朗,Server 端与Client 端相对独立,稳定性较好;
而共享内存实现方式复杂,没有客户与服务端之别, 需要充分考虑到访问临界
资源的并发同步问题,否则可能会出现死锁等问题;从这稳定性角度看,Binder
架构优越于共享内存。
仅仅从以上两点,各有优劣,还不足以支撑google 去采用binder 的IPC 机制,
那么更重要的原因是:
(3) 从安全的角度传统Linux IPC 的接收方无法获得对方进程可靠的UID/PID,
从而无法鉴别对方身份;而Android 作为一个开放的开源体系,拥有非常多的开
发平台,App 来源甚广,因此手机的安全显得额外重要;对于普通用户,绝不希
望从App 商店下载偷窥隐射数据、后台造成手机耗电等等问题,传统Linux IPC
无任何保护措施,完全由上层协议来确保。
Android 为每个安装好的应用程序分配了自己的UID,故进程的UID 是鉴别进程
身份的重要标志,前面提到C/S 架构,Android 系统中对外只暴露Client 端,
Client 端将任务发送给Server 端,Server 端会根据权限控制策略,判断UID/PID
是否满足访问权限,目前权限控制很多时候是通过弹出权限询问对话框,让用
户选择是否运行。Android 6.0,也称为Android M,在6.0 之前的系统是在App
第一次安装时,会将整个App 所涉及的所有权限一次询问,只要留意看会发现
很多App 根本用不上通信录和短信,但在这一次性权限权限时会包含进去,让
用户拒绝不得,因为拒绝后App 无法正常使用,而一旦授权后,应用便可以胡
作非为。
针对这个问题,google 在Android M 做了调整,不再是安装时一并询问所有权
限,而是在App 运行过程中,需要哪个权限再弹框询问用户是否给相应的权限,
对权限做了更细地控制,让用户有了更多的可控性,但同时也带来了另一个用
户诟病的地方,那也就是权限询问的弹框的次数大幅度增多。对于Android M
平台上,有些App 开发者可能会写出让手机异常频繁弹框的App,企图直到用
户授权为止,这对用户来说是不能忍的,用户最后吐槽的可不光是App,还有
Android 系统以及手机厂商,有些用户可能就跳果粉了,这还需要广大Android
开发者以及手机厂商共同努力,共同打造安全与体验俱佳的Android 手机。
传统IPC 只能由用户在数据包里填入UID/PID;另外,可靠的身份标记只有由IPC
机制本身在内核中添加。其次传统IPC 访问接入点是开放的,无法建立私有通道。
从安全角度,Binder 的安全性更高。
说到这,可能有人要反驳,Android 就算用了Binder 架构,而现如今Android
手机的各种流氓软件,不就是干着这种偷窥隐射,后台偷偷跑流量的事吗?没错,
确实存在,但这不能说Binder 的安全性不好,因为Android 系统仍然是掌握主
控权,可以控制这类App 的流氓行为,只是对于该采用何种策略来控制,在这
方面android 的确存在很多有待进步的空间,这也是google 以及各大手机厂商
一直努力改善的地方之一。在Android 6.0,google 对于app 的权限问题作为较
多的努力,大大收紧的应用权限;另外,在Google 举办的Android Bootcamp
2016 大会中,google 也表示在Android 7.0 (也叫Android N)的权限隐私方
面会进一步加强加固,比如SELinux,Memory safe language(还在research 中)
等等,在今年的5 月18 日至5 月20 日,google 将推出Android N。
话题扯远了,继续说Binder。
(4)从语言层面的角度大家多知道Linux 是基于C 语言(面向过程的语言),而
Android 是基于Java 语言(面向对象的语句),而对于Binder 恰恰也符合面向对
象的思想,将进程间通信转化为通过对某个Binder 对象的引用调用该对象的方
法,而其独特之处在于Binder 对象是一个可以跨进程引用的对象,它的实体位
于一个进程中,而它的引用却遍布于系统的各个进程之中。可以从一个进程传给
其它进程,让大家都能访问同一Server,就像将一个对象或引用赋值给另一个引
用一样。Binder 模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于
同一个面向对象的程序之中。从语言层面,Binder 更适合基于面向对象语言的
Android 系统,对于Linux 系统可能会有点“水土不服”。
另外,Binder 是为Android 这类系统而生,而并非Linux 社区没有想到Binder
IPC 机制的存在,对于Linux 社区的广大开发人员,我还是表示深深佩服,让世
界有了如此精湛而美妙的开源系统。也并非Linux 现有的IPC 机制不够好,相
反地,经过这么多优秀工程师的不断打磨,依然非常优秀,每种Linux 的IPC
机制都有存在的价值,同时在Android 系统中也依然采用了大量Linux 现有的
IPC 机制,根据每类IPC 的原理特性,因时制宜,不同场景特性往往会采用其下
最适宜的。比如在Android OS 中的Zygote 进程的IPC 采用的是Socket(套
接字)机制,Android 中的Kill Process 采用的 signa(l 信号)机制等等。而 Binder
更多则用在system_server 进程与上层App 层的IPC 交互。
(5) 从公司战略的角度
总所周知,Linux 内核是开源的系统,所开放源代码许可协议GPL 保护,该协议
具有“病毒式感染”的能力,怎么理解这句话呢?受GPL 保护的Linux Kernel 是运
行在内核空间,对于上层的任何类库、服务、应用等运行在用户空间,一旦进行
SysCall(系统调用),调用到底层Kernel,那么也必须遵循GPL 协议。
而Android 之父Andy Rubin 对于GPL 显然是不能接受的,为此,Google 巧妙
地将GPL 协议控制在内核空间,将用户空间的协议采用Apache-2.0 协议(允许
基于Android 的开发商不向社区反馈源码),同时在GPL 协议与Apache-2.0 之
间的Lib 库中采用BSD 证授权方法,有效隔断了GPL 的传染性,仍有较大争议,
但至少目前缓解Android,让GPL 止步于内核空间,这是Google 在GPL Linux
下开源与商业化共存的一个成功典范。
有了这些铺垫,我们再说说Binder 的今世前缘
Binder 是基于开源的OpenBinder 实现的,OpenBinder 是一个开源的系统IPC
机制,最初是由Be Inc.开发,接着由Palm, Inc.公司负责开发,现在OpenBinder
的作者在Google 工作,既然作者在Google 公司,在用户空间采用Binder 作为
核心的IPC 机制,再用Apache-2.0 协议保护,自然而然是没什么问题,减少法
律风险,以及对开发成本也大有裨益的,那么从公司战略角度,Binder 也是不错
的选择。
另外,再说一点关于OpenBinder,在2015 年OpenBinder 以及合入到Linux
Kernel 主线3.19 版本,这也算是Google 对Linux 的一点回馈吧。
综合上述5 点,可知Binder 是Android 系统上层进程间通信的不二选择。