android的IPC机制 - Binder

来源:互联网 发布:电大网站美工期末 编辑:程序博客网 时间:2024/04/30 06:58

一 Binder提供的功能

1.  用驱动程序来推进进程间通信。

2. 通过共享内存的方式来提供性能

3. 为进程请求分配每个进程的线程池

4. 引用计数和跨进程的对象引用映射。

5. 进程间同步调用。


如上为Binder原理图:

Binder通信模型

Binder框架:

Binder定义了四个角色:
 - client
 - Server
 - ServiceManager
 - Binder驱动
其中client, server, ServiceManager运行于用户空间,而Binder驱动运行于内核空间。

类比(互联网)理解:

 - client                                  ------->  客户端
 - Server                                ------->  服务器
 - ServiceManager              ------->  DNS服务器
 - Binder驱动                        ------->  路由器

Binder使用Client - Server通信方式:

  1.  一个进程作为Server提供诸如视频/音频解码,视频捕获,地址本查询,网络连接等服务;

  2. 多个进程作为 Client向Server发起服务请求,获得所需要的服务。


Client-Server通信据必须实现以下两点:

    一是server必须有确定的 访问接入点或者说地址来接受Client的请求,并且Client可以通过某种途径获知Server的地址;

    二是制定Command-Reply协议来传 输数据。

例子:

    例如在网络通信中Server的访问接入点就是Server主机的IP地址+端口号,传输协议为TCP协议。

  1. 对Binder而言,Binder可 以看成Server提供的实现某个特定服务的访问接入点, Client通过这个‘地址’向Server发送请求来使用该服务;

  2. 对Client而言,Binder可以看成是通向Server的管道入口,要想和某个 Server通信首先必须建立这个管道并获得管道入口。


Binder驱动

驱动作用:

     负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据 包在进程之间的传递和交互等一系列底层支持。

驱动和应用程序之间定义了一套接口协议:

     主要功能由ioctl()接口实现,不提供 read(),write()接口,因为ioctl()灵活方便,且能够一次调用实现先写后读以满足同步交互,而不必分别调用write()和 read()。

     Binder驱动的代码位于linux目录的kernel/drivers/staging/binder.c中。


ServiceManager与实名Binder

SMgr的作用是 (类似于DNS):

    将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得 对Server中Binder实体的引用。

具体过程:

    1. Server端:

        Server创建了Binder的实体,为其取一个字符形式并可读易记的名字,将这个Binder连同名字以数据包的形式通过Binder驱动发送给ServerManager,通知SMgr注册一个名叫张三的Binder,它位于Server中。

    2. 驱动端:

        它为这个穿过进程边界的Binder,创建位于内核中的实体节点以及SMgr对实体的引用,并将名字及新建的引用打包传递给SMgr。 

    2. ServiceManager端:

        Smgr收到数据包时,从中取出名字和引用填入一张查找表中。

这样,client就可以通过ServiceManager来获取Server中的binder实体的引用,从而可以进程间通信。


ServiceManager进程与Server进程? -> 也是进程间通信

我们的目的是client -> server进程间通信,但实际上,ServiceManager与Server也是进程间通信。

这里面其实是两条线:

一. Server注册服务,创建binder并通知ServiceManager.


二. ServiceManager也作为Server端,注册了服务,创建了SMgr的binder实体,SMgr内核Binder实体,这样client端就可以利用SMgr内核Binder实体的引用来实现Binder的注册,查询和获取

  - SMgr提供的Binder比较特殊,它没有名字也不需要注册,当一个进程使用BINDER_SET_CONTEXT_MGR命令将自己 注册成SMgr时Binder驱动会自动为它创建Binder实体。

  - 其次这个Binder的引用在所有Client中都固定为 0而无须通过其它手段获得。

    也就是说,一个Server若要向SMgr注册自己Binder就必需通过0这个引用号和SMgr的Binder通信。


Client获得实名Binder的引用

一. 当Server向SMgr注册了Binder实体及其名字后, client就可以通过名字获取该Binder的引用了。
  调用过程:
  -. Client利用保留的0号引用向SMgr请求访问某个Binder:
       eg. 要查找名字为张三的Binder,SMgr在查找表中根据名字查找到
       然后将该引用作为回复发送给发起请求的Client。

    该Binder,实际有两个引用:
    -- 位于SMgr中的
    -- 位于发送请求的Client中的

简单理解:
    实际调用正是通过0号引用获取到 -> SMgr内核Binder实体 -> SMgr中Binder实体 -> 获取SMgr中的binder引用
    即: Client的binder引用 <-- SMgr中的binder引用


Binder在应用程序中的表述 -- 具体需要参考AIDL使用去理解

  - Binder本质上只是一种底层的通信方式,和具体的服务没有任何关系,为了提供具体的服务,Server必须提供一套接口函数,一边Client通过远程访问使用各种服务。
  - 即Proxy 设计模式:
       将接口函数定义在一个抽象类中,Server和Client都会以该抽象类为基类实现所有接口函数,所不同的是Server端是真正的功能实现,而Client端是对这些函数远程调用请求的包装

  简单理解:
        Server端实际功能实现,Client端调用请求封装函数,通过Binder驱动来调用Server实际实现。

Binder在Server端的表述 - Binder实体

1. 定义一个抽象类封装Server所有功能,其中包含一系列纯虚函数,留待Server 和 Proxy 各自实现。
2. 引入Binder:
     Server端再定义一个Binder抽象类 -> 处理来自Client的Binder请求数据包,其中最重要的成员时虚函数: onTransact()
     onTransact()作用:分析收到的数据包,调用相应的接口函数处理请求。
  

Binder内存映射和接收缓存区管理

Binder 采用一种全新策略:由Binder驱动负责管理数据接收缓存。我们注意到Binder驱动实现了mmap()系统调用,这对字符设备是比较特殊的,因为 mmap()通常用在有物理存储介质的文件系统上,而象Binder这样没有物理介质,纯粹用来通信的字符设备没必要支持mmap()。Binder驱动当然不是为了在物理介质和用户空间做映射,而是用来创建数据接收的缓存空间。先看mmap()是如何使用的:


fd = open("/dev/binder", O_RDWR);


mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);


这样Binder的接收方就有了一片大小为MAP_SIZE的接收缓存区。mmap()的返回值是内存映射在用户空间的地址,不过这段空间是由驱动管理,用户不必也不能直接访问(映射类型为PROT_READ,只读映射)。


接收缓存区映射好后就可以做为缓存池接收和存放数据了。前面说过,接收数据包的结构为binder_transaction_data,但这只是消息头,真正的有效负荷位于data.buffer所指向的内存中。这片内存不需要接收方提供,恰恰是来自mmap()映射的这片缓存池。在数据从发送方向接收方拷贝时,驱动会根据发送数据包的大小,使用最佳匹配算法从缓存池中找到一块大小合适的空间,将数据从发送缓存区复制过来。要注意的是,存放 binder_transaction_data结构本身以及表4中所有消息的内存空间还是得由接收者提供,但这些数据大小固定,数量也不多,不会给接收方造成不便。映射的缓存池要足够大,因为接收方的线程池可能会同时处理多条并发的交互,每条交互都需要从缓存池中获取目的存储区,一旦缓存池耗竭将产生导致无法预期的后果。


有分配必然有释放。接收方在处理完数据包后,就要通知驱动释放data.buffer所指向的内存区。在介绍Binder协议时已经提到,这是由命令BC_FREE_BUFFER完成的。


通过上面介绍可以看到,驱动为接收方分担了最为繁琐的任务:分配/释放大小不等,难以预测的有效负荷缓存区,而接收方只需要提供缓存来存放大小固定,最大空间可以预测的消息头即可。在效率上,由于mmap()分配的内存是映射在接收方用户空间里的,所有总体效果就相当于对有效负荷数据做了一次从发送方用户空间到接收方用户空间的直接数据拷贝,省去了内核中暂存这个步骤,提升了一倍的性能。顺便再提一点,Linux内核实际上没有从一个用户空间到另一个用户空间直接拷贝的函数,需要先用copy_from_user()拷贝到内核空间,再用copy_to_user()拷贝到另一个用户空间。为了实现用户空间到用户空间的拷贝,mmap()分配的内存除了映射进了接收方进程里,还映射进了内核空间。所以调用copy_from_user()将数据拷贝进内核空间也相当于拷贝进了接收方的用户空间,这就是Binder只需一次拷贝的‘秘密’。






























原创粉丝点击