Android源码(8)-Binder理解篇

这是关于Android Binder机制的一篇文章,Binder是Android里面非常重要的组成,也是最难理解的一块知识点,学习Binder最好的方法是深入源码阅读,因为Binder相关的知识错综复杂,一般初学者也很容易迷失在源码的汪洋里,本文旨在梳理Binder的架构和流程,并且试着以实用的角度来看待Binder。

一、为什么需要Binder机制?

Android系统中,每个应用程序是由Android的Activity,Service,Broadcast,ContentProvider这四剑客的中一个或多个组合而成,这四剑客所涉及的多进程间的通信底层都是依赖于Binder IPC机制。例如当进程A中的Activity要向进程B中的Service通信,这便需要依赖于Binder IPC。
如果熟悉Android源码,其实可以知道整个Android系统架构中,也大量采用了Binder机制作为IPC(进程间通信)方案。
Android是在Linux内核的基础上设计的。而在Linux中,已经拥有”管道/消息队列/共享内存/信号量/Socket等等”众多的IPC通信手段;但是,Google为什么单单选择了Binder,可见Binder肯定有自己独特的优势:

1.1 Binder能很好的实现C/S架构

Android系统,很大一部分都是居于Client-Server架构的设计。Client端有什么需求,直接发送给Server端去完成,Server处理完毕再将反馈内容发送给Client。Server端与Client端相对独立,稳定性较好。传统的CS架构只有Socket,但是Socket通信效率相对于其他IPC来说又太低效,而Binder正是基于C/S架构设计的。

1.2 Binder传输效率高

Binder只需要进行一次拷贝,把Client端的用户空间的数据即copy_from_user()到内核空间,然后将内核空间的数据映射到Server端的用户空间。
Binder性能上仅仅次于Linux 共享内存的方式,但是共享内存的方式,进程间同步又是一个难题。

1.3 Binder安全性极高

Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志,Client端将任务发送给Server端,Server端会根据权限控制策略,判断UID/PID是否满足访问权限。
Client-Server通信过程中,Binder内核会为每个Client进程分配了UID/PID来作为鉴别身份的标示,并且在Binder通信时会根据UID/PID进行有效性检测。
而如果是传统的IPC只能由在数据包当中填入UID/PID,这个并不是一个可靠的方法。

知乎上有一位答主讲得很好,可以看看:

为什么 Android 要采用 Binder 作为 IPC 机制?(https://www.zhihu.com/question/39440766)

二、Binder原理

binder1

  1. Binder采用Client-Server架构,包含Client,Server,ServiceManager,Binder驱动四个组件
  2. 应用程序都运行在用户控件,每个应用程序都有它自己独立的内存空间,若不同的应用程序之间涉及到通讯,需要通过内核进行中转,因为要用到内核的copy_from_user()和copy_to_user()等函数
  3. Server进程要先注册Service 到ServiceManager,Client进程使用Server的Service前,需先向ServiceManager中获取相应的Service,然后使用Service

三、Binder驱动层

binder2

当用户空间调用open()方法,最终会调用binder驱动的binder_open()方法;mmap()/ioctl()方法也是同理,从用户态进入内核态,都依赖于系统调用过程。

3.1 binder_init

注册misc设备,指定相应文件操作方法

3.2 binder_open

创建binder_proc对象,并把当前进程等信息保存bind_proc对象,改对象管理IPC所需的各种信息并拥有其他结构体的根结构体,再把bind_proc对象保存到文件filp,以及把bind_proc加入到全局链表binder_procs

3.3 binder_mmap

在内核虚拟地址空间,申请一块于用户虚拟内存相同大小的内存;然后再申请1个page大小的物理内存,再将同一块物理内存映射到内核虚拟地址空间和用户虚拟内存空间,从而实现了用户空间的buffer和内核空间的buffer同步操作的功能

3.4 binder_ioctl

负责在两个进程间收发IPC数据和IPC reply数据,调用流程:

1
2
3
4
5
6
7
8
9
10
11
12
//step 1:
binder_write_read bwr;
ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)
// step 2:
binder_ioctl(filp, BINDER_WRITE_READ, &bwr)
// step 3:
binder_ioctl_write_read(filp, BINDER_WRITE_READ, &bwr, thread)
// step 4:
copy_from_user(&bwr, ubuf, sizeof(bwr))
binder_thread_write(proc, thread, bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
binder_thread_read(proc, thread, bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK);
copy_to_user(...)
binder_thread_write(): 处理binder请求码,以“BC_”开头,建成后bc码,用于IPC层传递到Binder Driver层;
binder_thread_read(): 生成Binder响应吗,以“BR_”开头,简称BR码,用于Binder Driver层传递到IPC层

binder4

四. Binder通信流程

例如当名为BatteryStatsService的Client向ServiceManager注册服务的过程中,IPC层的数据组成为:
Handle=0,RPC代码为ADD_SERVICE_TRANSACTION,RPC数据为BatteryStatsService,Binder协议为BC_TRANSACTION。
整个流程图大致如下:

binder5

handle为0正是指向ServiceManager。

五、启动ServiceManager

ServiceManager启动时序图:

binder5

  1. 打开binder驱动,并调用mmap()方法分配128k内存映射空间: binder_open()
  2. 通知binder驱动使其成为守护进程: binder_become_context_manager()
  3. 验证selinux权限,判断进程是否有注册或查看指定服务
  4. 进入循环状态,等待client端的请求:binder_loop()

六、获取ServiceManager

获取Service Manager是通过defaultServiceManager()方法来完成,当进程注册服务(addService)或 获取服务(getService)的过程之前,都需要先调用defaultServiceManager()方法来获取gDefaultServiceManager对象。

binder5

  1. 获取ProcessState对象,在其构造函数中调用open_driver函数打开binder驱动,并将句柄保存到mDriverFD;
  2. 调用gProcess->getContextObject函数来获得一个句柄值为0得binder引用,即BpBinder
  3. 通过interface_cast构造一个BpServiceManger对象,所以gDefaultServiceManager最终为new BpServicemanager(new BpBinder(0))

七、addService

以Native层的服务以media服务为例,注册MediaPlayerService的时序图如下:

binder5

  1. defaultServiceManager()返回的是BpServiceManager,会调用BpServiceManager.addService方法
  2. addService()通过remote()中保存的BpBinder调用到IPCThreadState的transact方法;
  3. IPCThreadState::transact会调用writeTransactionData()传输数据传输数据,然后和驱动交互,驱动把请求转发给ServiceManager执行真正的注册服务;
  4. 得到驱动的返回后,调用BBinder,最终调用到BnMediaPlayerService的onTransact方法;
  5. 开启两个线程不断和Binder进行交互,获取Client请求。

获取服务的流程基本也是差不多的,不再累述。

八、Binder架构

binder在framework层,采用JNI技术来调用native(C/C++)层的binder架构,从而为上层应用程序提供服务。 我们知道native层中,binder是C/S架构,分为Bn端(Server)和Bp端(Client)。对于java层在命名与架构上非常相近,同样实现了一套IPC通信架构。

  1. BinderProxy类代表Client端,Binder类代表Server端
  2. framework层的Binder逻辑是建立在Native层架构基础之上的,核心逻辑是交予Native层方法来处理
比如addService流程:
  1. java层通过getIServiceManager获得ServiceManagerProxy对象,通过该对象BinderProxy,最终会调用BpBinder对象,由BpBinder来完成通讯。
  2. Binder驱动将Client端的请求转发给BBinder的transact方法,然后由其子类JavaBBinder调用。后者调用指定Service的方法,并返回给驱动

九、Binder类图

9.1 Native Binder类图

binder5

9.2 Framework Binder类图

binder5

十、Binder其他

介绍一些Binder其他比较重要的点,方便理清Binder的一些疑问。比如Binder实体和引用,比如ProcessState和IPCThreadState,比如数据结构怎么传递等。

10.1 Binder中各个角色的关系

binder5

1. Binder实体 : binder_node

Binder实体,是各个Server以及ServiceManager在内核中的存在形式。
Binder实体实际上是内核中 binder_node 结构体的对象,它的作用是在内核中保存Server和ServiceManager的信息(例如,Binder实体中保存了Server对象在用户空间的地址)。简言之,Binder实体是Server在Binder驱动中的存在形式,内核通过Binder实体可以找到用户空间的Server对象。
在上图中,Server和ServiceManager在Binder驱动中都对应的存在一个Binder实体。

2. Binder引用 : binder_ref

所谓Binder引用,实际上是内核中binder_ref结构体的对象,它的作用是在表示”Binder实体”的引用。换句话说,每一个Binder引用都是某一个Binder实体的引用,通过Binder引用可以在内核中找到它对应的Binder实体。
如果将Server看作是Binder实体的话,那么Client就好比Binder引用。Client要和Server通信,它就是通过保存一个Server对象的Binder引用,再通过该Binder引用在内核中找到对应的Binder实体,进而找到Server对象,然后将通信内容发送给Server对象。
Binder实体和Binder引用都是内核(即Binder驱动)中的数据结构。每一个Server在内核中就表现为一个Binder实体,而每一个Client则表现为一个Binder引用。这样,每个Binder引用都对应一个Binder实体,而每个Binder实体则可以多个Binder引用。

3. 远程服务

Server都是以服务的形式注册到ServiceManager中进行管理的。如果将Server本身看作是”本地服务”的话,那么Client中的”远程服务”就是本地服务的代理。如果你对代理模式比较熟悉的话,就很容易理解了,远程服务就是本地服务的一个代理,通过该远程服务Client就能和Server进行通信。

10.2 进程和线程的关系

图解:
binder5

  1. Binder驱动通过binder_procs链表记录所有创建的binder_proc结构体,binder驱动层的每一个binder_proc结构体都与用户空间的一个用于binder通信的进程一一对应。
  2. 每个进程有且只有一个ProcessState对象,这是通过单例模式来保证的。
  3. 每个进程中可以有很多个线程,每个线程对应一个IPCThreadState对象,IPCThreadState对象也是单例模式,即一个线程对应一个IPCThreadState对象,在Binder驱动层也有与之相对应的结构,那就是Binder_thread结构体。在binder_proc结构体中通过成员变量rb_root threads,来记录当前进程内所有的binder_thread。
Binder线程池:

每个Server进程在启动时会创建一个binder线程池,并向其中注册一个Binder线程;之后Server进程也可以向binder线程池注册新的线程,或者Binder驱动在探测到没有空闲binder线程时会主动向Server进程注册新的的binder线程。对于一个Server进程有一个最大Binder线程数限制,默认为16个binder线程,例如Android的system_server进程就存在16个线程。对于所有Client端进程的binder请求都是交由Server端进程的binder线程来处理的。

10.3 Binder数据传输

binder5

  1. 用户空间的进程调用ioctl(fd,BINDER_WRITE_READ,&bwr)时传递给Binder驱动的信息。fd是Binder驱动的文件句柄,BINDER_WRITE_READ是ioctl()的一个标识,而bwr是传递的数据,write_buffer是请求数据的内容,而write_consumed是用来记录请求数据中已经被Binder驱动处理过的数据的大小。
  2. ioctl会走到binder_thread_write和binder_thread_read。这层的数据是”事务指令”+”binder_transaction_data结构体”组成的。data是保存事务中具体数据的内存地址。具体调用流程可以参考#3.4章节
  3. 这层是有效数据。如果该请求是传递给ServiceManager进行处理的,则有效数据是:消息头+”Server的相关信息”。消息头是用来进行有效性检查的,而”Server的相关信息”则是请求要处理的信息。

十一、源码目录

从上之下, 整个Binder架构所涉及的总共有以下5个目录:

1
2
3
4
5
/framework/base/core/java/               (Java)
/framework/base/core/jni/ (JNI)
/framework/native/libs/binder (Native)
/framework/native/cmds/servicemanager/ (Native)
/kernel/drivers/staging/android (Driver)

11.1 Java framework

1
2
3
4
5
6
7
8
9
10
11
12
13
/framework/base/core/java/android/os/  
- IInterface.java
- IBinder.java
- Parcel.java
- IServiceManager.java
- ServiceManager.java
- ServiceManagerNative.java
- Binder.java

/framework/base/core/jni/
- android_os_Parcel.cpp
- AndroidRuntime.cpp
- android_util_Binder.cpp (核心类)

11.2 Native framework

1
2
3
4
5
6
7
8
9
10
11
12
/framework/native/libs/binder         
- IServiceManager.cpp
- BpBinder.cpp
- Binder.cpp
- IPCThreadState.cpp (核心类)
- ProcessState.cpp (核心类)
/framework/native/include/binder/
- IServiceManager.h
- IInterface.h
/framework/native/cmds/servicemanager/
- service_manager.c
- binder.c

11.3 Kernel

1
2
3
/kernel/drivers/staging/android/
- binder.c
- uapi/binder.h