阿里面试必问:带你读懂Binder通信机制;拿捏面试官
Posted 初一十五啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了阿里面试必问:带你读懂Binder通信机制;拿捏面试官相关的知识,希望对你有一定的参考价值。
本文设计内容:
结合视频掌握更快
1.准备
2.为什么会有Binder通信机制
3.面试系列
4.为什么会有内核空间与用户空间
5.内存管理单元
6.Binder原理
之前有个朋友小明1.2.3号去阿里面试的时候被问道一些关于Binder相关的内容,由于本文重点讲解Binder的知识点,所有把小明被问到的技术点拿出来分析,同时打算去阿里面试的小伙伴也要注意这些技术点,不然被问到打不出来面试过不了就得不偿失了。
先看看小明是怎么回答的:
面试官:
谈谈你对 binder 的理解?
小明1号(自认为无所不知,水平已达应用开发天花板,目前月薪 10k):binder 是用来跨进程通信的,可以分为 client、server、binder 驱动以及 service manager 四部分。然后被问及到一次拷贝的原理的同时小明小明一号蒙了,被告知回家等通知。
小明二号(业余时间经常打游戏、追剧、熬夜,目前月薪 15k):
binder 是一种 IPC 方式,相比于 Linux 原有的管道、共享内存、Socket 等,它通过 mmap 实现一次拷贝,比 Socket 、管道传输速度更快,比共享内存更安全可控,是 android 系统中主要的 IPC 通信方式。
面试官:Intent 传参有大小限制,这跟 binder 有关系吗?,如何限制大小?二号回答不了解没有深入看过相关源码,也被告知回家等消息。
小明三号(坚持每天学习、不断的提升自己,目前月薪 30k):
binder 是 Android 中主要的跨进程通信方式,binder 驱动和 service manager
分别相当于网络协议中的路由器和 DNS,并基于 mmap 实现了 IPC 传输数据时只需一次拷贝。
binder 包括 BinderProxy、BpBinder
等各种 Binder 实体,以及对 binder 驱动操作的 ProcessState、IPCThreadState
封装,再加上 binder 驱动内部的结构体、命令处理,整体贯穿 Java、Native 层,涉及用户态、内核态,往上可以说到 Service、AIDL 等,往下可以说到 mmap、binder 驱动设备,是相当庞大、繁琐的一个机制。
我自己来谈的话,一天时间都不够,还是问我具体的问题吧。
面试官:基于 mmap 又是如何实现一次拷贝的?
Client 与 Server 处于不同进程有着不同的虚拟地址规则,所以无法直接通信。而一个页框可以映射给多个页,那么就可以将一块物理内存分别与 Client 和 Server 的虚拟内存块进行映射。
如图, Client 就只需 copy_from_user 进行一次数据拷贝,Server 进程就能读取到数据了。另外映射的虚拟内存块大小将近 1M (1M-8K),所以 IPC 通信传输的数据量也被限制为此值。
面试官继续问怎么理解页框和页?
页框是指一块实际的物理内存,页是指程序的一块内存数据单元。内存数据一定是存储在实际的物理内存上,即页必然对应于一个页框,页数据实际是存储在页框上的。
页框和页一样大,都是内核对内存的分块单位。一个页框可以映射给多个页,也就是说一块实际的物理存储空间可以映射给多个进程的多个虚拟内存空间,这也是 mmap 机制依赖的基础规则。
在简单说下 binder 的整体架构吧
再来画一个简单的示意图吧,这是一个比较典型的、两个应用之间的 IPC 通信流程图:
Client 通过 ServiceManager
或 AMS 获取到的远程 binder 实体,一般会用 Proxy 做一层封装,比如 ServiceManagerProxy
、 AIDL
生成的 Proxy 类。而被封装的远程 binder 实体是一个 BinderProxy
。
BpBinder
和 BinderProxy
其实是一个东西:远程 binder 实体,只不过一个 Native 层、一个 Java 层,BpBinder 内部持有了一个 binder 句柄值 handle。
ProcessState
是进程单例,负责打开 Binder 驱动设备及 mmap;IPCThreadState
为线程单例,负责与 binder 驱动进行具体的命令通信。
由 Proxy 发起 transact()
调用,会将数据打包到 Parcel 中,层层向下调用到 BpBinder ,在 BpBinder 中调用 IPCThreadState
的 transact()
方法并传入 handle 句柄值,IPCThreadState
再去执行具体的 binder 命令。
由 binder 驱动到 Server 的大概流程就是:Server 通过 IPCThreadState 接收到 Client 的请求后,层层向上,最后回调到 Stub 的 onTransact() 方法。
当然这不代表所有的 IPC 流程,比如 Service Manager
作为一个 Server 时,便没有上层的封装,也没有借助 IPCThreadState
,而是初始化后通过 binder_loop()
方法直接与 binder 驱动通信的。
好的,明天来报道
一丶准备
从上之下, 整个Binder架构所涉及的总共有以下5个目录:
/framework/base/core/java/ (Java)
/framework/base/core/jni/ (JNI)
/framework/native/libs/binder (Native)
/framework/native/cmds/servicemanager/ (Native)
/kernel/drivers/staging/android (Driver)
1.1 Java framework
/framework/base/core/java/android/os/
- IInterface.java
- IBinder.java
- Parcel.java
- IServiceManager.java
- ServiceManager.java
- ServiceManagerNative.java
- Binder.java
/framework/base/core/jni/
- android_os_Parcel.cpp
- AndroidRuntime.cpp
- android_util_Binder.cpp (核心类)
1.2 Native framework
/framework/native/libs/binder
- IServiceManager.cpp
- BpBinder.cpp
- Binder.cpp
- IPCThreadState.cpp (核心类)
- ProcessState.cpp (核心类)
/framework/native/include/binder/
- IServiceManager.h
- IInterface.h
/framework/native/cmds/servicemanager/
- service_manager.c
- binder.c
1.3 Kernel
/kernel/drivers/staging/android/
- binder.c
- uapi/binder.h
二丶为什么会有Binder通信机制
为什么会有Binder通信机制,为什么不能用linux中已有的进程框架呢?Google工程师究竟是如何考量的。
举个例子:
了解binder之前我们看看原有Linux进程是如何通信的吧! 为什么需要在内存中拷贝两次呢?
在了解Linux进程通信前我们先理解下一个故事吧!
故事:
男孩和女孩在某次旅游一见钟情,而旅行结束的他们不得不返回各自的工作城市,
那个时代还没有微信,由于相隔在不同的城市。相思念的他们只能通过邮局抒发彼此爱慕之情
如果男孩想要给女孩发送信封,需要向本地邮局 寄信。再有全国邮局转发到 女孩所在的本地邮局
最后由女孩所在的本地邮局送到女孩手中
在进程中的角色:
男孩 称为进程A,工作城市A城,女孩称为进程B,工作城市B城
本地邮局称为用户空间, 全国邮局称为内核空间
本地邮局是他们彼此 离的最近的地方。他们能够在本地邮局直接接受和发送信封
1.1 这是普通linux进程通信的方式
转换专业术语的图
进程间,用户空间的数据不可共享,所以用户空间相当于私有空间
进程间,内核空间的数据可共享,所以内核空间 相当于公共空间
进程间如果需要做到通信,需要通过共享空间对数据转换。转换过程需要调用系统的api,这个过程称为**系统调用。
问题来了:
男孩写好信之后,发送信件给本地邮局 , 相当于一次拷贝 我们把这个过程称为(copy_from_user)
女孩收到当地邮局通知,需要从本地邮局取信。相当于第二次拷贝,我们把这个过程称为(copy_to_user)
两次拷贝究竟性能怎么样,当然拷贝是非常耗性能的,而两次拷贝可以再优化优化。
那binder是怎么做到一次拷贝的呢
1.2接着讲爱情的例子(Binder的实现机制)
后来在疫情结束后,这个女孩去了全国邮局依赖的快递公司上班去了,所有的邮件需要用快递公司来处理,刚好这家快递公司处理的是他们两个城市的信件。
这就给女孩创造了便利,不用去本地邮件取信件
但是男孩还是要发送信件。 binder拷贝也是发生在男孩这个地方copy_from_user
,女孩由于在快递公司上班,可以随意浏览男孩的信件。不需要再取信件了。也就减少了从本地邮局取快递这次拷贝过程.
思考: 大家还记不记的 接收端怎么写的,对! 接收端一定必须是服务Service,接收端不能是其他java对象。Service就是那个女孩。她必须在快递公司上班才能减少一次拷贝。所以这个Service,在通信前会注册在ServiceManager中。而男孩可以是任意对象,出现在任意地方
Linux 已有的进程通信,发送端和接收端可以是任意对象。出现在任意类中。但是必须牺牲多拷贝一次
Android的Binder通信,接受端不能是任意对象,只能是Service,这也节约了一次拷贝,牺牲了开发者的体验.
1.3 Binder少拷贝一次的原理:
全国邮局 相当于内核空间的内存,所有的应用都与内核空间的内存发生频繁的调用,在Binder中传递数据本质上是通过文件读取来实现的
大家记不记得Linux系统 是文件操作系统,都是基于文件展开的。进程通信中 File也能实现进程通信
Binder机制中 在内存与文件中设计了一层映射关系。内核空间的内存是虚的的。文件IO是实的。 映射指的是内存与文件的映射,映射是通过mmap函数,
而mmap函数 需要依赖一个文件,这个文件叫做“binder”。 对! 他没有后缀名,他就是一个文件。但是人们习惯性的把它称为binder驱动。
我们再来看看实际的是如何映射的
- 全国邮局中的A城与B城的信件,相当于内核空间内存一部分虚拟内存区域。
- A城与B城的快递公司,相当于文件实体。实际上邮件是由快递公司转发。邮局与快递公司的合作关系,可以理解内存与文件形成了映射关系(mmap)
1.4 服务端Service 也通过mmap函数监听文件的变化。
一旦有信件到达女孩的公司直接读取出来,而不用去本地邮件取信件。
女孩怎么在公司取出来呢? 大家忘记了女孩在快递公司上班呢,快递公司会给员工分布工作内容
女孩的工作内容是查看快递接收站和快递发送站
这样你说能不能看到男朋友发过来的信件,还不用亲自跑到本地邮局,那不是爽歪歪
绿色部分 是女孩进程,绿色内有两个角色(女孩接触到的 接收站和收发站)
这一套机制的实现基于 aidl文件编译机制
在编译时生成了一个继承自Binder中的IInterface接口(女孩)。接口中有这样一个内部类叫Stub(女孩工作接触的接收站)和一个内部类叫Proxy(女孩工作接触的发送站)
男孩有邮件到了,女孩直接在 快递接收站查看男孩发过来的邮件(Stub中的 onTransact方法)
女孩思恋男孩,跟男孩发送邮件(Proxy中的transact方法)
Stub与Proxy中所有的方法是native直接调用过来的。参数是直接从内核空间传递过来的,不需要发生拷贝。
如果女孩需要给男孩发送消息,也不用跑到本地邮局,直接在 快递发送站,插入一个邮件就好了(调用Proxy的transct方法)
减少一次拷贝发生在 服务端service。直接有nativie层 的Binder调起。不需要再次拷贝
大家看完了 是不是对整个Binder机制有深刻的认识呢
从Binder的底层原理分析,让Android开发者真正了解到Binder的通信机制。从Linux进程通信技术认知到为什么Android会选择Binder作为通信方式,到Linux的进程原理,最后借鉴Binder中mmap函数打造优异的存储框架。
视频目标:
结合视频掌握更快
掌握Linux进程通信技术,了解内存空间与用户空间。从Binder源码解析,到深入了解Binder通信机制。到最后借助Binder中的mmap函数打造优异存储框架。
下节从为什么会有内核空间与用户空间继续讲解**
以上是关于阿里面试必问:带你读懂Binder通信机制;拿捏面试官的主要内容,如果未能解决你的问题,请参考以下文章
Android 大厂高级面试必问36题以及算法合集(附:2022年Android 中高级面试题汇总以及面试题解析)
为什么现在BAT面试必问分布式?阿里大牛带你实战剖析分布式锁
干货Android BAT高级面试必问36题以及算法合集(附:46份面试题+49份源码解析笔记+145份项目实战PDF)