从最上层到驱动层看Android Conetext.getSystemService究竟做了什么

曾经好奇为什么同样是binder接口,Conetext.getService为什么可以直接获取,而不需要像bindeService一样异步回调,今天终于从能够从上层源码到binder驱动源码,彻底了解这一过程,同时分析binder接口在调用的时候,底层究竟是如何实现的。

1.从getSystemService 到native的getContextObject

ContextImpl.java

SystemServiceRegistry.java

SYSTEM_SERVICE_FETCHERS是一个hasmap,键值是一个抽象类,ServiceFetcher,它在初始化的时候实现,见如下代码

registerService 的作用就是把CachedServiceFetcher 放到 SYSTEM_SERVICE_FETCHERS 中,这样每次取服务时都可以从这个缓存获取,因此我们第二次getSystemService时性能是非常高的,时间复杂度位O(1),然后所有系统服务端最终都会调用到这一步

frameworks/base/core/java/android/os/ServiceManager.java

这里也有缓存,当没有的时候会走rawGetService

/frameworks/base/core/java/android/os/Binder.java

allowBlocking 在这里必然走的是BinderProxy这个条件,因为系统服务的实现都是在系统进程,对于app来说拿到的肯定是BinderProxy对象,这里只进行了一个赋值,无需关注。

回到上面的rawGetService方法,里面仅仅是嵌套了一下getIServiceManager,就不再重复贴出了,getIServiceManager实现如下

可以看到为了获取IServiceManager 调用到了BinderInternal.getContextObject(),getContextObject 是一个native方法,可以肯定 返回的IBinder 对象就是IServiceManager的远程接接口

2.从getContextObject 到talkwithDriver

frameworks/base/core/jni/android_util_Binder.cpp

ProcessState::self(),是一个单例,在进程启动的时候进行了初始化,他是上层java方法getContextObject 的native实现

frameworks/base/core/jni/android_util_Binder.cpp

javaObjectForIBinder这个方法是根据IBinder为上层创建对应的BinderProxy.java对象

system/libhwbinder/ProcessState.cpp

这里主要是根据句柄从现有缓存中查找实现,没有的话创建一个新的BpHwBinder对象

system/libhwbinder/BpHwBinder.cpp

BpHwBinder的构造方法里调用了IPCThreadState::self()的方法为当前的BpHwBinder注册一个弱引用,这个注册最终会走告诉binder驱动,我对句柄为handler的对象增加一个若引用,麻烦其他进程的实体对象回收的时候考虑一下

system/libhwbinder/IPCThreadState.cpp

incWeakHandle 往写到驱动的buffer里缓存了BC_INCREFS,这个命令此时并没写入到驱动中,需要等一次transaction执行的时候才会写入,在java层调用任何aidl的函数都会触发transaction.这里就不列出了

frameworks/native/libs/binder/BpBinder.cpp

当上层调用getSystemService的时候,经过binder.java的binder.transact方法,从而调用到BpBinder.cpp里的transact方法,而这个方法调用了单例IPCThreadState.transact方法

IPCThreadState

transact里首先使用writeTransactionData 向buffer里填充parcel好的数据,然后调用waitForResponse里的talkWithDriver 把缓冲数据里BC_INCREFS命令和transaction数据写入到缓存中

talkWithDriver里首先会将缓冲在mOut里的数据写到驱动中,根据流程,也就是包含引用添加命令和transaction数据,待远程执行结束后从mIn读取到BBR_REPLY命令,然后把后续数据填充到返回值Parcel对象中 (mOut,mIn)都是parcel对象

talkWithDriver的参数如果不填默认是true(定义在头文件中),这里向驱动进行了读或者写数据,是读还是写取决于write_size和read_size,按目前的流程,这里首先是执行写入引用增加命令和getSystemService的transaction数据,然后读取返回的数据到mIn中由waitForResponse函数把读取到的数据转换为parcel数据,parcel再一层层抛到java层,最后调用parcel.read系列得到函数的返回值.也就是其他服务的binderproxy对象

至此,驱动上层的所有调用流程分析完毕,下面是驱动层

3.驱动层的命令读写和事务处理

从前一节可以知道,上层在这次方法中有以下重点操作

a.向驱动写入BC_INCREFS 命令和hanlde=0的句柄

b.向驱动写入getSystemService的transaction数据

c.等待驱动返回,读取返回值转为parcel对象

现在一步步分析以上操作在驱动里究竟是怎样做的

以下代码均运行在内核空间,内核空间所有进程都可以访问并且是唯一的,但数据必须由copy_from_user或者_copy_to_user传递

紧接前一节,talkwithdrive执行了binder_ioctl方法,这个方法实际上调用的是驱动binder_ioctl里的实现

可以看到,当上层调用talkwithdriver的时候,调用的oictl的时候,线程将会被挂起,然后对传入的数据进行写入操作,这个写入操作是由方法binder_thread_write来完成的,这个方法的省略实现如下

可以看到,当BC_INCREFS执行的时候时候会从当前进程的binder_refs_desc红黑树查找引用节点,当handle不等于0的时候,这个引用必须存在,当handle等于0时,可以先拿到句柄再创建引用。为什么呢?因为binder_context_mgr_node 是在servicemanger启动的时候向驱动设置的一个节点,他是一个全局变量,在任何进程调用时候都可以拿到它(所有进程共用内核空间)。而其他的节点不允许为空,原因是当前节点的引用必须由其他进程提前创建,具体见核心方法binder_transaction

binder_transaction 是binder中事务处理的核心方法,任何binder调用都会进入到这个方法,一般来说,一次binder接口调用会调用两次 binder_transaction 方法,一次发起,一次回应。

当由service进程或者client调用transaction时候,会进入这个函数,reply为true表示由service进程返回结果client.

注意,其中每个进程下的binder_proc的binder_ref有两颗binder_ref的红黑树,,refs_by_node,refs_by_desc,他们的节点是同用的,只是排序方式不同 ,这个两颗树的插入时间点是一致的(当目标进没有需要传递的binder_node的binder_ref时插入),查询时机不一致,可以简单的说是一个是给其他进程用( refs_by_node ),一个是给自己进程用( refs_by_desc)。

当发现传输的类型是binder实体的时候,如果目标进程没有该 binder_node 的对应引用节点binder_ref( refs_by_node 查询),则帮目标进程创建引用节点,然后将引用节点的句柄传递给目标进程,如果已经有了则不创建,同时继续传递句柄

当发现传输的类型是binder引用的时候,如果binder_ref的实体对象( 用binder在 refs_by_desc 查询,然后由binde_ref 得到实体)binder_node所在进程就是目标进程,则直接向目标进程传递指针,如果binder_ref的实体对象不在目标进程则传递句柄给目标进程

binder代码十分复杂,里面还有大量的引用计数,线程池维护等没有详说,这里仅仅分析了其中的一条流水线