Asop 之 Binder 机制

1. Binder 简介

Android 内核也是基于 Linux 内核,先解释 Linux 中一个重要机制:IPC 。IPC 是 Inter-Process Communication 的缩写,含义即是跨进程通信。Binder 即是 Android 中最重要的 IP C机制,用一下官网上的图片,先来看下 Android 的整体架构:

Asop 之 Binder 机制

可以看出 Binder IPC 是 Android application 层与系统服务之间的桥梁。在 Linux 世界里跨进程通信的方式还有很多,常见的如:socket,管道,消息队列,信号量,共享内存。

Android 为什么选用 Binder 作为最重要的 IPC 机制?下面分别分析几种跨进程通信的优缺点:

  • Socket

Socket 是一种比较通用的通信方式,同一个进程(虽然可以,很少有人这么干),不同进程(zygote,Property Service),不同主机之间都可以使用 Socket,基于标准的 tcp 协议它的好处是到处通用。但 Socket 通信是一种比较复杂的通信方式,通常服务端需要开启单独的监听线程来监听从客户端发过来的数据,客户端线程发送数据给服务端,如果需要等待服务端的响应,并通过监听线程接受数据,需要进行同步,以及线程池的管理,Socket 资源的管理等,是一件很麻烦的事情。在同一个主机多进程场景来看 Socket 通信速度也不快。Socket 更多的是用于不同机器或跨网络的通信。


  • 管道

是 UNIX 系统 IPC 最古老的形式。在创建时分配一个 page 大小的内存,缓存区大小比较有限。它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。速度比较慢。


  • 消息队列

容量受到系统限制,且要第一次读的时候,要考虑上一次没有读完数据的问题。


  • 信号量

不能传递复杂消息,只能用来同步,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据,若要在进程间传递数据需要结合共享内存。

  • 共享内存

指两个或多个进程共享一个给定的存储区。能够容易控制容量,速度非常快,但要保持同步,比如写一个进程的时候,另一个进程要注意读写的问题,相当于线程中的线程安全。通常结合信号量一起使用,用来同步对共享内存的访问。

Binder 是 Android 中的一种跨进程通信方式。Binder 还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,这种通信方式在 Linux 中没有。

Binder 主要提供以下功能:

1、用驱动程序来推进进程间的通信方式 2、通过共享内存来提高性能 3、为进程请求分配每个进程的线程池,每个进程默认启动两个 Binder 服务线程 4、针对系统中的对象引入技术和跨进程对象的引用映射 5、进程间同步调用

从安全角度来看:

传统 Linux IPC 的接收方无法获得对方进程的 UID,从而无法鉴别对方身份,这对 Android 这样一个开放的操作系统来说非常致命,可能有人会说传统 IPC 可以在数据包里填入 UID,这样确实可行,但是可靠的身份标记只有由 IPC 机制本身在内核中添加。其次传统 IPC 访问接入点是开放的,无法建立私有通道。从安全角度,Binder 的安全性更高。

Android 为每个安装好的应用程序分配了自己的 UID,进程的 UID 是鉴别进程身份的重要标志,Android 系统中对外只暴露 Client 端,Client 端将任务发送给 Server 端,Server 端会根据权限控制策略,判断 UID 是否满足访问权限,从而做权限的控制。

2. 简述 Android Binder 架构

Binder 在 Android 系统中无处不在,地位非常高。App 进程的任何一处向 Service 的调用都跟 Binder 有关,在 Zygote 孵化出 system_server 进程后,在 system_server 进程中出初始化支持整个 Android framework 的各种各样的 Service,而这些 Service 从大的方向来划分,分为 Java 层 Framework 和 Native Framework 层 (C++) 的 Service,几乎都是基于 Binder IPC 机制。

Java framework:作为 Server 端继承(或间接继承)于 Binder 类,Client 端继承(或间接继承)于 BinderProxy 类。例如 ActivityManagerService(用于控制 Activity、Service、进程等)这个服务作为 Server 端,间接继承 Binder 类,而相应的 ActivityManager 作为 Client 端,间接继承于 BinderProxy 类。当然还有 PackageManagerService、WindowManagerService 等等很多系统服务都是采用 C/S 架构。

Native Framework 层:这是 C++ 层,作为 Server 端继承(或间接继承)于 BBinder 类,Client 端继承(或间接继承)于 BpBinder。例如 MediaPlayService(用于多媒体相关)作为 Server 端,继承于 BBinder 类,而相应的 MediaPlay 作为 Client 端,间接继承于 BpBinder 类。

Binder 使用面向对象的方式设计,进行一次远程过程调用就好像直接调用本地对象一样,异常方便。每一个系统服务在应用框架层都有一个 Manager 与之对应,方便开发者调用其相关功能,具体关系如下:

Asop 之 Binder 机制

3. IPC 原理

从进程的角度来看 IPC 机制:

每个 Android 进程,只能运行在自己的进程所拥有的虚拟地址空间,如果是32位的系统,对应一个 4GB 的虚拟地址空间,其中 3GB 是用户空,1GB 是内核空间,而内核空间的大小是可以通过参数配置的。对于用户空间,不同进程之间彼此是不能共享的,而内核空间是可以共享的。Client 进程与 Server 进程通信,恰恰是利用进程间可共享的内核内空间来完成底层通信工作的,Client 端与 Server 端进程往往采用 ioctl 等系统调用跟内核空间的驱动进行通信。Binder 核心被实现成一个 Linux 驱动程序,并运行于内核态。这样它才能具有强大的跨进程访问能力。 

4. Binder 通信机制

a) Binder 的流程

Android 内部采用 C/S 架构。而 Binder 通信也是采用 C/S 架构。Binder 的具体流程如下:

  • 服务的提供者 Server 先向 Service Manager 注册自己的服务

  • 服务的使用者 Client 需要使用服务时向 Service Manager 申请服务

  • Client 申请成功后,即可以使用服务

在上面这个过程中 Service Manager 充当一个中介的角色,是整个 Binder 通信机制的大管家,管理着所有的服务,同时响应 Client 的请求并为之分配相应的服务(Proxy),通常情况 Client 向 Server 的调用是通过 Binder 机制跨进程调用,这里注意 Client,Server,Service Manager 这三者可能分别位于不同的进程,也就意味着 Client 和 Server 调用 Service Manager 也可能是跨进程的,这个过程也是 Binder 机制。注意这里的 Service Manager 是指 Native 层的 Service Manager(C++),并非是 framework 层的 Service Manager(Java)。b) Binder 通信整体框架Client 和 Server 存在于用户空间,Client 和 Server 实现通信是由 Binder 在内核空间实现的:

Asop 之 Binder 机制

Service Manager 和 Binder 驱动属于 Android 平台层,Client 和 Server 属于 Android 的应用层,开发者只需要自定义实现 Client 和 Server 端,借助于 Android 的平台架构就可以进行 IPC 通信。

Client 部分用户代码最终会调用 Binder Driver 的 transact 接口,Binder Driver 会调用 Server 的 onTransact 方法:

Asop 之 Binder 机制

Client 部分在实际的代码实现过程中一般不会直接调用 transact 方法(当然直接调用也是可以的),而是会借助 AIDL 自动生成接口类,同样 Server 端通常也不会直接实现 onTransact 方法,也是借助 AIDL 自动生成 Stub 接口类,Server 只需要继承这个 Stub 类实现其中的 AIDL 接口方法。关于 AIDL 全称是 Android Interface Definition Language,也就是 Android 接口定义语言。它是一种语言,设计这门语言的目的是为了实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。这门语言非常的简单,基本上它的语法和 Java 是一样的。 借助开发者工具集,帮我们简化了 transact 底层的序列化以及反序列化相关的一系列繁琐易错的操作,使开发者可以将精力全部集中在业务功能的开发上(AIDL 接口的实现上)。

5. Binder 内核通信协议

Biner 协议格式基本是"命令+数据",使用 ioctl (fd,cmd,arg) 系统调用函数实现与内核的交互。命令由参数 cmd 承载,数据由参数 arg,随着 cmd 不同而不同。

关于 cmd 这里只简单说一下最常用的 BINDER_WRITE_READ 命令。该命令向 Binder 写入或读取数据。参数分为两段:写部分和读部分。如果 write_size 不为0,就将 write_buffer 里的数据写入 Binder;如果 read_ size 不为0再从 Binder 中读取数据存入 read_buffer 中。write_consumered 和 read_consumered 表示操作完成时 Binder 驱动实际写入或者读出数据的个数。 

cmd 除了常用的 BINDER_WRITE_READ 还有:

BINDER_SET_MAX_THREADS 告知 Binder 驱动接收方(通常是 Server 端)线程池中最大的线程数;

BINDER_SET_CONTEXT_MGR 当前进程注册为 SM;

BINDER_TREAD_EXIT 通知 Binder 驱动当前线程退出了;

BINDER_VERSION 获取 Binder 驱动的版本号。


上一篇:阿里P7深入Binder原理讲解,真香


下一篇:阿里P7大牛亲自讲解!Android开发应该了解的Binder原理,吐血整理