Framework笔记---binder详解

116 阅读11分钟

#####谈谈你对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:![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4fff5b288c3f4af1be6e74b99da67ed5~tplv-k3u1fbpfcp-zoom-1.image) - 首先调用`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()的作用:

  1. 保存诸多数据到Parcel中,尤其注意Service系统服务的binder对象也写到Parcel实例data中;
  2. 调用transact(),进行后续的逻辑;
  • 两个Parcel成员data是发到驱动的参数, reply是驱动返回的结果, 把各种参数都放进data里面, 包括Service系统服务的binder对象也写到Parcel实例data中;(data.writeStrongBinder(service);

  • 写好之后(各种data.writexxx()之后), remote()拿到ServiceManagerBinderProxy对象, 然后调用其方法transact(), 把请求发出去,请求参数包括 请求码ADD_SERVICE_TRANSACTION, data、&reply等等;
    transact()干的事情 transact()把请求转给了IPCThreadState, 调用IPCThreadStatetransact()注意这里的mHandle, 我们可以看到底层在跟驱动交互的时候, 它是不分BPBinder,还是BinderProxy这些的, 它只认这个Handle值
    所以我们看上层封装的这么一层层对象, 其实传递到最后, 核心就是传递到,这么一个Handle值操作上来; 之后, code其实就是函数调用码, data就是带的参数, reply就是binder驱动返回的结果, flags就是一些标志;



####接着看IPCThreadStatetransact()函数

  • 函数中, 首先调用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端是怎么处理的

  • 以上就是ServiceManagerServer端主要代码;
  • 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, 通过IPCThreadStatetransact函数, 发送请求到Binder驱动;

  • 接着驱动再转发,发到Server进程; 然后在Server进程的Binder线程里边, 就会处理、执行到onTransact()函数, 再一层层往上,传到应用层;

  • 所以我们看这个分层, 其实跟网络里边传输的分层(计网四层、五层、七层协议等)有点像;






Binder和BinderProxy是IBinder的子类;