Binder机制

Binder机制

Posted by candy1126xx on April 29, 2017

简介

Binder机制解决了用户空间中的进程A如何向进程B发送信息的问题。“发送信息”是指“进程A调用进程B的方法,向B传递参数,B执行完成后,把结果返回A”。Binder机制采用了CS架构。其中,发送信息的进程是“客户端”,执行任务的是“服务端”。主角是“1个缓冲区,2个线程池”。

1个缓冲区

“1个缓冲区”是指从Binder设备(/dev/binder)映射出的一块内存。这块内存以“handle”分块,一块内存对应一个“服务端”。每个“服务端”的handle是由Binder驱动分配。“服务端”的handle由ServiceManager统一管理。ServiceManager的handle是一个特定值0。

2个线程池

“2个线程池”是指“客户端发送线程池”和“服务端接收线程池”。但实际上1个进程只有1个用于Binder通信的线程池。这里这样说,只是为了更清晰地说明通信过程。通信层与Binder设备交互时,是客户端还是服务端,取决于mIn、mOut哪个有数据哪个没数据,和指令码。

流程

当“客户端”要发送信息的时候,会从“客户端发送线程池”中找到一个可用线程,执行两层while死循环嵌套,外读内写;向Binder设备写入数据,数据包括四部分,handle、InterfaceToken、请求码、参数。数据写入后跳出内层循环。

同时,“服务端接收线程池”中有线程在不断试图从Binder设备中handle标示的内存块读出信息。当成功读到后,它通过InterfaceToken找到进程中的目标组件,通过请求码找到组件的目标函数。然后执行函数。执行完毕后把返回值发送到Binder设备。

当“客户端”从Binder设备读出返回值的指令码是BR_REPLY时,会跳出外层循环。然后把返回信息返回到业务层。

数据传输过程中用到了2种数据结构,结构体binder_write_read用于封装通信层与Binder设备交换的数据;C++的类Parcel用于封装业务层与通信层交换的数据。

架构

Native层
  • 通信层
  • “客户端”是BpBinder,通过IPCThreadState -> writeTransactionData()向Binder驱动中某一数据缓冲区写入数据;
  • “服务端”是BBinder,从Binder驱动中读出数据,并转调虚方法onTransact()。
  • “定义接口”是IBinder。
  • 模版层:这一层的作用主要是用模版扩展通信层。
  • “客户端”是BpInterface;
  • “服务端”是BnInterface;
  • “定义接口”是IInterface;
  • 业务层:比如MediaService向ServiceManager注册的过程
  • “客户端”是BpServiceManager,通过定义在IInterface.h的模版函数interface_cast<>(BpBinder)得到;是BpBinder的装饰类;它实现了IServiceManager中定义的业务相关的虚方法,但没有实际处理逻辑,而是构造两个Parcel对象(一个用于封装向服务端发送的数据,一个用于封装接收服务端返回的数据),并转调BpBinder -> transact()。
  • “服务端“是BnServiceManager,通过asBinder()得到;它实现了BBinder的虚方法onTransact(),针对不同的请求码转调实现层的不同方法。
  • “定义接口”是IServiceManager。
  • 实现层:比如ServiceManager,是定义在service_manager.c中的各个方法,是实际处理逻辑的地方。
Java层
  • 通信层
  • “客户端”是BinderProxy对象,通过JNI调用到BpBinder。
  • “服务端”是Binder对象,通过JNI调用到BBinder。
  • “定义接口”是IBinder。
  • 业务层,比如WMS
  • “客户端”是IWindowManager.Proxy,通过Stub.asInterface(BinderProxy)得到;
  • “服务端”是IWindowManager.Stub,通过Stub.asBinder()得到;
  • “定义接口”是IWindowManager。
  • 实现层:实现了业务层的“定义接口”;是实际处理逻辑的类;比如WMS是WindowManagerService。
  • 封装层:为了便于应用进程调用,会在Android应用层定义一个(组)类,以封装通信过程。比如WMS在应用进程中是WindowManagerGolbal等。

Java层缓存系统服务的服务端

在获取之后会缓存到ServiceManager.sCache,是以服务名为键,以Binder对象为键的Map。

重要的类(C++)

  • IBinder:Native层通信层接口,定义Binder机制协议。
  • queryLocalInterface():由客户端实现;根据指定的字符串查询在本进程的业务层的客户端实例。
  • transact():由客户端实现是向Binder设备写入信息;由服务端实现是从Binder设备读出信息并调用相应函数。
  • BBinder:Native层通信层服务端,继承自IBinder。
  • transact():对IBinder的实现,转调虚方法onTransact()。
  • BpBinder:Native层通信层客户端,继承自IBinder。
  • transact():对IBinder的实现;转调IPCThreadState::self() -> transact()。
  • IPCThreadState:进程中负责IPC通信的一条线程。
  • mIn(Parcel):接收来自Binder驱动的数据的缓冲区。
  • mOut(Parcel):向Binder驱动发送的数据的缓冲区。
  • transact():先调用writeTransactionData()向Binder设备写入数据,然后调用waitForResponse()等待回应。
  • writeTransactionData():创建binder_transaction_data,根据目标服务端、要传递的数据赋值,并写入mOut。
  • waitForResponse():while死循环,在循环中不断调用talkWithDriver(),并根据mIn中的指令码执行不同操作;直到talkWithDriver()的返回值小于NO_ERROR,或指令码为BR_REPLY。
  • talkWithDriver():创建binder_write_read;其中写的部分由mOut赋值,(如需)读的部分由mIn赋值;while死循环,循环中系统调用ioctl,指令码为BINDER_WRITE_READ,参数为binder_write_read;直到ioctl返回值不为—EINTR;跳出循环后,即数据写入Binder设备后,Binder设备把服务端执行结果写入binder_write_read中读的部分,即mIn。
  • waitForResponse()中BR_REPLY的case:这是客户端读到服务端返回值的case;用返回信息赋值reply并返回业务层。 executeCommand()中BR_TRANSACTION的case:这是服务端读到客户端请求的case;用请求信息构造BnXXXService,并调用transact()。
  • IServiceManager:ServiceManager在Native层业务层的接口。
  • checkService():检查服务是否有注册到ServiceManager的权限。
  • addService():把服务注册到ServiceManager。
  • getService():根据服务名获取服务。
  • listServices():列出所有已注册的服务。
  • BnServiceManager:ServiceManager在Native层业务层的服务端,继承自BnInterface
  • onTransact():对BBinder的实现。针对不同的请求码转调实现层的不同方法。
  • BpServiceManager:ServiceManager在Native层业务层的客户端,继承自BpInterface

Binder驱动指令码

  • BINDER_VERSION:设置Binder Version。
  • BINDER_SET_CONTEXT_MGR:设置守护进程。
  • BINDER_WRITE_READ:向Binder驱动写入/读出数据。

一次拷贝

由于mmap()分配的内存是映射在接收方用户空间里的,所有总体效果就相当于对有效负荷数据做了一次从发送方用户空间到接收方用户空间的直接数据拷贝,省去了内核中暂存这个步骤,提升了一倍的性能。顺便再提一点,Linux内核实际上没有从一个用户空间到另一个用户空间直接拷贝的函数,需要先用copy_from_user()拷贝到内核空间,再用copy_to_user()拷贝到另一个用户空间。为了实现用户空间到用户空间的拷贝,mmap()分配的内存除了映射进了接收方进程里,还映射进了内核空间。所以调用copy_from_user()将数据拷贝进内核空间也相当于拷贝进了接收方的用户空间,这就是Binder只需一次拷贝的‘秘密’。

Binder线程池

创建线程池

Binder线程池在其所在进程的创建中产生。Java层进程的创建都是通过Process.start()方法,向Zygote进程发出创建进程的socket消息,Zygote收到消息后会调用Zygote.forkAndSpecialize()来fork出新进程,在新进程中会调用到RuntimeInit.nativeZygoteInit(),该方法经过jni映射,最终会调用到app_main.cpp中的onZygoteInit(),在onZygoteInit()中又会先后调用ProcessState::self()和ProcessState::startThreadPool() 。

ProcessState::self()的主要工作是调用open()打开/dev/binder驱动设备,再利用mmap()映射内核的地址空间,将Binder驱动的fd赋值ProcessState对象中的变量mDriverFD,用于交互操作;

ProcessState::startThreadPool() 的主要工作是创建一个新的binder线程,并不断进行talkWithDriver();具体地说:

  1. mThreadPoolStarted置为true(保证startThreadPool()只执行一次);即1个进程只有一个线程池;
  2. 创建PoolThread对象,为其取名为“Binder_%d”,并调用其run(),最终调用到IPCThread::joinThreadPool();
  3. 向mOut写入指令码BC_ENTER_LOOPER;
  4. 进入while循环,在循环中不断调用goAndExcuteCommond();
  5. 在goAndExcuteCommond()中,调用talkWithDriver(),把mOut写入Binder设备,同时Binder设备把处理结果写入mIn;
  6. 从mIn中解析出指令码,调用excuteCommond()执行;
  7. 直到goAndExcuteCommond()返回值为-ECONNREFUSED或-EBADF退出循环,向mOut写入指令码BC_EXIT_LOOPER,发送到Binder设备。

线程休眠

当缓冲区中没有数据时,binder线程会进入休眠状态。

创建线程

第一条线程是伴随线程池的创建而创建的,并向Binder驱动发送指令码BC_REGISTER_LOOPER。其余线程是由Binder驱动控制创建,当同时满足下列4个条件时,Binder驱动会返回通知线程池创建新线程的响应码BR_SPAWN_LOOPER:

  1. 当前进程中没在请求创建binder线程;
  2. 当前进程没有空闲可用的binder线程,即进入休眠状态的线程个数为0;
  3. 当前进程已启动线程个数小于最大上限(默认15);
  4. 当前进程处于BINDER_LOOPER_STATE_ENTERED状态,即收到过BC_ENTER_LOOPER。