binder整体架构
使用binder进行跨进程通信/调用的基本架构如下图所示:
Server提供binder服务,Client通过binder远程访问Server提供的服务。对Client而言,从binder的用户角度看 client访问server提供的服务就如同client调用本地的API一样方便。
Client和server之间传送消息是通过binder驱动实现的,其中的大多数访问驱动是通过ioctl系统调用实现。
在实现方面,binder主要有几个部分:
- Binder驱动:实现在linux内核中,这部分实现真正的跨进程通信。驱动向上提供杂项设备/dev/binder、/dev/hwbinder、/dev/vndbinder等,用户层通过操作这几个设备实现对binder驱动的访问。
- libbinder动态库:所有基于binder开发应用程序使用的动态库,封装了对binder驱动的底层调用,使得用户可以不必关心如何和binder驱动交互。
- service_manager可执行程序:在android系统中,所有的binder服务都在一个统一的管理中心注册 并可被系统中的任意进程发现并访问。Service_manager便是这个管理中心。系统启动时,service_manager作为一个守护进程启动,并持续在系统中运行。
service manager介绍
service manager的实现分为两部分:
1. service_manager.c service manager的主体部分,单独进程运行,处理注册服务/获取服务/检查服务等命令
2. IserviceManager.cpp/IserviceManager.h service manager对外提供的proxy接口,使得其他进程和service manager的交互可以通过几个简单的API来实现。
Service manager创建了一个特殊的binder实体,它的编号是固定值0。其他所有的binder服务需要向0号binder服务去注册。当某个client想要访问某个binder服务时,也是先去0号binder服务去查询该目标服务是否存在。
在驱动中,所有binder实体均通过handle来索引。service manager是一个特殊的binder实体,其handle值为固定值0,因而其他任何进程都可以通过0号handle来访问service manager。
每个binder设备(如:/dev/binder)有一个context信息,该context内保存当前binder设备使用的context manager的binder实体。当service manager进程调用ioctl:BINDER_SET_CONTEXT_MGR时,将在该进程内部申请一个binder实体,并将该binder实体用作相应binder设备的context manager。
实现IPC
Binder协议
用户层操作binder驱动基本都是通过ioctl控制/dev/binder设备来实现的,binder协议是指用户层操作binder设备使用的ioctl命令。几个常见的binder协议如下:
|
|
BINDER_WRITE_READ | 读写操作,最常用的命令。IPC过程就是通过这个命令进行数据传递 |
BINDER_SET_MAX_THREADS | 设置进程支持的最大线程数量 |
BINDER_SET_CONTEXT_MGR | 设置自身为ServiceManager |
BINDER_THREAD_EXIT | 通知驱动Binder线程退出 |
BINDER_VERSION | 获取Binder驱动的版本号 |
Binder transact流程
Binder client里调用BpBinder::transact()来向binder服务发送消息。
BpBinder::transact()
|- IPCThreadState::transact() -- 此处将目标server改用handle来描述,handle仅在client进程内有意义
|- IPCThreadState::writeTransactionData(BC_TRANSACTION,...) -- 此处将目标server的坐标 以及待传数据Parcel的data指针 写入mOut
|- IPCThreadState::talkWithDriver(doReceive=true)
|- ioctl(“/dev/binder”, BINDER_WRITE_READ)
Driver处理:
binder_ioctl(BINDER_WRITE_READ)
|- binder_thread_write 解出命令 BC_TRANSACTION, 读出目标server的坐标 以及待传数据Parcel的data指针
|-- binder_transaction 根据handle找到目标进程的binder实体,执行一次拷贝动作,转换userspace传下来的objects(如 binder/fd等)
|-- binder_proc_transaction 将binder传输事务 发送给目标进程 并唤醒目标进程(当前线程进入睡眠,直到目标进程处理完成 或出错 返回)
|-- binder_thread_read 读取返回消息
Binder多线程
用户进程使用binder前会实例化一个ProcessState对象。
ProcessState::self()
|-- new ProcessState("/dev/binder")
|-- open_driver
|-- ioctl(“/dev/binder”, BINDER_SET_MAX_THREADS)
设置默认的最大binder线程个数。
用户也可以自行调用该ioctl来设置最大线程个数。
在需要时创建新的binder线程是通过ProcessState完成的。
ProcessState::spawnPooledThread()
|-- ProcessState::makeBinderThreadName() 新线程命名为Binder:%d_%X(%d是进程pid,%X是binder线程序号)
|-- new PoolThread()->run()
|-- (new thread) IPCThreadState::self()->joinThreadPool(mIsMain)
|-- while busy: getAndExecuteCommand
|-- talkWithDriver()
|-- executeCommand() 将handle转成BBinder,并调用BBinder->transact()
|-- exit if idle and not main
每个binder线程都有一个IPCThreadState对象,用来维护当前线程的一些binder相关的状态信息。如果不是主线程(调用ProcessState::startThreadPool()的线程),那么在处理完所有事务并且读取命令超时后 会自动结束线程。
创建线程的触发条件
当启动1个以上binder线程后,binder线程读取并处理binder client发来的消息。
IPCThreadState::getAndExecuteCommand()
|-- IPCThreadState::talkWithDriver()
|- ioctl(“/dev/binder”, BINDER_WRITE_READ)
(Driver处理)
binder_ioctl(BINDER_WRITE_READ)
|-- binder_ioctl_write_read
|-- binder_thread_read 读操作处理完成后,如果发现没有空余线程,并且当前已启动的线程个数少于最大线程数,那么返回给userspace一个 BR_SPAWN_LOOPER命令
(userspace处理)
IPCThreadState::executeCommand()
|-- BR_SPAWN_LOOPER: ProcessState::spawnPooledThread(false) 创建新的binder线程。
实现RPC
在底层binder IPC机制的基础上,binder在用户层整体基于代理模式实现了一套RPC(Remote Procedure Call)框架,来使得用户可以方便的实现RPC操作。
Binder框架
Binder在用户层的框架如上图所示。几个比较关键的类的作用如下:
类名 | 作用 | 来源 |
IBinder | Binder抽象接口 | 框架原有 |
BBinder | 本地binder对象 | 框架原有 |
BpBinder | 远端binder代理 | 框架原有 |
BnInterface | 本地服务的一些公共方法 | 框架原有 |
BpInterface | 远端服务代理的一些公共方法 | 框架原有 |
IINTERFACE | 用户自定义的服务接口 | 自动生成 |
BnINTERFACE | 一个中间层,能够通过类的继承,在调用onTransact()方法中调用用户在IMPLINTERFACE类中实现的服务方法 | 自动生成 |
BpINTERFACE | 一个代理类,通过binder架构的处理,client端调用此类中的方法,实际上会实现RPC,实际调用到IMPLINTERFACE中的对应方法 | 自动生成 |
IMPLINTERFACE | 用户实现的服务,即图中sayHello()的具体实现 | 用户编写 |
AIDL
AIDL全称是Android Interface Definition Language,即Android接口定义语言,是用于定义服务器和客户端通信接口的一种描述语言,可以拿来生成用于RPC的代码。
早期的AIDL主要应用于Java语言,用来协助Android Framework/APP开发者使用binder开发跨进程通信功能。
在Android 9.0版本中,AIDL实现了对C++语言的支持。用户可以编写AIDL文本,在编译期间,由aidl-cpp工具将AIDL源代码翻译成C++代码,自动为用户实现IINTERFACE、BpINTERFACE、BpINTERFACE几个部分的所有源码,大大提高了基于C++语言开发binder应用的效率。
AIDL语法格式与java语言非常相近,更多的信息可以参考AIDL官方介绍文档。
进程间传递binder
按照应用场景,传递binder的操作 可以分为如下几种:
- 查找service manager
- 向service manager查找service
- 传递匿名binder
在使用中,根据调用者和binder实体是否在同一进程中,binder在进程中的表示又可分为 本地binder 和 远程(代理)binder两种。
下面 以几个常用的场景来分析如何使用binder机制来传递binder。
实例1:普通进程查找service manager
前面有讲到,普通进程查找service manager使用特殊的handle值0来查找。其查找过程中,先是往0号handle的binder发送一条binder命令 PING_TRANSACTION,如果命令被正确执行并返回,那么会在当前进程中根据handle 值0在本地创建一个BpBinder,并保存到当前进程的ProcessState对象中。该被从使用者本地创建出来的BpBinder作为当前进程访问service manager的IBinder指针。
实例二:向service manager查找service
Service manager提供2个相关的API:getService() 和 checkService()。其中 2者的区别在于 在查找不到目标service的情况下,前者会持续查找5s,直到超时 或者 目标service就绪;后者则不会重试,不管目标service是否存在,检查一次立即返回结果。
在请求端,进程是通过向service manager发送一条CHECK_SERVICE_TRANSACTION binder消息 并携带目标service的名称实现的。
在service manager中,根据请求端传来的service的名称 查找对应service的handle值,然后将查到的handle值作为一个特殊的binder obj(type: BINDER_TYPE_HANDLE),将目标service的handle返回给请求端(Parcel.cpp:flatten_binder())。
binder驱动收到 BINDER_WRITE_READ 命令来处理binder传送消息,在binder驱动的传输处理结尾,会翻译上层传下来的binder obj,如果传递的binder属于原始请求端(回复消息的接收端)进程,那么把该binder obj的类型修改为BINDER_TYPE_BINDER(binder实体)。
在接收端应用层,Parcel读取该binder obj,并根据binder obj内的handle值本地创建一个BpBinder,用来后续访问该binder(Parcel.cpp:unflatten_binder())。