Android Binder 机制
- Binder 是 Android 系统的一种跨进程通信机制(IPC)。AIDL 和 Messenger 也是基于 Binder 实现的;
- 对于 Server,Binder 指的是 Binder 本地对象;
- 对于 Client,Binder 指的是 Binder 代理对象,它只是 Binder 本地对象的一个远程代理;对这个 Binder 代理对象的操作,会通过驱动最终转发到 Binder 本地对象上去完成;
- 对于传输过程而言,Binder 是可以进行跨进程传递的对象;Binder 驱动会对具有跨进程传递能力的对象做特殊处理,即自动完成代理对象和本地对象的转换。
Linux 系统调用:
- copy_from_user() //将数据从用户空间拷贝到内核空间
- copy_to_user() //将数据从内核空间拷贝到用户空间
Binder 架构
Binder 在整个 Android 系统中有这举足轻重的地位,在 Native 层和 Java 层都有一套基于 C/S 的架构。
Binder 线程池:每个 Server 进程在启动时会创建一个 binder 线程池,并向其中注册一个 Binder 线程;之后 Server 进程也可以向 binder 线程池注册新的线程,或者 Binder 驱动在探测到没有空闲 binder 线程时会主动向 Server 进程注册新的的 binder 线程。对于一个 Server 进程有一个最大 Binder 线程数限制,默认为 16,例如 Android 的 system_server 进程就存在 16 个线程。对于所有 Client 端进程的 binder 请求都是交由 Server 端进程的 binder 线程来处理的。
Binder 实现原理
依靠 Linux 的动态内核可加载模块(Loadable Kernel Module,LKM)和内存映射。
- LKM: Android 系统通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。这个内核模块就叫 Binder 驱动。
- 内存映射:通过 mmap() 系统调用实现。mmap() 通常是用在有物理介质的文件系统上的,而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。
一次完整的 Binder IPC 通信过程如下:
- Binder 驱动在内核空间创建一个数据接收缓存区;
- 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
- 发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
Binder 通信模型
Binder 是基于 C/S 架构的。由一系列的组件组成,包括 Client、Server、ServiceManager、Binder 驱动。其中 Client、Server、ServiceManager 运行在用户空间,Binder 驱动运行在内核空间。其中 ServiceManager 和 Binder 驱动由系统提供,而 Client 和 Server 由应用程序来实现。
Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 系统调用来访问 /dev/binder,从而实现与 Binder 驱动的交互来间接的实现跨进程通信。
- Client、Server 和 ServiceManager 实现在用户空间中,Binder 驱动程序实现在内核空间;
- Binder 驱动程序和 ServiceManager 在 Android 平台中已经实现,开发者只需要在用户空间实现自己的 Client 和 Server;
- Binder 驱动负责进程之间 Binder 通信的建立,Binder 在进程之间的传递,Binder 引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。
- ServiceManager 和 DNS 类似,作用是
将字符形式的 Binder 名字转化成 Client 中对该 Binder 的引用,使得 Client 能够通过 Binder 的名字获得对 Binder 实体的引用。
注册了名字的 Binder 叫实名 Binder。
Server 创建了 Binder,并为它起一个字符形式,将这个 Binder 实体连同名字一起以数据包的形式通过 Binder 驱动发送给 ServiceManager,通知 ServiceManager 注册一个具体名称的 Binder,它位于某个 Server 中。
驱动为这个穿越进程边界的 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager。ServiceManger 收到数据后从中取出名字和引用填入查找表。
ServiceManager 提供的 Binder 比较特殊,它没有名字也不需要注册。当一个进程使用BINDER_SET_CONTEXT_MGR
命令将自己注册成 ServiceManager 时,Binder 驱动会自动为它创建 Binder 实体。这个 Binder 实体的引用在所有 Client 中都固定为 0 而无需通过其它手段获得。
一个 Server 想要向 ServiceManager 注册自己的 Binder 就必须通过这个 0 号引用和 ServiceManager 的 Binder 通信。
大致通信过程如下:
- 一个进程使用
BINDER_SET_CONTEXT_MGR
命令通过 Binder 驱动将自己注册成为 ServiceManager; - Server 通过驱动向 ServiceManager 中注册 Binder,表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager 并填入查找表;
- Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。
ServiceManager 是如何注册 Binder 的?
- 当 Android 系统启动后,会创建一个名称为 servicemanager 的进程,这个进程通过一个约定的命令 BINDER_SET_CONTEXT_MGR 向 Binder 驱动注册,申请成为 ServiceManager,Binder 驱动会自动为 ServiceManager 创建一个 Binder 实体;
- 并且这个 Binder 实体的引用在所有的 Client 中都为 0,也就说各个 Client 通过这个 0 号引用就可以和 ServiceManager 进行通信。Server 通过 0 号引用向 ServiceManager 进行注册,Client 通过 0 号引用就可以获取到要通信的 Server 的 Binder 引用。
- Android Binder 框架分为客户端、服务端以及 Binder 驱动。
- 客户端通过 Binder 驱动获取到
服务端的代理对象
,调用代理对象的方法,会间接调用transact()
发送消息至服务端。 服务端是 Binder 类的对象
。该对象一旦创建,内部会启动一个隐藏线程来接收 Binder 驱动发送的消息。收到消息后,会执行Binder#onTransact()
函数,并按照该函数的参数执行不同的服务端端代码。- Binder 驱动,该对象也为 Binder 类的实例,客户端通过该对象访问远程服务。
Binder 通信中的代理模式:
A 进程想要 B 进程中某个对象是如何实现的呢?毕竟它们分属不同的进程,A 进程没法直接使用 B 进程中的 对象。
前面我们介绍过跨进程通信的过程都有 Binder 驱动的参与,因此在数据流经 Binder 驱动的时候驱动会对数据做一层转换。当 A 进程想要获取 B 进程中的 object 时,驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一摸一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。
当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了。
BINDER_WRITE_READ 之写操作
Binder 请求和应答数据是通过BC_TRANSACTION/BC_REPLY
命令实现的。这对命令所承载的数据包由结构体 struct binder_transaction_data
定义。Binder 交互有同步和异步之分,利用 binder_transaction_data 中 flag 域区分。如果 flag 域的TF_ONE_WAY位为 1 则为异步交互
,即 Client 端发送完请求交互即结束, Server 端不再返回 BC_REPLY 数据包;否则 Server 会返回 BC_REPLY 数据包,Client 端必须等待接收完该数据包方才完成一次交互。
BINDER_WRITE_READ:从 Binder 读出数据
参考
[1] Android Bander 设计与实现 - 设计篇
[2] 关于 Binder,作为应用开发者你需要知道的全部
[3] 写给 Android 应用工程师的 Binder 原理剖析
[4] Binder 系列 10—总结
[5] Android Binder - 魅族内核团队