【安卓BInder底层2】对Binder的理解

一、Binder是干嘛的?

binder是用来通信的,分为client端和sever端。client和sever可在同一进程,也可以不在同一进程中。

client端可向sever端发起远程函数调用。sever端也可以给client端返回数据。

二、binder存在的意义是什么?为啥选择它?

1、一套完善的跨进程通信方式

Linux提供了很多跨进程方式,在安卓的Framework层使用也有很多,这些都是根据不同的场景、用途进行合理筛选使用的。但是在这些跨进程方式在安卓端的应用层就会有很多安全问题了(如下图)

下面是基于linux提供的IPC方式设计如何实现一套完善的跨进程方式的构思:

请添加图片描述
注意:binder是驱动,是内核态,binder没有用到linux的跨进程方式,而是自己发明的跨进程方式。

2、一套完善的跨进程方式通常满足如下几点

(1)性能好:只需要一次内存拷贝。

linux常用的跨进程通信方式在跨进程通信时是需要内核作为中转的,这就意味着两份内存拷贝 即一份是从应用层拷贝到内核,还有一份就是从内核拷贝到应用层。

binder是和linux提供的IPC有点区别的,binder提供一份物理内存,这块物理内存同时映射到 内核和目标应用进程。这样当把数据从应用进程拷贝到内核空间时,另一个进程的用户空间 会映射出一份数据。所以只需要拷贝一次。

(2)方便易用

逻辑简单直接、不容易出问题。共享内存虽然性能好但是使用十分复杂,远远没有binder好用。

(3)安全

普通的linux跨进程方式是很不安全的,容易被恶意利用,主要是拿不到调用方可靠的声明信息, 声明信息总不能让调用方自己去填吧?明显是不可靠的。可靠的方式是身份标志由IPC机制在内核态添加。这点binder做到了。

Linux ipc unsafe example:

  • Socket:IP地址是开发的,如果知道他的IP地址就可以给发消息。
  • 匿名管道:如果知道了管道的名称就可以往管道里面写点东西。
3、总结

性能好,方便,安全就是Binder存在的意义,这也是安卓选择他作为独特的IPC通信方式的理由。

三、binder的通信架构原理

请添加图片描述

系统服务binder的通信架构原理图

如图为系统服务binder的通信架构图,其中:

  • Client 为应用进程端,可以IPC调用系统服务。
  • Sever为系统服务,几乎系统服务都是跑在SystemSever进程。系统服务被ServiceManager统一管理。
  • ServiceManager跑在单独的进程中,管理安卓的系统服务。

注意不论哪个进程,在启动时首先要启动bind机制,这样进程就能与其他进程进行IPC了。这也是bind通信的前提。

1、进程如何启动BInder机制的回顾

(1)打开binder驱动:首先要打开Binder驱动,binder驱动就会为进程创建一些信息。
(2)内存映射,分配缓冲区:打开驱动后会返回一个文件描述符,接下来使用这个描述符进行内存映射,分配缓冲区。
(3)启动binder线程:一方面吧线程注册到binder驱动,另一方面线程不断loop循环跟binder驱动交互

2、开始binder通信-从ServiceManager说起

请添加图片描述

1、通过binder_open 函数打开binder驱动、映射内存
2、通过binder_become_context_manager 使binder成为上下文管理者

其实就是告诉binder驱动 自己就是ServiceManager,自己就是那个中转站,系统服务的注册、查询都可以来找自己。

3、开启binder loop循环

请添加图片描述

1、首先把当前线程注册为binder线程。

  • 吧BC_ENTER_LOOPER 指令通过buffer写入binder驱动,就表示把当前线程注册为binder线程
  • 当前线程就是ServiceManager主线程

2、for循环中不断从binder驱动请求,读取请求消息

  • bwr 的readsize>0 为读
  • binder_parse 进行解析请求,然后通过func回调函数解析请求
3、总结梳理

回顾下系统服务binder的通信架构原理图:

(1)ServiceManager启动binder机制后就进入loop循环了,等待系统服务和应用进程的请求。

(2)系统服务优先于应用进程启动,所以ServiceManager是优先于系统服务交互的

(3)系统服务启动时会把自己的服务注册到ServiceManager。

(4)Client端使用服务时就向ServiceManager进行ipc查询获取相应服务对象。

4、系统服务-SurfaceFlinger 启动栗子

(1)SurfaceFlinger #main
在这里插入图片描述

1、前两行就是启动BInder机制

打开binder驱动、映射内存、注册binder线程。

2、三四行就是初始化对象。
3、五六行就是像ServiceManager注册

拿到ServiceManager bpBInder对象发起远程调用注册SurfaceFlinger 到ServiceManager

4、最后开启循环

(2)深挖:defaultServiceManager 具体实现
在这里插入图片描述

defaultServiceManager 是如获取ServiceManager对象的?
1、通过getContextObject 来获取ServiceManager对象,具体是通过getStrongProxyForHandle(0) 来实现的。
2、getStrongProxyForHandle() 这里就是查询0对应的Handle值,安卓系统中0号handle值就是ServiceManager对象。
3、ServiceManager为空代表ServiceManager还未完成初始化。

(3)深挖:addService的具体实现

在这里插入图片描述

1、定义了Parcel类型的变量data、reply

data:向binder驱动发送的数据
reply:binder驱动返回的结果

2、remote是binderProxy对象,调用transact进行ipc
3、transact其实吧功能交给了IPCThreadState的transact进行处理。

mHandler:可以看出底层代码给驱动进行交互时是不区分bpbinder、binderProxy这些东西的,底层直接使用mHandler这个变量使用不同的handle值进行区分。
code:函数调用码
data:发送的数据
reply:返回的数据
flags:一些标志

IPCThreadState#transact

在这里插入图片描述

1、Parcel这个数据结构 底层的binder是不认识的,所以通过writeTransationData 来转换为binder认识的数据结构。
2、如果是one way机制,就不用等回复了,如代码 reply 参数为null

err = waitForResponse(null,null)

3、如果不是one way,且返回了数据,调用err = waitForResponse(reply)
4、如果不是one way,且无数据返回,则调用err = waitForResponse(&fakeReply)
ps:waitForResponse走通信协议的,通过底层通信协议与binder驱动进行交互。

(4)ServiceManager#onTransact Sever端的处理
在这里插入图片描述

1、从parcel中读取系统服务的binder对象

其实就是根据handle值封装了binderProxy对象
2、调用本地的addService方法
3、reply里面写返回值

(5)分层总结

请添加图片描述

四、一次完整的 IPC 通信流程是怎样的?

1、Client 端如何和 Binder 驱动进行交互的

首先是从应用层的 Proxy 的 transact 函数开始,传递到 Java 层的 BinderProxy,最后到 Native 层的 BpBinder 的 transact。

在 BpBinder 的 transact 实际上是调用 IPCThreadState 的 transact 函数,它的第一个参数是 handle 值,Binder 驱动就会根据这个 handle 找到 Binder 引用对象,继而找到 Binder 实体对象。

IPCThreadState#transact 这个函数中,做了两件事件,一件是调用 writeTransactionData 向 Binder 驱动发出一个 BC_TRANSACTION 的命令协议,把所需参数写到 mOut 中,第二件是 waitForResponse 等待回复,在它里面才会真正的和 Binder 驱动进行交互,也就是调用 talkWithDriver,然后接收到的响应执行相应的处理。

这时候 Client 接收到的是 BR_TRANSACTION_COMPLETE,表示 Binder 驱动已经接收到了 Client 的请求了。在这里面还有一个 cmd 为 BR_REPLY 的返回协议,表示 Binder 驱动已经把响应返回给 Client 端了。在 talkWithDriver 中,是通过系统调用 ioctl 来和 Binder 驱动进行交互的,传递一个 BINDER_WRITE_READ 的命令并且携带一个 binder_write_read 数据结构体。在 Binder 驱动层就会根据 write_size/read_size 处理该 BINDER_WRITE_READ 命令。

到这里,已经讲完了 Client 端如何和 Binder 驱动进行交互的了,下面就讲 Service 端是如何和 Binder 驱动进行交互的。

2、Service 端是如何和 Binder 驱动进行交互的

Service 端首先会开启一个 Binder 线程来处理进程间通信请求,也就是通过 new Thread 然后把该线程 joinThreadPool 注册到 Binder 驱动。

注册呢也就是通过 BC_ENTER_LOOPER 命令协议来做的,接下来就是在 do while 死循环中调用 getAndExecuteCommand。

它里面做的就是不断从驱动读取请求,也就是 talkWithDriver,然后再处理请求 executeCommand。在 executeCommand 中,就会根据 BR_TRANSACTION 来调用 BBinder Binder 实体对象的 onTransact 函数来进行处理,然后在发送一个 BC_REPLY 把响应结构返回给 Binder 驱动。

Binder 驱动在接收到 BC_REPLY 之后就会向 Service 发送一个 BR_TRANSACTION_COMPLETE 协议表示 Binder 驱动已经收到了,在此同时呢,也会向 Client 端发送一个 BR_REPLY把响应回写给 Client 端。

3、总结

上面的 onTransact 函数就是 Service 端 AIDL 生成的 Stub 类的 onTransact 函数,这时一次完整的 IPC 通信流程就完成了。

在这里插入图片描述

五、Binder 对象跨进程传递的原理是怎么样的?

有以下五点:

1、Parcel 的 writeStrongBinder 和 readStrongBinder
2、Binder 在 Parcel 中存储原理,flat_binder_object
3、说清楚 binder_node,binder_ref
4、目标进程根据 binder_ref 的 handle 创建 BpBinder
5、由 BpBinder 再往上到 BinderProxy 到业务层的 Proxy

在 Native 层,Binder 对象是存在 Parcel 中的,通过 readStrongBinder/writeStrongBinder 来进行读或写,在其内部是通过一个 flat_binder_object 数据结构进行存储的,它的 type 字段是 BINDER_TYPE_BINDER,表示 Binder 实体对象,它的 cookie 指向自己。

Parcel 到了驱动层是如何处理的呢?其实就是根据 flat_binder_object 创建用于在驱动层表示的 binder_node Binder 实体对象和 binder_ref Binder 引用对象。

读 Binder 对象就是调用 unflatten_binder 把 flat_binder_object 解析出来,如果是 Binder 实体对象,返回的就是 cookie,如果是 Binder 引用对象,就是返回 getStrongProxyForHandle(handle),其实也就是根据 handle 值 new BpBinder 出来。

六、Binder OneWay 机制

OneWay 就是异步 binder 调用,带 ONEWAY 的 waitForResponse 参数为 null,也就是不需要等待返回结果,而不带 ONEWAY 的,就是普通的 AIDL 接口,它是需要等待对方回复的。

对于系统服务来说,一般都是 oneway 的,比如在启动 Activity 时,它是异步的,不会阻塞系统服务,但是在 Service 端,它是串行化的,都是放在进程的 todo 队列里面一个一个的进行分发处理。

在这里插入图片描述

oneWay在framwork中的应用
1、scheduleLauncherActivity
2、IWindow接口
3、IServiceConnection接口

End

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值