Framework笔记 | binder详解

作者: 凌川江雪 | 来源:发表于2019-09-14 12:38 被阅读0次
    谈谈你对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()的作用:

    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的子类;

    相关文章

      网友评论

        本文标题:Framework笔记 | binder详解

        本文链接:https://www.haomeiwen.com/subject/gbuyectx.html