跨进程通信是需要内核空间做支持的。传统的 IPC 机制如管道、Socket 都是内核的一部分,因此通过内核支持来实现进程间通信自然是没问题的。但是 Binder 并不是 Linux 系统内核的一部分,那怎么办呢?这就得益于 Linux的动态内核可加载模块(Loadable Kernel Module,LKM)的机制;模块是具有独立功能的程序,它可以被单独编译, 但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。
在 Android 系统中,这个运行在内核空间,负责各个用户进程通过 Binder 实现通信的内核模块就叫 Binder驱 动(Binder Dirver)。
那么在 Android 系统中用户进程之间是如何通过这个内核模块(Binder 驱动)来实现通信的呢?难道是和前面说的 传统 IPC 机制一样,先将数据从发送方进程拷贝到内核缓存区,然后再将数据从内核缓存区拷贝到接收方进程,通过 两次拷贝来实现吗?显然不是。
这就涉及到Linux 下的另一个概念:内存映射。
Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。
内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。
Binder IPC 正是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。
比如进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次 拷贝(磁盘–>内核空间–>用户空间);通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间 建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。
而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是在内核空间创建数据接收的缓存空间。
一次完整的 Binder IPC 通信过程通常是这样:
- 首先 Binder 驱动在内核空间创建一个数据接收缓存区
- 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;发送方进程通过系统调用
- copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信
性能
首先说说性能上的优势。Socket 作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。共享内存虽然无需拷贝,但控制复杂,难以使用。Binder 只需要一次数据拷贝,性能上仅次于共享内存。
稳定性
再说说稳定性,Binder 基于 C/S 架构,客户端(Client)有什么需求就丢给服务端(Server)去完成,架构清晰、职责明确又相互独立,自然稳定性更好。共享内存虽然无需拷贝,但是控制负责,难以使用。从稳定性的角度讲,Binder 机制是优于内存共享的。
安全性
另一方面就是安全性。Android 作为一个开放性的平台,市场上有各类海量的应用供用户选择安装,因此安全性对于Android 平台而言极其重要。作为用户当然不希望我们下载的 APP 偷偷读取我的通信录,上传我的隐私数据,后台偷跑流量、消耗手机电量。传统的 IPC 没有任何安全措施,完全依赖上层协议来确保。首先传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份。Android 为每个安装好的 APP 分配了自己的 UID,故而进程的 UID 是鉴别进程身份的重要标志。传统的 IPC 只能由用户在数据包中填入 UID/PID,但这样不可靠,容易被恶意程序利用。可靠的身份标识只有由 IPC 机制在内核中添加。其次传统的 IPC 访问接入点是开放的,只要知道这些接入点的程序都可以和对端建立连接,不管怎样都无法阻止恶意程序通过猜测接收方地址获得连接。同时Binder 既支持实名 Binder,又支持匿名 Binder,安全性高。
基于上述原因,Android 需要建立一套新的 IPC 机制来满足系统对稳定性、传输性能和安全性方面的要求,这就是Binder。
Binder何时初始化
Binder初始化一般是指binder驱动的初始化,在使用binder的过程中,从来没有执行过new Binder的方式来实现Binder初始化,原因很简单:binder初始化有它自身独立的特点。
每一个应用进程启动的时候,都是通过zygote fork产生的,所以,当fork产生进程后app进程的代码就开始执行,开始运行的地方如下:
1 | public static final Runnable zygoteInit(int targetSdkVersion, |
可以看到,会执行ZygoteInit.nativeZygoteInit()函数,而nativeZygoteInit函数执行appRuntime的onZygoteInit
代码,也就是App_main.cpp中的 onZygoteInit()函数,函数如下:
1 | virtual void onZygoteInit() |
在ProcessState的self函数里面就会初始化ProcessState(),而这个初始化的一个非常重要的动作就是启动binder驱动和并构建binder的Map映射。具体代码如下:
1 | ProcessState::ProcessState(const char *driver) |
所以,总的来说,Binder的初始化是在进程已创建就完成了。创建进程后会第一时间为这个进程打开一个binder驱动,并调用mmap接口向Binder驱动中申请内核空间的内存。
Binder通信的流程
首先,一个进程使用 BINDERSETCONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager,Binder的引用在所有Client中都固定为0;
Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。
Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用。
通过这个Binder实体引用,Client实现和 Server 进程的通信。
aidl通信的基本步骤
Client通过ServiceConnection获取到Server的Binder,并且封装成一个Proxy。
通过Proxy来同步调用IPC方法(xxxFunction),同时通过Parcel将参数传给Binder,最终触发Binder的 transact方法。
Binder的transact方法最终会触发到Server上Stub的onTransact方法。
Server上Stub的onTransact方法中,会先从Parcel中解析中参数,然后将参数带入真正的方法中执行,然后将 结果写入Parcel后传回。
请注意:Client的Ipc方法中,执行Binder的transact时,是阻塞等待的,一直到Server逻辑执行结束后才会继 续执行。当然,如果IPC方法是oneWay的方式,那么就是非阻塞的等待。
当Server返回结果后,Client从Parcel中取出返回值,于是实现了一次IPC调用。
bindService的全流程
将Server端的Binder对象发送给Client端
Activity作为Client发起bindService,最终会调度到AMS 去执行bindService。在这个过程中,Client要去调用 AMS的代码,所以此时就会涉及到跨进程调度,基于第三章的Binder通信模型我们不难知道,Client会先和 ServiceManager通信,从ServiceManager中拿到AMS的IBinder。
Activity拿到AMS的IBinder后,跨进程执行AMS的BindService函数;
由于AMS管理所有的应用进程,因此AMS中持有了应用进程的Binder,所以此时AMS可以发起第4步也就是跨进 程调度scheduleBindService();
Server端会在收到AMS的bindService的请求后,会将自己的IBinder发送给client,但是Server必须通过AMS才能 将Binder对象传过去,所以此时需要跨进程从ServiceManager中去拿到AMS的binder;
Server端通过AMS的binder直接调用AMS的代码publishService(),将service的Binder发送给AMS;
经过层层调用,最终AMS讲Server端的binder通过回调connect函数传递给了Client端的Activity;
Java与Native 通信的基本流程
- Client端通过ServiceManager 拿到Server端服务的Binder 代理,也就是BinderProxy(是Server端Binder的一个代理);
- 这个BinderProxy的访问需要经过JNI层的Android_util_binder类将请求转交给native的BpBinder(p代表代理的意思);
- BpBinder会通过ioctl将请求转交给Binder驱动设备;
- 在服务端注册了一个监听请求的回调函数,一旦驱动层收到BpBinder 的调用,就会回调BBInder注册的回调函数,于是,就将请求转给了BBinder;
- BBinder拿到请求后,会进行一些数据的处理,然后通过JNI将请求转交给了java类;
- java层会通过aidl中的函数将请求发送给Server端的实现者,由Server端通过stub 去调用相关的执行代码,并将结果通过类似的路径返回。
ServcieManager场景
在跨进程通信过程中,比如与AMS通信,首先需要拿到AMS的binder,然而,AMS的binder往往是通过ServcieManager获取的,因此会有代码:
1 | ServiceManager.getService(Context.ACTIVITY_SERVICE) |
j接下来调用Binder.allowBlocking(rawGetService(name)),核心代码在rawGetService中
1 | private static IBinder rawGetService(String name) throws RemoteException { |
这里的逻辑是,先通过getIServiceManager()获取到IServiceManager对象,然后再通过这个IServiceManager对象获取到一个IBinder。
注意,此处有两个IPC:
获取到IServiceManager
通过IServiceManager获取到该name的Service的Binder的IPC
重在探索java到native的逻辑,先看第一个。
1 | private static IServiceManager getIServiceManager() { |
到这,可以看到返回的其实是ServiceManagerNative.asInterface()的返回值。
对AIDL有所了解就知道,asInterface()的内容大概如下:
这个方法属于aidl接口的内部类 Stub。 在同一进程中,就会直接返回Stub,如果在另一个进程中调用,就会返回将这个ibinder封装好的Proxy对象。后面会分析ServiceManagerNative.asInterface 函数。先看一下IBinder的来源,也就BinderInternal.getContextObject()。
BinderInternal.getContextObject()代码如下:
1 | public static final native IBinder getContextObject(); |
JNI层代码如下:
android_util_Binder.android_os_BinderInternal_getContextObject()代码如下
1 | static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz) |
先通过ProcessState获取到了一个native 层的IBinder强引用,也就是一个BpBinder。 然后将这个native层的IBinder强引用传入javaObjectForIBinder()方法,最终封装成java层的IBinder然后返回。此处先不深究ProcessState的逻辑,整个native层的binder有自己的一整套的逻辑,后面的文章会继续探索。我们可以先稍微看下javaObjectForIBinder()的大概逻辑。 android_util_Binder.javaObjectForIBinder()
1 | // 将一个BpBinder对象(这是native中的类型)转换成java中的类型 |
上面的函数就是将一个BpBinder对象(这是native中的类型)转换成java中的类型,中间采用了反射技术而已。
核心代码gBinderProxyOffsets
1 | static struct binderproxy_offsets_t |
gBinderProxyOffsets实际上是一个用来记录一些java中对应类、方法以及字段的结构体,用于从native层调用java层代码,而通过int_register_android_os_BinderProxy,我们知道,binderproxy_offsets_t中的mClass字段就是 BInderProxy,而mGetInstance 就是BInderProxy.java 中getInstance方法。因此核心代码创建的是一个BinderProxy对象。
具体的执行流程如下图所示:
第一大步:为了获取ServcieManager 的IServiceManager,首先要ServcieManager进程创建一个底层的Binder,所
以会有android_os_BinderInternal_getContextObject也就是第2步,第2步会在ProcessState::self()中初始化Binder
驱动,然后再执行第3步;
第二大步:上图中第3步会调用到第4步,在第4步getStrongProxyForHandle代码如下:
1 | sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle) |
其中会执行一个BpBinder::create(handle),此处会创建一个BpBinder对象,并且将BpBinder对象赋值给result对象
并返回result,也就是返回BpBinder对象。这个BpBinder对象一路返回,最终在android_util_Binder.cpp中的
android_os_BinderInternal_getContextObject函数中执行。
第三大步:BpBinder需要返回给java层Client端使用,所以此时的封装就是将BpBinder封装成为Java层的BinderProxy对象。因此在Client端得到的IServiceManager 其实是BinderProxy类的子类的对象。所以,BinderInternal.getContextObject(),返回的是一个层层封装的类的实例,具体来说,是Native层的BpBinder对象被封装成为BinderProxy对象并返回。
不同类型的Binder
IBinder
1 | // IBinder从Refbase继承而来,一提供强弱指针计数能力 |
对于IBinder中的方法,基本都是没有实现的,这些方法的实现都交给继承它的子类来实现,那下面直接看BpBinder的内容。
BpBinder,BBinder
BpBinder和BBinder都是Android中与Binder通信相关的代表,他们都是从IBinder中继承而来的。其中BpBinder是客户端用来与Server交互的代理类,BBinder则是和proxy相对的一端,它是proxy交互的目的端。如果说Proxy代表客户端,那么BBinder就代表这服务端。这里BpBinder和BBinder是一一对应的,即某个BpBinder只能和对应的BBinder交互。
JavaBBinder
IBinder是BBinder的父类,BBinder是JavaBBinder的父类。
java层直接与native层交互的对象有两个——Binder对象与BinderProxy对象。
Binder对应“Binder在本进程”的场景,BinderProxy对应“Binder在其他进程”的场景。
native层javaBBinder与java层的Binder一一对应。
native层的BinderProxyNativeData与java层的BinderProxy一一对应。
在native层,gBinderProxyOffsets(binderproxy_offsets_t)存储了java层binderProxy的对象与需要调用的方法和属
性。gBinderOffsets(binderproxy_offsets_t)存储了java层binder的对象与需要调用的方法和属性。
ibinderForJavaObject负责通过java的Binder或者BinderProxy对象,找到并返回native层的IBinder对象。
javaObjectForIBinder通过native层的IBinder对象,找到或者封装成java对象返回。
ServiceManager.getService方法
第一步:先调用IServiceManager.Stub.Proxy的getService方法
1 | public android.os.IBinder getService(java.lang.String name) throws |
第二步:接下来会调用mRemote.transact
而mRemote则是BinderProxy对象,所以接下来执行下面的代码:
1 | public boolean transact(int code, Parcel data, Parcel reply, int flags) throws |
首先,系统会通过checkParcel检测数据的格式和大小,Android默认设置了Parcel数据传输不能超过800k,如果超过了的话,便会调用Slog.wtfStack打印日志,需要注意的是,在当前进程不是系统进程并且系统也不是工程版本的情况下,这个方法是会结束进程的,所以在应用开发的时候,我们需要注意跨进程数据传输的大小,避免因此引发crash。
核心函数是调用transactNative方法,这是一个native方法,在frameworks/base/core/jni/android_util_Binder.cpp中实现。
1 | static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj, |
这里首先是获得native层对应的Parcel并执行判断,Parcel实际上功能是在native中实现的,java中的Parcel类使用mNativePtr成员变量保存了其对应native中的Parcel的指针,然后调用getBPNativeData函数获得BinderProxy在native中对应的BinderProxyNativeData,再通过里面的mObject域成员变量得到其对应的BpBinder。
getBPNativeData(env, obj)->mObject.get();是上面代码的核心之一,调用getBPNativeData函数获得BinderProxy在native中对应的BinderProxyNativeData,target 事实上是BpBinder。
第三步,BpBinder 的transact函数
此时就顺理成章的调用到了BpBinder的transact函数了
1 | status_t BpBinder::transact( |
这里有一个Alive判断,可以避免对一个已经死亡的binder服务再发起事务,浪费资源,除此之外便是调用IPCThreadState的transact函数了。
第四步:IPCThreadState的transact函数调用
路径:frameworks/native/libs/binder/IPCThreadState.cpp
ProcessState负责打开binder驱动并进行mmap映射,而IPCThreadState则是负责与binder驱动进行具体的交互
IPCThreadState也有一个self函数,与ProcessState的self不同的是,ProcessState是进程单例,而IPCThreadState是线程单例。
我们接着看它的ProcessState的transact函数:
1 | status_t IPCThreadState::transact(int32_t handle, |
这个函数的重点在于writeTransactionData和waitForResponse,我们依次分析:
首先看writeTransactionData:
1 | status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags, |
binder_transaction_data结构体(tr结构体)是向Binder驱动通信的数据结构,上面函数中,我们将binder请求码(这里为BC_TRANSACTION)和binder_transaction_data结构体依次写入到mOut中,为之后binder_tansaction做准备。
再看waitForResponse函数
1 | status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult) |
这里有一个循环,正如函数名所描述,会一直等待到一整条binder事务链结束返回后才会退出这个循环,在这个循环的开头,便是talkWithDriver方法,在talkWithDriver 函数里面主要是调用Binder驱动的ioctl方法完成数据的传输。
上面整理了Client端通过ServiceManager的getService函数去获取对应服务的对象的过程,分析到了将getService请求发送给Binder驱动。下面继续开始分析服务端收到getService的请求后如何处理的总流程。
ServiceManager进程 死循环
servicemanager进程的入口函数在frameworks\native\cmds\servicemanager\main.cpp中
1 | int main(int argc, char** argv) { |
这里的Looper和我们平常应用开发所说的Looper是一个东西,可以通过Looper::addFd函数监听文件描述符,通过Looper::pollAll或Looper::pollOnce函数接收消息,消息抵达后会回调LooperCallback::handleEvent函数
1 | class BinderCallback : public LooperCallback { |
在servicemanager进程启动的过程中调用了BinderCallback::setupTo函数,这个函数首先向binder驱动发起了一个BC_ENTER_LOOPER事务请求,获得binder设备的文件描述符,然后调用Looper::addFd函数监听binder设备文件描述符,这样当binder驱动发来消息后,就可以通过Looper::handleEvent函数接收并处理了。
1 | status_t IPCThreadState::setupPolling(int* fd) |
基于以上的分析,一旦Client端发送IPC请求,就会通过Binder驱动发送消息给服务端,而服务端则通过BinderCallback来接收消息,并做下一步的处理。
Binder驱动事务处理
BinderCallback类重写了handleEvent函数,里面调用了IPCThreadState::handlePolledCommands函数来接收处理binder事务
1 | status_t IPCThreadState::handlePolledCommands() |
这个函数的重点在getAndExecuteCommand,首先无论如何从binder驱动那里读取并处理一次响应,如果处理完后发现读缓存中还有数据尚未消费完,继续循环这个处理过程(理论来说此时不会再从binder驱动那里读写数据,只会处理剩余读缓存)
1 | status_t IPCThreadState::getAndExecuteCommand() |
从binder驱动中读取数据,然后从数据中读取出BR响应码,接着调用executeCommand函数继续往下处理
1 | status_t IPCThreadState::executeCommand(int32_t cmd) |
重点分析这个函数在BR_TRANSACTION下的case,首先,这个函数从读缓存中读取了binder_transaction_data,我们知道这个结构体记录了实际数据的地址、大小等信息,然后实例化了一个Parcel对象作为缓冲区,从binder_transaction_data中将实际数据读取出来。
接着找到本地BBinder对象,对于ServiceManager来说就是之前在main函数中setTheContextObject的ServiceManager对象,而对于其他binder服务端来说,则是通过tr.cookie获取,然后调用BBinder的transact函数。
理论上来说,the_context_object->transact(tr.code, buffer, &reply, tr.flags),应该是执行ServiceManager中的transact函数,但是在ServiceManager中没有实现该函数,因此只能去父类BnServiceManager中去找transact函数,但是很不巧BnServiceManager中也不存在于是再找父类,只有在BBinder中存在transact函数,因此会执行到BBinder中的transact函数。
1 | status_t BBinder::transact( |
分析下继承关系后,会执行到BnServiceManager中的onTransact中
1 | android::status_t BnServiceManager::onTransact(uint32_t _aidl_code, const ::android::Parcel& |
代码的核心是addService,而addService的实现类就是ServiceManager里面的addService函数
1 | Status ServiceManager::addService(const std::string& name, const sp<IBinder>& binder, bool |
最终,服务的binder被封装成为了一个Service,并复制给了mNameToService数组进行存储。
IPCThreadState 解析
在Android中,每个参与Binder通信的线程都会有一个IPCThreadState实例与之关联。我最开始接触到这个类是在BpBinder::transact方法中。
1 | tatus_t BpBinder::transact( |
其就是调用的IPCThreadState::transact来完成的数据传输工作,其工作可以分为两步:
1、发送数据
实际上,writeTransactionData只是将数据转换成binder_transaction_data结构并重新写入到IPCThreadState::mOut中。
并没有真正的将数据发送出去。实际的发送操作是在waitForResponse中完成的。
2、接收数据
F_ONE_WAY表示的是单向通信,不需要对端回复。所以这里接收数据就多了几个判断分支。区别就是参数不一样。
该函数必定需要被执行的,因为数据要发出去
1 | status_t IPCThreadState::transact(int32_t handle, |
1 | status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult) |
需要处理如下这些cmd
1 | static const char *kReturnStrings[] = { |
处理CMD的函数就是waitForResponse和executeCommand。
前面通过writeTransactionData,已经把数据写入到了binder_transaction_data中。
talkWithDriver就是调用ioctl(BINDER_WRITE_READ)完成真正的数据接发
1 | status_t IPCThreadState::talkWithDriver(bool doReceive) |