面试题五:Binder详解 - Go语言中文社区

面试题五:Binder详解


目录

相关文章:

一、Linux内核的基础知识

1、进程隔离 / 虚拟地址空间

2、系统调用(用户空间和内核空间)

3、binder驱动连接进程

二、Binder通信机制介绍

1、为什么使用binder

2、binder通信模型:第一种解释

2.1、binder通信模型

2.2、Binder机制跨进程原理

3、binder通信模型:第二种解释


相关文章:

Binder学习指南(文章8:讲得清晰明白,就是根据这篇文章终于了解了binder机制

Android Bander设计与实现 - 设计篇(文章1:阅读量12w,211赞,全面)

一篇文章了解相见恨晚的 Android Binder 进程间通讯机制(文章2:阅读量5w,76赞,全面)

写给 Android 应用工程师的 Binder 原理剖析(文章7:由浅入深很详细,有代码实例

BAT大咖助力Android面试6-Binder面试详解(文章3:阅读量不多,详细,有代码实例)

Android面试题(二)——IPC机制(文章4:阅读量4568,全面)

android面试(6)-Binder机制(文章5:阅读量2533,)

如何在android面试中说清楚android中binder机制的实现过程(文章6:阅读量1217,很详细)

​​关于Binder,作为应用开发者你需要知道的全部

Android Bander设计与实现 - 设计篇

面试题:IPC(跨进程通信)

一、Linux内核的基础知识

1、进程隔离 / 虚拟地址空间

为了保证 安全性 & 独立性,一个进程不能直接操作或者访问另一个进程,即Android的进程是相互独立、隔离的。

2、系统调用(用户空间和内核空间)

  • 进程间,用户空间的数据不可共享,所以用户空间 = 不可共享空间
  • 进程间,内核空间的数据可共享,所以内核空间 = 可共享空间
  • 所有进程共用1个内核空间

进程内 用户空间 & 内核空间 进行交互 需通过 系统调用,主要通过函数: 
copy_from_user():将用户空间的数据拷贝到内核空间 
copy_to_user():将内核空间的数据拷贝到用户空间

3、binder驱动连接进程

 

二、Binder通信机制介绍

1、为什么使用binder

(1)Android使用的Linux内核拥有着非常多的跨进程通信机制

(2)性能

(3)安全

Android Binder是用来做进程通信的,Android的各个应用以及系统服务都运行在独立的进程中,它们的通信都依赖于Binder。

为什么选用Binder,在讨论这个问题之前,我们知道Android也是基于Linux内核,Linux现有的进程通信手段有以下几种:

  1. 管道:在创建时分配一个page大小的内存,缓存区大小比较有限;
  2. 消息队列:信息复制两次,额外的CPU消耗;不合适频繁或信息量大的通信;
  3. 共享内存:无须复制,共享缓冲区直接付附加到进程虚拟地址空间,速度快;但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决;
  4. 套接字:作为更通用的接口,传输效率低,主要用于不通机器或跨网络的通信;
  5. 信号量:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  6. 信号: 不适用于信息交换,更适用于进程中断控制,比如非法内存访问,杀死某个进程等;

既然有现有的IPC方式,为什么重新设计一套Binder机制呢。主要是出于以上三个方面的考量:

  • 高性能:从数据拷贝次数来看Binder只需要进行一次内存拷贝,而管道、消息队列、Socket都需要两次,共享内存不需要拷贝,Binder的性能仅次于共享内存。
  • 稳定性:上面说到共享内存的性能优于Binder,那为什么不适用共享内存呢,因为共享内存需要处理并发同步问题,控制负责,容易出现死锁和资源竞争,稳定性较差。而Binder基于C/S架构,客户端与服务端彼此独立,稳定性较好。
  • 安全性:我们知道Android为每个应用分配了UID,用来作为鉴别进程的重要标志,Android内部也依赖这个UID进行权限管理,包括6.0以前的固定权限和6.0以后的动态权限,传统IPC只能由用户在数据包里填入UID/PID,这个标记完全是在用户空间控制的,没有放在内核空间,因此有被恶意篡改的可能,因此Binder的安全性更高。

2、binder通信模型:第一种解释

2.1、binder通信模型

Binder学习指南(文章8:讲得清晰明白,就是根据这篇文章终于了解了binder机制)

对于跨进程通信的双方,我们姑且叫做Server进程(简称Server),Client进程(简称Client);由于进程隔离的存在,它们之间没办法通过简单的方式进行通信,那么Binder机制是如何进行的呢?

回想一下日常生活中我们通信的过程:假设A和B要进行通信,通信的媒介是打电话(A是Client,B是Server);A要给B打电话,必须知道B的号码,这个号码怎么获取呢?通信录.

这个通信录就是一张表;内容大致是:

1  B -> 12345676
2  C -> 12334354

先查阅通信录,拿到B的号码;才能进行通信;否则,怎么知道应该拨什么号码?回想一下古老的电话机,如果A要给B打电话,必须先连接通话中心,说明给我接通B的电话;这时候通话中心帮他呼叫B;连接建立,就完成了通信。

另外,光有电话和通信录是不可能完成通信的,没有基站支持;信息根本无法传达。

我们看到,一次电话通信的过程除了通信的双方还有两个隐藏角色:通信录和基站。Binder通信机制也是一样:两个运行在用户空间的进程要完成通信,必须借助内核的帮助,这个运行在内核里面的程序叫做Binder驱动,它的功能类似于基站;通信录呢,就是一个叫做ServiceManager的东西(简称SM)

OK,Binder的通信模型就是这么简单,如下图:

 整个通信步骤如下:

  1. SM建立(建立通信录);首先有一个进程向驱动提出申请为SM;驱动同意之后,SM进程负责管理Service(注意这里是Service而不是Server,因为如果通信过程反过来的话,那么原来的客户端Client也会成为服务端Server)不过这时候通信录还是空的,一个号码都没有。
  2. 各个Server向SM注册(完善通信录);每个Server端进程启动之后,向SM报告,我是zhangsan, 要找我请返回0x1234(这个地址没有实际意义,类比);其他Server进程依次如此;这样SM就建立了一张表,对应着各个Server的名字和地址;就好比B与A见面了,说存个我的号码吧,以后找我拨打10086;
  3. Client想要与Server通信,首先询问SM;请告诉我如何联系zhangsan,SM收到后给他一个号码0x1234;Client收到之后,开心滴用这个号码拨通了Server的电话,于是就开始通信了。

那么Binder驱动干什么去了呢?这里Client与SM的通信,以及Client与Server的通信,都会经过驱动,驱动在背后默默无闻,但是做着最重要的工作。驱动是整个通信过程的核心,因此完成跨进程通信的秘密全部隐藏在驱动里面;这个我们稍后讨论。

OK,上面就是整个Binder通信的基本模型;做了一个简单的类比,当然也有一些不恰当的地方,(比如通信录现实中每个人都有一个,但是SM整个系统只有一个;基站也有很多个,但是驱动只有一个);但是整体上就是这样的;我们看到其实整个通信模型非常简单。

2.2、Binder机制跨进程原理

上文给出了Binder的通信模型,指出了通信过程的四个角色: Client, Server, SM, driver; 但是我们仍然不清楚Client到底是如何与Server完成通信的

两个运行在用户空间的进程A和进程B如何完成通信呢?内核可以访问A和B的所有数据;所以,最简单的方式是通过内核做中转;假设进程A要给进程B发送数据,那么就先把A的数据copy到内核空间,然后把内核空间对应的数据copy到B就完成了;用户空间要操作内核空间,需要通过系统调用;刚好,这里就有两个系统调用:copy_from_usercopy_to_user

但是,Binder机制并不是这么干的。讲这么一段,是说明进程间通信并不是什么神秘的东西。那么,Binder机制是如何实现跨进程通信的呢?

Binder驱动为我们做了一切。

假设Client进程想要调用Server进程的object对象的一个方法add;对于这个跨进程通信过程,我们来看看Binder机制是如何做的。 (通信是一个广泛的概念,只要一个进程能调用另外一个进程里面某对象的方法,那么具体要完成什么通信内容就很容易了。)

首先,Server进程要向SM注册;告诉自己是谁,自己有什么能力;在这个场景就是Server告诉SM,它叫zhangsan,它有一个object对象,可以执行add 操作;于是SM建立了一张表:zhangsan这个名字对应进程Server;

然后Client向SM查询:我需要联系一个名字叫做zhangsan的进程里面的object对象;这时候关键来了:进程之间通信的数据都会经过运行在内核空间里面的驱动,驱动在数据流过的时候做了一点手脚,它并不会给Client进程返回一个真正的object对象,而是返回一个看起来跟object一模一样的代理对象objectProxy,这个objectProxy也有一个add方法,但是这个add方法没有Server进程里面object对象的add方法那个能力;objectProxyadd只是一个傀儡,它唯一做的事情就是把参数包装然后交给驱动。(这里我们简化了SM的流程,见下文)

但是Client进程并不知道驱动返回给它的对象动过手脚,毕竟伪装的太像了,如假包换。Client开开心心地拿着objectProxy对象然后调用add方法;我们说过,这个add什么也不做,直接把参数做一些包装然后直接转发给Binder驱动。

驱动收到这个消息,发现是这个objectProxy;一查表就明白了:我之前用objectProxy替换了object发送给Client了,它真正应该要访问的是object对象的add方法;于是Binder驱动通知Server进程,调用你的object对象的add方法,然后把结果发给我,Sever进程收到这个消息,照做之后将结果返回驱动,驱动然后把结果返回给Client进程;于是整个过程就完成了。

由于驱动返回的objectProxy与Server进程里面原始的object是如此相似,给人感觉好像是直接把Server进程里面的对象object传递到了Client进程;因此,我们可以说Binder对象是可以进行跨进程传递的对象

但事实上我们知道,Binder跨进程传输并不是真的把一个对象传输到了另外一个进程;传输过程好像是Binder跨进程穿越的时候,它在一个进程留下了一个真身,在另外一个进程幻化出一个影子(这个影子可以很多个);Client进程的操作其实是对于影子的操作,影子利用Binder驱动最终让真身完成操作。

理解这一点非常重要;务必仔细体会。另外,Android系统实现这种机制使用的是代理模式, 对于Binder的访问,如果是在同一个进程(不需要跨进程),那么直接返回原始的Binder实体;如果在不同进程,那么就给他一个代理对象(影子);我们在系统源码以及AIDL的生成代码里面可以看到很多这种实现。

另外我们为了简化整个流程,隐藏了SM这一部分驱动进行的操作;实际上,由于SM与Server通常不在一个进程,Server进程向SM注册的过程也是跨进程通信,驱动也会对这个过程进行暗箱操作:SM中存在的Server端的对象实际上也是代理对象,后面Client向SM查询的时候,驱动会给Client返回另外一个代理对象。Sever进程的本地对象仅有一个,其他进程所拥有的全部都是它的代理。

一句话总结就是:Client进程只不过是持有了Server端的代理;代理对象协助驱动完成了跨进程通信。

3、binder通信模型:第二种解释

写给 Android 应用工程师的 Binder 原理剖析

Binder是基于C/S架构的,对于通信双方来说,发起请求的进程属于Client,接收请求的进程属于Server,由于存在进程隔离,双方不能直接通信,Binder是如何实现的呢?

写给 Android 应用工程师的 Binder 原理剖析中举的网络通信例子很贴切,Binder的通信过程与网络请求类似,网络通信过程可以简化为4个角色:Client、Server、DNS服务器和路由器。一次完整的网络通信大体过程如下:

1、Client输入Server的域名

2、DNS解析域名:

通过域名是无法直接找到相应Server的,必须先通过DNS服务器将Server的域名转化为具体的IP地址。

3、通过路由器将请求发送至Server:

Client通过DNS服务器解析到Server的IP地址后,也还不能直接向Server发起请求,需要经过路由器的层层中转才还到达Server。

4、Server返回数据:

Server接收到请求并处理后,再通过路由器将数据返回给Client。

在Binder机制中,也定义了4个角色:Client、Server、Binder驱动和ServiceManager

  • Binder驱动:类似网络通信中的路由器,负责将Client的请求转发到具体的Server中执行,并将Server返回的数据传回给Client。
  • ServiceManager:类似网络通信中的DNS服务器,负责将Client请求的Binder描述符转化为具体的Server地址,以便Binder驱动能够转发给具体的Server。Server如需提供Binder服务,需要向ServiceManager注册。


 

具体的通信过程是这样的:

1、Server向ServiceManager注册:

Server通过Binder驱动向ServiceManager注册,声明可以对外提供服务。ServiceManager中会保留一份映射表:名字为zhangsan的Server对应的Binder引用是0x12345。/

2、Client向ServiceManager请求Server的Binder引用

Client想要请求Server的数据时,需要先通过Binder驱动向ServiceManager请求Server的Binder引用:我要向名字为zhangsan的Server通信,请告诉我Server的Binder引用。

3、向具体的Server发送请求

Client拿到这个Binder引用后,就可以通过Binder驱动和Server进行通信了。

4、Server返回结果

Server响应请求后,需要再次通过Binder驱动将结果返回给Client。

可以看到,Client、Server、ServiceManager之间的通信都是通过Binder驱动作为桥梁的,可见Binder驱动的重要性。也许你还有一点疑问,ServiceManager和Binder驱动属于两个不同的进程,它们是为Client和Server之间的进程间通信服务的,也就是说Client和Server之间的进程间通信依赖ServiceManager和Binder驱动之间的进程间通信,这就像是:“蛋生鸡,鸡生蛋,但第一个蛋得通过一只鸡孵出来”。Binder机制是如何创造第一只下蛋的鸡呢?

  1. 当Android系统启动后,会创建一个名称为servicemanager的进程,这个进程通过一个约定的命令BINDERSETCONTEXT_MGR向Binder驱动注册,申请成为为ServiceManager,Binder驱动会自动为ServiceManager创建一个Binder实体(第一只下蛋的鸡);
  2. 并且这个Binder实体的引用在所有的Client中都为0,也就说各个Client通过这个0号引用就可以和ServiceManager进行通信。Server通过0号引用向ServiceManager进行注册,Client通过0号引用就可以获取到要通信的Server的Binder引用。

Android Binder设计与实现 - 设计篇中对Client、Server、Binder驱动和ServiceManager有更详细的介绍。

 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/songzi1228/article/details/94569878
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-07 21:25:35
  • 阅读 ( 1443 )
  • 分类:面试题

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢