Framework笔记 | binder详解

谈谈你对binder的理解

思路:

  • binder是干嘛的(注意拓展)
  • binder存在的意义是什么?
    为什么不用别的替代方案呢?(主要分三点展开)
  • binder的架构原理是怎样的?
    (可以把架构图画出来,对着图讲)

binder是干嘛的

通信
  • binder分成两端
    一个是Client端,一个是Server端,两者
    可以在同一进程,也可以在不同进程

  • Client端可以向Server端发起远程调用
    可以传数据,把数据当做函数的参数来传;

  • 远程调用进程的边界是比较模糊的,
    你不用关心对方是在哪一个进程;

    9125154-3cb0f092cbe79d75.png



远程调用机制常规套路
9125154-19e0d6404bde6720.png
  • 首先,Client端要调用Server端的某个函数(如这里的call()函数);
    做法是首先把参数序列化到一个buffer,
    然后通过Linux的各种跨进程通信方式传到Server端的buffer,
  • 接着反序列化buffer,还原出各个参数;
  • 然后调用Server端对应的函数如这里的call函数;
  • 最后把函数返回结果按原路返回到Client端;



机制需要注意的问题
  • 性能要好;
    跨进程传递buffer的时候速度要快,
    尽量减少拷贝的次数;

  • 要方便:
    Linux提供的跨进程通信工具好比只是一根电话线,
    电话转发的算法,
    电话发送端声音转电信号,
    接收端电信号转声音,
    这些都需要做好上层工作;
    也就是说,
    我们需要在Linux提供的跨进程底层传输机制上,
    再搭建一套完整的框架才行,
    不然应用层开发很艰难;

  • 安全:
    就像打电话一样,
    首先肯定不是电话过来我就要接,还要看看号码是谁;
    其次最好是要有个骚扰拦截机制;

所以一套好用的远程调用机制还是很复杂的,
需要兼顾性能、方便、安全等因素,
而Binder机制,就是这么一个好机制!




binder存在的意义是什么

  • binder是跑在驱动层的,不是基于Linux的跨进程通信机制;
    它在内核态是没有用到任何Linux提供的跨进程底层传输机制
    它是单独被创作出来的一套机制;

  • 性能
    Linux的跨进程通信机制中管道和Socket,
    在跨进程通信的时候是需要内核来做中转的,
    这个就意味着两次数据拷贝(从应用层拷到内核,从内核拷到应用层)
    binder是有点区别,
    对于Binder来说,
    数据从发送方的缓存区拷贝到内核的缓存区,
    而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,
    所以只要拷一次;

    9125154-52cc5c83b7ec4a00.png

  • 方便应用
    逻辑简单直接,不会出问题;
    共享内存虽然性能很好,但是用起来很复杂;

    9125154-0cb9d6fab5e71f05.png

  • 安全
    普通的跨进程通信方式其实是不太安全的,
    像Socket,ip地址、端口什么的都是开放的,
    别人知道它的ip地址就能来连接它了;
    或者说管道也是,
    知道管道的名称,就能往里面写东西;
    这样子其实是不太安全的,很容易被人为利用;
    主要是因为,
    我们拿不到调用方可靠的声明信息,
    这个声明信息总不能让调用方自己去填吧,明显不可靠;
    可靠的方式是,
    这个身份标记只能由IPC机制本身在内核开通添加
    关于这一点Binder是做到了

以上三点足够说明Binder存在的意义



Binder的通信架构

9125154-0b4c196326c8d548.png
  • 四端参与,
    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:
    9125154-86ab285b8a5ea668.png
  • 首先调用binder_open()打开binder驱动,映射内存,然后启动binder线程
  • 接着调用binder_become_context_manager()
    “binder成为了上下文的管理者”,
    作用是:
    告诉binder驱动——“我就是ServiceManager,我就是中转站
    (像整理诸多电话发送端,分配给诸多电话接收端的一个中转站),
    ,大家不论是注册还是查询都可以来找我”;
  • 最后调用binder_loop(),进入binder的Loop循环;
binder的loop循环
9125154-d88ddc0c7d5f9974.png

函数中,

  • 首先把当前线程注册成binder线程
    BC_ENTER_LOOPER这个指令
    写到binder驱动(binder_write())
    就表示把当前线程注册成binder线程
    这里所说的当前线程
    ServiceManager的主线程;

  • 接着是一个for循环,里边,
    首先是读,
    BINDER_WRITE_READ,看起来好像是又写又读,
    但其实我们看bwr_read_size是大于0的,
    write_size没有赋值,所以这里是
    binder驱动发过来的数据、请求读进来;(ioctl())

  • 接着解析读进来的数据 / 请求
    然后调用回调函数func去处理这个请求;

相关阅读:
9125154-b0c6dbadf91dd137.png
图一
9125154-b1e3b40faac3fc6d.png
图二

ServiceManager总的流程就如上了;

接着往下,回顾架构图:
9125154-9dc993d3f3b454a6.png
  • ServiceManager启动Binder机制之后,
    它就进入了一个loop循环,(详细的如刚刚描述)
    等待Client和Server的请求;

  • Server是系统服务,Client一般是应用程序;
    系统服务启动之后,才是应用启动;
    所以图中这里,Server端是先和ServiceManager交互的;

  • Server启动的时候,
    要先把自己的binder对象
    注册到ServiceManager;
    接下来看一下相关的代码;



以系统服务SurfaceFlinger,观察系统服务是怎么在ServiceManager注册的
  • 首先看一下SurfaceFlinger的入口main函数:
    9125154-a58a465d49b61704.png
    • 首先头两行就是启动binder机制,即打开binder驱动,映射内存,然后启动binder线程

    • 接着第三第四行是,binder实体对象的初始化,
      对于系统服务SurfaceFlinger,
      它的业务类对象就是SurfaceFlinger,
      这个业务类对象SurfaceFlinger同时也是一个binder实体对象;

    • 初始化完了之后,
      就是要向ServiceManager注册了,
      注册的话首先通过defaultServiceManager()
      拿到ServiceManager的BpBinder;
      然后,
      发起Service调用,
      把flinger这个binder对象
      传到ServiceManager;sm->addServier();

    • 最后进入一个loop循环;flinger->run();

系统服务是在ServiceManager注册的整个流程就是如上这样;



看一下defaultServiceManager()的实现
  • 重点了解一下怎么获取ServiceManager对象

    9125154-80e848a3f0c4c092.png

  • 首先第一行,
    有一个gDefaultServiceManager
    它只初始化一次,然后直接返回;

  • 接着是一个while循环,
    聚焦标红的地方,
    ProcessState::self()->getContextObject()
    是真正获取ServiceManager对象的;

  • getContextObject()的作用:
    getStrongProxyForHandle(0)

    9125154-fbe4b6d5c4db0302.png

    查询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(),进行后续的逻辑;
    9125154-ac69825fbac12736.png
  • 两个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()
    9125154-c14e55c34d0c9e76.png
    注意这里的mHandle,
    我们可以看到底层在跟驱动交互的时候,
    它是不分BPBinder,还是BinderProxy这些的,
    它只认这个Handle值

    所以我们看上层封装的这么一层层对象,
    其实传递到最后,
    核心就是传递到,这么一个Handle值操作上来;
    之后,
    code其实就是函数调用码,
    data就是带的参数,
    reply就是binder驱动返回的结果,
    flags就是一些标志;



接着看IPCThreadStatetransact()函数
9125154-10e8eca776c4bdb7.png
  • 函数中,
    首先调用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端是怎么处理的
9125154-625677f6d4fde720.png
  • 以上就是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通信的分层架构图

通过这个架构图我们回顾一下刚刚讨论的知识点:

9125154-9bba070cabe3e6d8.png

这个图我们可以分成几个维度来看,

  • 首先有三个角色,
    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的子类;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌川江雪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值