#####谈谈你对binder的理解 思路:
- binder是干嘛的(注意拓展)
- binder存在的意义是什么? 为什么不用别的替代方案呢?(主要分三点展开)
- binder的架构原理是怎样的? (可以把架构图画出来,对着图讲)
#binder是干嘛的 ####通信
-
binder分成两端, 一个是Client端,一个是Server端,两者 可以在同一进程,也可以在不同进程; -
Client端可以向Server端发起远程调用, 可以传数据,把数据当做函数的参数来传; -
远程调用其进程的边界是比较模糊的, 你不用关心对方是在哪一个进程;
####远程调用机制常规套路
- 首先,Client端要调用Server端的某个函数(如这里的call()函数); 做法是首先把参数序列化到一个buffer, 然后通过Linux的各种跨进程通信方式传到Server端的buffer,
- 接着反序列化buffer,还原出各个参数;
- 然后调用Server端对应的函数如这里的call函数;
- 最后把函数返回结果按原路返回到Client端;
####机制需要注意的问题
-
性能要好; 跨进程传递buffer的时候速度要快, 尽量减少拷贝的次数;
-
要方便: Linux提供的跨进程通信工具好比只是一根电话线, 电话转发的算法, 电话发送端声音转电信号, 接收端电信号转声音, 这些都需要做好上层工作; 也就是说, 我们需要在Linux提供的跨进程底层传输机制上, 再搭建一套完整的框架才行, 不然应用层开发很艰难;
-
安全: 就像打电话一样, 首先肯定不是电话过来我就要接,还要看看号码是谁; 其次最好是要有个骚扰拦截机制;
所以一套好用的
远程调用机制还是很复杂的, 需要兼顾性能、方便、安全等因素, 而Binder机制,就是这么一个好机制!
- Android系统架构普及
物理内存即移动设备上的RAM, 当启动一个Android程序时,会启动一个Dalvik VM(或ART)进程, 系统会给它分配固定的内存空间(16M,32M不定),这块内存空间会映射到RAM上某个区域。 然后这个Android程序就会运行在这块空间上。
####为什么需要IPC?!! 每一个APP对应一个VM进程, 进程之间的内存相互独立,无法直接交流, 只能以Linux Kernel为媒介, 进行IPC!!!
#binder存在的意义是什么
-
binder是跑在驱动层(如上图中的
Drivers)的, 同时它不是基于Linux的跨进程通信机制; 它在内核态是没有用到任何Linux提供的跨进程底层传输机制它是单独被创作出来的一套机制; -
性能: Linux的跨进程通信机制中管道和Socket, 在跨进程通信的时候是需要内核来做中转的, 这个就意味着两次数据拷贝(从应用层拷到内核,从内核拷到应用层) binder是有点区别, 对于Binder来说, 数据从发送方的缓存区拷贝到内核的缓存区, 而接收方的缓存区与内核的缓存区是映射到同一块物理地址的, 所以只要拷一次;
-
方便应用 逻辑简单直接,不会出问题; 共享内存虽然性能很好,但是用起来很复杂;
-
安全 普通的跨进程通信方式其实是不太安全的, 像Socket,ip地址、端口什么的都是开放的, 别人知道它的ip地址就能来连接它了; 或者说管道也是, 知道管道的名称,就能往里面写东西; 这样子其实是不太安全的,很容易被人为利用; 主要是因为, 我们拿不到调用方可靠的声明信息, 这个声明信息总不能让调用方自己去填吧,明显不可靠; 可靠的方式是, 这个身份标记只能由IPC机制本身
在内核开通添加, 关于这一点Binder是做到了
以上三点足够说明Binder存在的意义
#Binder的通信架构
-
四端参与, Client、Server、ServiceManager、binder驱动;
-
上图展示的是
系统服务的binder通信, 只有系统服务才能注册到ServiceManager,应用服务的binder是不能注册到ServiceManager,通过不了验证的; -
Client是应用进程; Server是系统服务,可能跑在SystemService进程,也可能是在单独的进程; ServiceManager是单独的系统进程; 这里不论哪个进程,它们在启动的时候, 第一件事,都是要先启动binder机制,这个是binder通信的前提;
####进程如何启动binder机制?
- ######打开binder驱动 这样binder驱动就会为进程创立一套档案;
- ######创立后返回
档案的描述符(句柄), 用这个描述符去进行内存映射,分配缓冲区(接下来的binder通信需用到缓冲区); - ######最后,启动binder线程; 启动binder线程, 一方面是要把这个线程注册到Binder驱动, 另一方面这个线程要进入Loop循环,不断地跟binder驱动进行交互;
###binder通信 #####ServiceManager - 下面这个是ServiceManager的入口函数main: - 首先调用`binder_open()`去`打开binder驱动,映射内存,然后启动binder线程`; - 接着调用`binder_become_context_manager()` “binder成为了上下文的管理者”, 作用是: 告诉binder驱动——“我就是`ServiceManager`,我就是`中转站` (像整理诸多电话发送端,分配给诸多电话接收端的一个中转站), ,大家不论是`注册`还是`查询`都可以来找我”; - 最后调用`binder_loop()`,进入binder的Loop循环;
######binder的loop循环
函数中,
-
首先把
当前线程注册成binder线程; 把BC_ENTER_LOOPER这个指令 写到binder驱动,(binder_write())就表示把当前线程注册成binder线程; 这里所说的当前线程即ServiceManager的主线程; -
接着是一个for循环,里边, 首先是读, 即
BINDER_WRITE_READ,看起来好像是又写又读, 但其实我们看bwr_read_size是大于0的, 而write_size没有赋值,所以这里是读, 把binder驱动发过来的数据、请求读进来;(ioctl()) -
接着解析读进来的
数据 / 请求, 然后调用回调函数func去处理这个请求;
相关阅读:
ServiceManager总的流程就如上了;
接着往下,回顾架构图:
-
ServiceManager启动Binder机制之后, 它就进入了一个loop循环,(详细的如刚刚描述) 等待Client和Server的请求;
-
Server是系统服务,Client一般是应用程序; 系统服务启动之后,才是应用启动; 所以图中这里,Server端是先和ServiceManager交互的;
-
Server启动的时候, 要先把自己的binder对象 注册到ServiceManager; 接下来看一下相关的代码;
####以系统服务SurfaceFlinger,观察系统服务是怎么在ServiceManager注册的
- 首先看一下
SurfaceFlinger的入口main函数:-
首先头两行就是启动binder机制,即
打开binder驱动,映射内存,然后启动binder线程; -
接着第三第四行是,binder实体对象的初始化, 对于系统服务SurfaceFlinger, 它的业务类对象就是SurfaceFlinger, 这个业务类对象SurfaceFlinger同时也是一个binder实体对象;
-
初始化完了之后, 就是要向ServiceManager注册了, 注册的话首先通过
defaultServiceManager()拿到ServiceManager的BpBinder; 然后, 发起Service调用, 把flinger这个binder对象 传到ServiceManager;sm->addServier(); -
最后进入一个loop循环;
flinger->run();
-
系统服务是在ServiceManager注册的整个流程就是如上这样;
####看一下defaultServiceManager()的实现
-
重点了解一下怎么获取
ServiceManager对象 -
首先第一行, 有一个
gDefaultServiceManager, 它只初始化一次,然后直接返回; -
接着是一个while循环, 聚焦标红的地方,
ProcessState::self()->getContextObject()是真正获取ServiceManager对象的; -
getContextObject()的作用: 它
getStrongProxyForHandle(0),即
查询0号handle值对应的binder引用,即ServiceManager;-
有点像要查某家公司的电话号码, 需要打114查号台查询, 这里的
ServiceManager就类似于114, 正如所有人都知道114是干嘛的, 所有的启用binder机制的进程都知道0号handle对应的就是ServiceManager; -
那我们要查系统服务的时候, 只要找0号的handle咨询即可;
-
若没有查到0号handle? 可能这个ServiceManager还没有来得及给自己注册binder驱动; 就像114客服人员还没来得及上班一样; 那怎么办? 等一会儿再重新试试咯——
if(....==NULL)sleep(1);
-
####接着看一下addService()的实现
小结,addService()的作用:
- 保存诸多数据到Parcel中,尤其注意Service系统服务的binder对象也写到Parcel实例data中;
- 调用
transact(),进行后续的逻辑;
-
两个
Parcel成员,data是发到驱动的参数,reply是驱动返回的结果, 把各种参数都放进data里面, 包括Service系统服务的binder对象也写到Parcel实例data中;(data.writeStrongBinder(service);) -
写好之后(各种
data.writexxx()之后),remote()拿到ServiceManager的BinderProxy对象, 然后调用其方法transact(), 把请求发出去,请求参数包括 请求码ADD_SERVICE_TRANSACTION, data、&reply等等;
transact()干的事情transact()把请求转给了IPCThreadState, 调用IPCThreadState的transact(),注意这里的
mHandle, 我们可以看到底层在跟驱动交互的时候, 它是不分BPBinder,还是BinderProxy这些的, 它只认这个Handle值;
所以我们看上层封装的这么一层层对象, 其实传递到最后, 核心就是传递到,这么一个Handle值操作上来; 之后, code其实就是函数调用码, data就是带的参数, reply就是binder驱动返回的结果, flags就是一些标志;
####接着看IPCThreadState的transact()函数
-
函数中, 首先调用
writeTransactionData()就是 要把要写到binder驱动中的数据准备好; 我们知道binder驱动是不认识这个Parcel的数据结构的, 我们得先把它转化成一个binder驱动认识的数据结构, 即binderTransactionData这个数据结构再发给binder驱动; 而writeTransactionData()的作用 就是完成这个数据结构的转化过程, 或者说这个数据准备过程;
总之, 这个方法会将传递来的参数组装成一个binder_transaction_data结构体对象, 然后将cmd(BC_TRANSACTION)和这个结构体对象tr都写入IPCThreadState的属性mOut中。 -
接着往下看, 这个
waitForResponse()就是跟binder驱动交互,和通信协议的, 这个方法主要调用了talkWithDriver方法, 与Binder驱动进行数据交互, 并一直等待Binder驱动的响应,接收服务端的返回结果;
函数中, 首先判断一下传进来transact()的flags, 如果是TH_ONE_WAY, 就不用等回复了,两个相关参数都传NULL; (waitForResponse(NULL,NULL)) 如果不是TH_ONE_WAY, 就需要有一个reply来接收回复, 调用时有传进来reply,则用调用传进来的reply, 没有传进来的,则临时创建一个Parcel来充当reply;
####接着看请求在Server端是怎么处理的
- 以上就是
ServiceManager的Server端主要代码; - binder的Server端处理请求都是在onTransact()里边;
swich(code)找到ADD_SERVICE_TRANSACTION这个case, 然后从Pacel里面, 把系统服务的binder读出来(data.readStrongBinder();), 读出来的是 根据binder引用对应的handle值封装的一个BinderProxy对象;- 接着调用本地的
addService函数, 把刚刚取出来的系统服务binder存好;- 上面讨论过了,
addService()的作用是 保存诸多数据到Parcel中, 包括Service系统服务的binder对象也写到Parcel实例data中;
data.readStrongBinder();读出来的是 系统服务的一个BinderProxy对象, 而Binder和BinderProxy是IBinder的子类, 这里向上转型成IBinder对象(sp<IBinder> b); 然后调用本地的addService函数,存好数据;
- 上面讨论过了,
- 存好数据之后,
往
reply中写一个返回值;
在Server端注册就差不多是这样了; 至于Client端从ServcieManager获取系统服务的原理跟这个差不多;
###Binder通信的分层架构图
通过这个架构图我们回顾一下刚刚讨论的知识点:
这个图我们可以分成几个维度来看,
-
首先有三个角色,
Client、Server、Binder驱动 -
从分层的角度,又可以分成
应用层、Framework层(Java层 + Native层)、驱动层 -
从Binder对象的角度看, 可以分成两端,
代理端(Proxy、BinderProxy、BpBinder);实体端(Stub、Binder、BBinder); -
从
Client端开始看, 当我们拿到一个Binder对象的Proxy的时候, 我们要发起IPC调用了; 这个调用其实就是把请求往下 丢给了BinderProxy; 请求继续往下走,又会丢到Native层的Proxy--BpBinder对象; 然后这个BpBinder对象,又会把请求 转交给IPCThreadState, 通过IPCThreadState的transact函数, 发送请求到Binder驱动; -
接着驱动再转发,发到Server进程; 然后在Server进程的Binder线程里边, 就会处理、执行到
onTransact()函数, 再一层层往上,传到应用层; -
所以我们看这个分层, 其实跟网络里边传输的分层(计网四层、五层、七层协议等)有点像;
Binder和BinderProxy是IBinder的子类;