Android IPC - Binder 学习总结

转自Android IPC - Binder 学习总结

最近想学些Android Framework中的东西,必经之路就是binder,不夸张的说,binder是整个android架构 的基础。本文从Binder的基本概念和框架入手,讲述binder机制的点点滴滴。全文分为以下四部分:1、Android为何设计binder进行进程间通信。2、Binder的框架结构和概述。3、Client和Server通信过程。4、binder总结。

PS,本文绝大部分内容来自网络,是我自己学习binder的一篇总结,而且本文只是binder的入门,不涉及到过多底层的描述,要想更多了解binder的内容,还请移步到引用文献中老罗和邓凡平老师的文章。

Android IPC 为何设计Binder

Android 系统中,为了向应用开发者提供丰富多样的功能,这种通信方式更是无处不在,诸如媒体播放,音频视频捕获,通知,到各种让手机更智能的传感器(加速度,方位,温度,光亮度等)都由不同的Server负责管理,应用程序只需做为Client与这些Server建立连接便可以使用这些服务,花很少的时间和精力就能开发出令人眩目的功能。

设计Binder的原因主要有以下3个:

  • Linux支持CS通信模式的IPC少 目前linux支持的IPC包括传统的管道,System V IPC,即消息队列/共享内存/信号量,以及socket,其中只有socket支持Client-Server的通信方式。当然也可以在这些底层机制上架设一套协议来实现Client-Server通信,但这样增加了系统的复杂性,在嵌入式这种条件复杂,资源稀缺的系统中可靠性也难以保证。
  • 传输性能低 socket作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。
  • 安全性不高 Android作为一个开放式,拥有众多开发者的的平台,应用程序的来源广泛,确保智能终端的安全是非常重要的。终端用户不希望从网上下载的程序在不知情的情况下偷窥隐私数据,连接无线网络,长期操作底层设备导致电池很快耗尽等等。传统IPC没有任何安全措施,完全依赖上层协议来确保。首先传统IPC的接收方无法获得对方进程可靠的UID/PID(用户ID/进程ID),从而无法鉴别对方身份。

Binder总体概述

Binder框架及概述

  • Binder是Android中进程间通信最常见的方式,采用Client-Server的通信模型。
  • Client和Server之间的通信通过Binder驱动间接实现。
  • Service Manager是一个守护进程,用来管理Server,并向Client提供查询Server接口的能力。


    Android IPC - Binder 学习总结_第1张图片
    binder框架

四大角色

  • Binder Driver:Binder驱动,跑在内核空间,由framework实现。
  • Service Manager:Services管理员,跑在用户空间,由framework实现。
  • Server: 服务端,跑在用户空间,framework实现了很多系统级的server,也可以在application中实现自己的server。
  • Client:客户端,跑在用户空间,一般在application中实现。

通讯模型

这四个角色的关系和互联网类似:Server是服务器,Client是客户终端,ServiceManager是域名服务器(DNS),Binder驱动是路由器。

(1)客户端通过某种方式得到服务器端的代理对象。代理对象和他的本地对象没有什么差别,它可以像其他本地对象一样调用其方法,访问其变量。
(2)客户端通过调用代理对象的方法向服务器端发送请求信息。
(3)代理对象通过binder设备节点(/dev/binder),把用户请求信息发送到Linux内核空间(内存共享),由Binder驱动获取并发送到服务进程。
(4)服务进程处理用户请求,并通过Linux内核的Binder驱动返回处理结果给客户端的代理对象。
(5)客户端收到服务端的返回结果。

Android IPC - Binder 学习总结_第2张图片

四大角色的分工

  • Binder Driver:Binder驱动虽然默默无闻,却是通信的核心。
    尽管名叫‘驱动’,实际上和硬件设备没有任何关系,只是实现方式和设备驱动程序是一样的。驱动和应用程序之间定义了一套接口协议,主要功能由open()、ioctl()接口实现。
  • ServiceManager:相当于DNS,为服务端的地址取一个好记的名字,便于客户端访问。
    有一个有趣的事情,ServiceManager是一个进程,Server在另一个进程,Server向ServiceManager注册一个好记的名字必然会涉及进程间通信。当前实现的是进程间通信却又要用到进程间通信,这就好象蛋可以孵出鸡,前提却是要找只鸡来孵蛋。Binder的实现比较巧妙:预先创造一只‘0号鸡’来孵蛋:ServiceManager和其它进程同样采用Binder通信,这里ServiceManager是Server端,有自己的Binder对象(实体),其它Server和Client进程在这里都是Client,需要通过这个Binder的引用来实现Binder的注册,查询和获取。ServiceManager提供的Binder比较特殊,它没有名字也不需要注册,这个Binder的引用在所有Client中都固定为0而无须通过其它手段获得。类比网络通信,0号引用就好比域名服务器的地址,你必须预先手工或动态配置好。要注意这里说的Client是相对SServiceManager而言的,它本身可能是个提供服务的Server,但对ServiceManager来说它仍然是个Client。
  • Server:提供各种各样的服务
    Server利用保留的0号引用向ServiceManager注册了Binder实体及其名字,ServiceManager收到后,将‘张三’和Binder引用填入到一张查找表中。Client就可以通过名字获得该Binder的引用了。
  • Client:获取Server的服务
    Client也利用保留的0号引用向ServiceManager请求访问某个Binder:我申请获得名字叫‘张三’的Binder的引用。ServiceManager收到这个连接请求,从请求数据包里获得Binder的名字,在查找表里找到该名字对应的条目,从条目中取出Binder的引用,将该引用作为回复发送给发起请求的Client。
  • 代理对象:是指在客户端应用程序中获取生成的Server代理(proxy)类对象。
    从应用程序角度看代理对象和本地对象没有差别,都可以调用其方法,方法都是同步的,并且返回相应的结果。
  • 匿名Binder:并不是所有Binder都需要注册给ServiceManager广而告之的。
    Server端可以通过已经建立的Binder连接将创建的Binder实体传给Client,当然这条已经建立的Binder连接必须是通过实名Binder实现。由于这个Binder没有向ServiceManager注册名字,所以是个匿名Binder。Client将会收到这个匿名Binder的引用,通过这个引用向位于Server中的实体发送请求。匿名Binder为通信双方建立一条私密通道,只要Server没有把匿名Binder发给别的进程,别的进程就无法通过穷举或猜测等任何方式获得该Binder的引用,向该Binder发送请求。

从面向对象的角度,这个Binder对象现在有了两个引用:一个位于ServiceManager中,一个位于发起请求的Client中。如果接下来有更多的Client请求该Binder,系统中就会有更多的引用指向该Binder,就象java里一个对象存在多个引用一样。而且类似的这些指向Binder的引用是强类型,从而确保只要有引用Binder实体就不会被释放掉。

Client和Server的通讯过程

前戏

  • 获得ProcessState对象:一个进程会创建一个ProcessState单例对象,每个进程只能创建一个,作用是维护管理当前进程中所有服务代理对象(Service proxy)。具体代码:sp proc(ProcessState::self());
    在ProcessState中打开了一个binder设备文件,并返回一个fd(文件描述符),这个fd很重要,用来找到要通信的那个binder对象。
  • 获得ServiceManager的代理对象:ServiceManager是服务管家,它负责系统服务的管理。Client是通过这个代理对象和Service Manager通讯的。具体代码:sp sm = defaultServiceManager(); 这个对象的handle为0,也就是说和ServiceManager通信都使用handle为0的binder。
  • 真正传输的对象IPCThreadState:IPCThreadState也是一个singleton的类型,一个进程中也只能有一个这样的对象。它在android的binder机制中扮演什么样的角色呢?IPCThreadState主要负责信息的传递,所有要到达binder驱动和驱动中出来的信息,都要由IPCThreadState来传递。不管是Client进程和Service进程都是需要用IPCThreadState来和binder设备通讯。

Server初始化(MediaService为例)

Server初始化的代码如下:

//获得一个ProcessState实例
sp proc(ProcessState::self());
//得到一个ServiceManager的代理对象
sp sm = defaultServiceManager();
//初始化MediaPlayerService服务,并向服务管家成功添加了这些服务。
MediaPlayerService::instantiate();
//启动Process的线程池,进入循环状态,以便接收来自客户端的请求
ProcessState::self()->startThreadPool();
//将IPCThreadState对象加入到刚才的线程池
IPCThreadState::self()->joinThreadPool();

作为Service进程,当他完成初始化工作之后,需要进入循环状态等待客户端的请求,Service进程调用它的IPCThreadState对象的joinThreadPool方法,开始轮询binder设备,等待客户端请求的到来。

MediaService通信

Android IPC - Binder 学习总结_第3张图片

Binder总结

Android IPC - Binder 学习总结_第4张图片

如果一个服务需要通过binder机制对外提供跨进程的接口,需要做下面这些事情。
(1)第一步,需要为这个接口定义一个继承自IInterface的接口类,假设叫做IMyService。
(2)第二步,需要定义两个binder类,一个是代理类BpMyService,继承BpInterface;另一个是实现类BnMyService,继承BnInterface。
(3)第三步,定义BnMyService的子类,这个子类可以是任何名字,比如就叫MyService,在其中真正实现接口所提供的各个函数。
(4)第四步,创建MyService的实例,注册到服务管理器,也可以在其它接口的函数中创建。

这篇基本是Binder机制的入门介绍,以后有机会详细研究一下Binder机制。

引用资料

  1. Android Bander设计与实现 - 设计篇
  2. Android深入浅出之Binder机制
  3. 老罗的Android之旅
  4. Android学习笔记–Binder

你可能感兴趣的:(Android IPC - Binder 学习总结)