Framework笔记 | binder详解
谈谈你对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机制,就是这么一个好机制!
binder存在的意义是什么
-
binder是跑在驱动层的,不是基于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的子类;