前言
本文基于 Android S。
Binder 是什么
Android 设计了一个轻量级的进程间通信机制,也称 远程调用机制,Binder 是这个机制中的 远程对象 的基础类。
即,Binder 对象实现了一些接口,供远程进程调用。
为了被远程进程调用,它必须遵循某种定义好的协议,这个协议为 IBinder。
同时,为了使远程进程有统一调用其方法的方式,Android 规定它实现的接口必须继承自 IInterface。
google 官网 - Binder:Base class for a remotable object, the core part of a lightweight remote procedure call mechanism defined by IBinder.
其核心是如何被远程进程调用,即,其遵循的由 IBinder 接口描述的协议。这个协议由 RPC 协议衍变而来。
RPC 协议
一个通用的 RPC 框架实现如下:
其重点是:代理模式 和 传输。
代理模式指在 Client 使用 Server 端接口对象的代理。这样 Client 端在调用接口时,无需关注 RPC 实现,和调用本地对象的方法一样使用就可以。
传输即 RPC 协议定义的东西。为了使两端进程能理解对方的 方法/参数,我们需要定义 序列化/反序列化 及 打包/解包 的标准。就 Binder 而言,因为中间涉及到 Java 代码和 C++ 代码的转换,所以我们在传输之前必须把 参数/返回值类型 序列化成 JNI 可以理解的基础类型。
Android 中封装了一个 Parcel 类来负责 序列化/反序列化 及 打包/解包。
通常 RPC 框架里面还会涉及到一个服务中心,所有意图公开给所有用户使用的服务都在里面注册,然后想使用这个服务的客户端从中取出服务来使用。
Binder 架构中的 ServiceManager#addService/ServiceManager#getService 采用的就是这个设计。
线程迁移
线程迁移指进程间的 IPC 看起来像是,Client 端的线程跳到 Server 端执行代码,再带着结果跳回来。
但实际上,Binder IPC 机制,并不是使用线程迁移来实现,而是采用一种模拟线程迁移的方式。
Binder 系统的用户空间代码在它运行的每个进程中维护了一个线程池,这个线程池中的线程用来处理来自其它进程的 IPC。内核模块通过以下手段模拟线程迁移:在分派 IPC 时跨进程传递线程优先级,并确保如果 IPC 递归回原始进程,由其原始线程处理。
Binder IPC 机制中的 RPC 架构
先看下总的流程图:
我们可以看到 Client 端调用 Binder 代理的方法,方法和参数最后被封装后传递给了 Server 端,Server 端再把方法和参数解析出来,调用真正的 Binder 对象执行方法,再把结果返回给 Client 端。
其中一次 RPC 的来回在 Android 的 Binder 机制中被称为 transaction(事务)。
其中的核心是如何把封装好的数据从 Client 端传到 Server 端。
Binder 机制中的数据传输
Binder 机制中的数据传输,是通过内存映射来实现的。这个内存映射机制由位于内核中的 Binder 驱动实现。
可以看到映射的重点为 Server 进程中的 binder_mmap() 方法。
binder_mmap() 方法对应的代码在 binder.c 中。
其主要流程如图:
Binder 的服务中心
上面的数据传输中有个问题,即,Client 进程如何知道 Server 进程 binder_proc->buffer 所指向的物理地址空间?
答案是,通过一个服务中心:ServiceManager 进程实现。
当愿意公开自己的 Server 通过 Binder 驱动向服务中心 ServiceManager 注册自己时,Binder 驱动会做以下事情:
- 为这个 Server 创建其对应的 binder_node,binder_node 中包含了 binder_proc 对象;
- 创建与 binder_node 对应的 binder_ref,并向服务中心注册 [服务名称,binder_ref 对象]。
当 Client 需要获取 Server 时,只需要使用 服务名称 通过 Binder 驱动向服务中心查找该服务名称对应的 binder_ref 对象即可。服务中心会通过 Binder 驱动把 Server 对应的 binder_ref 返回给 Client 进程。
上面过程有个问题,即,Binder 驱动是如何知道发送给它的请求是需要转交给 ServiceManager 进程的呢?
答,开机时,ServiceManager 会向 Binder 驱动设置自己为上下文管理者。其它进程只要把命令发送给上下文管理者就可以了。
创建 ServiceManager 的 BBinder,并设置为 Binder 驱动的上下文管理者
BBinder: Server 进程中,native 代码中的 本地(local) Binder;
BpBinder: Client 进程中,native 代码中的 远端(remote) Binder,即 Binder 代理;
IInterfaceImpl.Stub: Server 进程中,Java 代码中的本地 Binder;
IInterfaceImpl.Stub.Proxy: Client 进程中,Java 代码中的 Binder 代理。
开机时,ServiceManager 会向 Binder 驱动设置自己为上下文管理者,并把自己加入到 ServiceManager 的 [name, BBinder] Map 中。
在 Java 代码中获取 ServiceManager(获取 BpBinder 在 Java 层的对象)
具体流程如下:
公共 Server 向 ServiceManager 注册自己
简单概括为:
- Server 进程新建一个要公开的 Binder,Java 层为 Stub 对象,native 层为 JavaBBinder 对象;
- 把 Binder 对象放入 Parcel 中;
- 调用 ServiceManager 的 Binder 代理(BpBinder)的 addService() 方法;
- 通过 Binder 驱动把请求转发给 ServiceManager 的本地 Binder(ServiceManager 的 BBinder);
- 从 Parcel 中解析出封装的要做为公开 Server 的 BBinder 对象,并创建其 BpBinder;
- 并加入到 ServiceManager 的 map 中。
具体流程如下:
Client 向 ServiceManager 获取一个公共 Server
其主要过程和添加 Server 大致一致,唯一不一样的是,transact 后会返回从 Parcel reply 中读取出来的 BinderProxy:
其具体代码流程为:
取出公开的 Server 后,调用其 BinderProxy 接口完成一个普通的 RPC 流程的例子
匿名 Server 的获取
一个匿名 Binder Server 与实名 Server 的差异主要就在于后者是通过 Service Manager 来获取对它的引用;而前者则是以其它实名 Server 为中介来传递这一引用信息,仅此而已。另外,对于 Binder 驱动而言,只要 “路过” 它且以前没有出现过的 Binder 对象,都会被记录下来。
—— 林学森《深入理解 Android 内核设计思想》
我们以 bindService 为例:
- 客户端应用调用 IActivityManager#bindService 时传入 IServiceConnection;
- IActivityManager 在做 RPC 时就创建了 IServiceConnection 的本地 Binder 和其远程代理 Binder;
- IActivityManager 的本地 Binder 实现者 AMS 在执行 bindService 时收到了 IServiceConnection 的 Binder 代理 BinderProxy,并通过 asInterface 接口把 BinderProxy 转换成 IServiceConnection 接口即 IServiceConnection.Stub.Proxy;
- AMS 启动 Service 成功后,调用 IServiceConnection#connected 把 Service.Stub 放入 Parcel;
- RPC 过程中会为 Service 创建本地 Binder 和 远程 Binder 代理;
- 客户端应用的 IServiceConnection 本地 Binder 在执行 connected() 方法时可以获得 Service Binder 代理 IInterfaceImpl.Stub.Proxy;
- 客户端应用通过 IInterfaceImpl.Stub.Proxy 调用 Service 的方法。
总结
Binder 是 Android 实现的远程过程调用机制中的远程对象,它提供一系列接口给远程进程使用。Server 端实现接口,Client 端调用接口。Server 端创建本地 Binder 并通过 Binder 驱动把 Binder 代理提供给 Client 端。Client 端在调用接口时把封装好的 方法名、参数、返回值、RPC 类型 放入 Server 进程的内核物理空间,因为该物理空间同时被映射到了 Server 进程的用户虚拟地址空间,所以 Server 进程的专为 RPC 调用而创建的线程可以直接从内核物理空间取出封装好的数据。数据取出后,Server 进程的本地 Binder 将数据解包,并执行其实现方法。
参考链接:
OpenBinder 官网之 Binder IPC 机制
google 官网:Binder
林学森《深入理解 Android 内核设计思想》
skywangkw:Android Binder机制(三) ServiceManager守护进程
skywangkw:Android Binder机制(一) Binder的设计和框架
小林coding:内存管理
《Linux 是怎样工作的》
gityuan:彻底理解 Android Binder 通信架构
gityuan:Binder 系列10—总结
原创文章,欢迎转载,但请注明出处。