瘟疫青年

Binder的设计架构

Word count: 2.7kReading time: 9 min
2018/05/29 Share

一、为什么选择Binder

android 是基于Linux开发的移动端操作系统,而传统的Linux已有的IPC机制,包括管道,消息队列、共享内存、信号量、socket为什么不适合安卓,而非要采用Binder?

1.拷贝次数

Socket

Linux中除了Binder之外唯一一个C/S架构的IPC,为了兼容本地操作系统中的进程间通讯和互联网中的远程主机之间的通讯,Socket采用的是更加通用型的架构,一般的socket通讯流程如下

  1. 客户端通过 int socket(int domain, int type, int protocol); 函数创建了一个socket,返回了该套接字的文件描述符
  2. 通过bind函数 绑定了该socket的端口号,监听该端口
  3. 循环检查该端口是否有消息,如果有消息则fork出子进程去处理
  4. 使用read()读取客户端发送的消息数据,通过write()处理消息并发送给客户端
  5. 客户端也要创建socket
  6. 客户端连接到制定IP和端口的服务端socket
  7. 使用write向服务端写数据,用read 从服务端度数据
    可以看到,一次标准的socket通讯步骤繁琐,计算机内的进程间通讯,如果采用socket,不可避免的会先write()将文件写到内核缓冲区,再read() 将文件读取到用户缓冲区,涉及两次拷贝。如果是网络传输,一次通讯过程可能会涉及三到四次拷贝,多次切换上下文。

    匿名管道

  8. 半双工,数据流动方向是单向的,读写端都是固定的
  9. 只能用于父子进程间的通讯
  10. 属于一种特殊的文件,只存在内存中
  11. 数据传递方式仍然采用read 和 write ,先从发送方缓冲区拷贝到内核,再从内核缓冲区拷贝到接收方
    父子进程通过管道实现数据交互
  • 拷贝次数 两次

命名管道

可以在无关进程之间交流数据,它有些类似用文件的方式在进城之间传递数据,但该文件又同时具有管道的特点,它允许所有知道其FIFO接口的进程参与读写,遵循先入先出的原则。

  • 拷贝次数 2次拷贝

消息队列

  1. 面向记录,消息队列中的消息具有特定格式和优先级
  2. 独立于进程,就算进程被杀死,队列中的消息也不会被清除
  3. 可以实现消息的独立查询
  4. 消息队列同样需要两次拷贝,进程A 拷贝消息到消息队列,消息队列从队列中取消息 将其拷贝到进程B

    共享内存

    指的是两个或以上进程共享的一块内存区域
  5. 速度最快 因为是直接读取,不涉及拷贝
  6. 多个进程需要同时读取,所以需要同步机制,一般配合信号量使用

Binder

Binder是Android系统中广泛采用的C/S架构IPC通讯方式,它只需要一次内存拷贝。
Binder的一次通讯过程:
通过 BinderProxy 将我们的请求参数发送给 ServiceManager,通过共享内存的方式使用内核方法 copy_from_user() 将我们的参数先拷贝到内核空间,这时我们的客户端进入等待状态,然后 Binder 驱动向服务端的 todo 队列里面插入一条事务,执行完之后把执行结果通过 copy_to_user()将内核的结果拷贝到用户空间(这里只是执行了拷贝命令,并没有拷贝数据,binder只进行一次拷贝),唤醒等待的客户端并把结果响应回来,这样就完成了一次通讯。

关于消息传递效率 参考下表

IPC 特征 拷贝次数 特点
共享内存 速度快,但是控制复杂,要解决同步问题 0 极少用
Binder 简单 通用 安全 1 Android常用、性能高
Socket A缓冲区->内核缓冲区->B缓冲区 2 Socket作为通用接口,开销大,传输性能低,一般用作跨网络或者进程间的低速IPC

2.安全性

Android作为一个开放式,拥有众多开发者的的平台,应用程序的来源广泛,确保智能终端的安全是非常重要的。终端用户不希望从网上下载的程序在不知情的情况下偷窥隐私数据,连接无线网络,长期操作底层设备导致电池很快耗尽等等。传统IPC没有任何安全措施,完全依赖上层协议来确保。

  • 传统IPC的接收方无法获得对方进程可靠的UID/PID(用户ID/进程ID),从而无法鉴别对方身份。Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志。
  • 使用传统IPC只能由用户在数据包里填入UID/PID,但这样不可靠,容易被恶意程序利用。可靠的身份标记只有由IPC机制本身在内核中添加。其次传统IPC访问接入点是开放的,无法建立私有通道。比如命名管道的名称,system V的键值,socket的ip地址或文件名都是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。

基于以上原因,Android需要建立一套新的IPC机制来满足系统对通信方式,传输性能和安全性的要求,这就是Binder。Binder基于Client-Server通信模式,传输过程只需一次拷贝,为发送发添加UID/PID身份,既支持实名Binder也支持匿名Binder,安全性高。

二、 Binder 的面向对象思想

Binder可以看做是server端提供某个功能的访问接入点,client通过访问该接入点获得server的服务,但和其他ipc不同的是,binder采用一种面向对象的思想来设计该入口。

Binder的实体是位于server中的对象,它实现了一系列方法用以访问server的服务,有点类似于类中的成员函数,而client中拿到的其实是该binder对象的引用

client —> binder引用—->Binder—->server

面向对象思想的引入将进程间通信转化为通过对某个Binder对象的引用调用该对象的方法,而其独特之处在于Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,而它的引用却遍布于系统的各个进程之中。这个引用和java里引用一样既可以是强类型,也可以是弱类型,而且可以从一个进程传给其它进程,让大家都能访问同一Server,就象将一个对象或引用赋值给另一个引用一样。Binder模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中。形形色色的Binder对象以及星罗棋布的引用仿佛粘接各个应用程序的胶水,这也是Binder在英文里的原意。

当然面向对象只是针对应用程序而言,对于Binder驱动和内核其它模块一样使用C语言实现,没有类和对象的概念。Binder驱动为面向对象的进程间通信提供底层支持。

三、Binder的通讯模型

一个完整的Binder驱动包含四个角色

Server、Client、ServiceManager、Binder驱动。

server client smg 运行在用户空间,Binder驱动运行在内和空间。

1.Binder驱动

实现方式类似硬件驱动,工作在内核态

1
2
3
4
5
6
7
8
9
static const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};

这是binder驱动的file_operation结构体,可以看到binder驱动为应用层提供了open(),mmap(),poll(),ioctl()等标准的文件操作【注1】,open()负责打开驱动,mmap()负责对binder做内核空间向用户空间的地址映射,ioctl()负责binder协议的通信。
Binder驱动负责在内核中创建对应的Binder实体节点,为client或者server进程创建对该实体节点的引用,负责进程之间Binder通信的建立,Binder在进程之间传递,Binder引用计数管理,数据包在进程之间的传递等一系列操作的底层支持,驱动和应用程序之间定义了一套标准的接口协议,主要功能由ioctl()【注2】来实现,不提供read(),write接口。(因为ioctl是更为高灵活的操作文件的接口,自动遵循先写后读的顺序,可以满足同步请求)

注释1:设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:
1、对设备初始化和释放;
2、把数据从内核传送到硬件和从硬件读取数据;
3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据;
4、检测和处理设备出现的错误。

注释2: ioctl()函数是驱动中对设备的I/O通道进行管理的函数,ioctl函数里面都实现了多个的对硬件的操作步骤,将他们综合起来完成一个任务,而不是单一的某个write/read操作,通过应用层传入的命令来调用相应的操作。

ServiceManager

ServiceManger有点类似于路由器,使得client端可以通过字符串形式的binder名来获取server端binder的引用,它提供注册和查询服务。server端像smg注册服务,client从smg获取服务。

server创建了一个binder实体,为其取一个容易记住的名字,然后将这个binder实体 连同名字通过binder驱动发送给smg,通知smg注册一个服务,以及该服务的名字,binder驱动会为这个穿过进程边际的binder创建一个位于内核的实体节点,以及smg对该实体节点的引用,然后将名字以及新建的引用打包发送给serviceManager,smg会在它的svcinfo表中添加该信息。该svcinfo便是服务查询表。

CATALOG
  1. 1. 一、为什么选择Binder
    1. 1.1. 1.拷贝次数
      1. 1.1.1. Socket
      2. 1.1.2. 匿名管道
      3. 1.1.3. 命名管道
      4. 1.1.4. 消息队列
      5. 1.1.5. 共享内存
      6. 1.1.6. Binder
    2. 1.2. 2.安全性
  • 二、 Binder 的面向对象思想
  • 三、Binder的通讯模型
    1. 1. 1.Binder驱动
    2. 2. ServiceManager