Binder问题分享

Posted tgltt

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Binder问题分享相关的知识,希望对你有一定的参考价值。

1Binder架构简介

     Binder是一种IPC通信机制,在android系统中处于核心地位,几乎所有的跨进程通信,或者进程内部系统组件通信都是通过Binder进行交互。 Binder如同其字面意思,将系统各部件粘结起来,形成一个有机的整体。如果不能很好地理解Binder,就肯定不能很好地理解android系统内部运行机制。Binder基于C/S架构而设计,Binder服务端注重优质服务的实现,Binder客户端只需向服务端按事先约定好的协议访问Binder服务端即可,而具体的通信通道建立、请求发起、请求接入、呼应返回由相应的Binder通信模块完成。

        图1描述了Binder架构简图,图中将Binder架构分为FramworkLibraryKernel三大部分,该三大部分也是对应于Android系统的FrameworkLibraryKernel

技术分享图片

1 Binder架构简图

  • Binder Framwork

包含Camera服务、Activity服务、Window服务、Power管理服务等,是App开发人员最熟悉的一层。

    Framework左边包括ActivityManagerWindowManager等系统服务客户端,通过Context.getSystemService方法取得,每个Manager内部定义了一个BinderProxy,用于向远端Binder服务发起IPC调用,并接收处理结果。

    Framework右边所括ActivityManagerServiceWindowManagerServicePowerManagerServiceBinder服务端,用于接收BinderProxy发来的IPC调用请求,并返回处理结果,这些service运行于SystemServer进程。

    Binder Framework使用Java语言编写成,主要是为Application层应用提供API及系统服务。Binder Framework经编译打包后,位于framework.jar中。


  • Binder Library

包含BpBinderBBinder/JavaBBinderProcessStateIPCThreadState等组件。

       其中BpBinder对应Binder FrameworkBinderProxy,由JNI层的android_util_BinderProxy进行联结,作用是将Binder启用Binder驱动传输调用方法代码、调用参数以及等待IPC调用返回结果。

BBinder (JavaBBinder)对应Binder FrameworkBinder,当BBinder接收到Binder客户端调用请求后,通过JNI

       调用将调用请求转交给Binder FrameworkBinder对象,然后等待Binder对象返回处理结果,再将结果转交给底层传输回Binder客户端。

    ProcessStateIPCThreadStateBinder框架底层通信重要的两个类,可归属于Binder HAL层的内容。

每一个App或进程,对应唯一一个ProcessState实例,该实例持有当前进程与Binder驱动的通信状态,定义了非系统AppBinder空间大小、Binder线程数等信息,负责打开、维护、关闭/dev/binder设备。ProcessStateandroid_util_Binder.cpp中的android_util_BinderInternal的方法中初始化。

2描述了 ProcessState初始化的代码,其主要工作是打开Binder驱动设备,然后将Binder设备mmap到当前进程的地址空间,地址空间范围一般为1MB-8KB,起始地址由Linux系统调用mmap自行根据当前进程情况确定。

技术分享图片

2 ProcessState初始化代码

        图3描述了ProcessState打开Binder驱动设备的过程。

技术分享图片

3 ProcessState打开Binder设备代码

IPCThreadState记录了IPC线程的状态,用于与系统内核中的Binder驱动进行具体的交互通信,通信方式使用Linux系统调用ioctl。每接收到一个新的Binder客户端请求,Binder服务端会重新建立一个线程和IPCThreadState记录对该Binder客户端的通信,最大IPC线程数为15,在ProcessState中定义,见图4

技术分享图片

4 ProcessState常量定义


  • Binder Kernel

       该部分模块运行于Linux系统内核,包含Binder驱动,Binder LibraryBinder Kernel使用ioctl进行读写操作。

相比较其他的IPC通信,比如消息机制、共享内存、管道、信号量等,Binder仅需一次内存拷贝,即可让目标进程读取到更新数据,同共享内存一样相当高效,其他的IPC通信机制大多需要2次内存拷贝。

       图5描述了Binder内存拷贝的原理示意图,进程ABinder客户端,在IPC调用前,需将其用户空间的数据拷贝到Binder驱动的内核空间,由于进程B在打开Binder设备(/dev/binder)时,已将Binder驱动的内核空间映射(mmap)到自己的进程空间,所以进程B可以

       直接看到Binder驱动内核空间的内容改动。因为Windows/Linux系统,内核空间范围大都为1GB,所以内核空间一般比较有限,所以Binder驱动的内存地址空间也相对较小。

技术分享图片

 图5 Binder内存拷贝示意图


2Binder崩溃问题

      在XX项目中,Binder异常导致的崩溃时不时会出现一次,图6为一例Binder崩溃日志。

技术分享图片

6 Binder崩溃日志

Binder崩溃,究其原因是Googleandroid 6.0后,调整了Framework层在捕获到Binder通信过程中产生的异常的错误处理机制,android 6.0之前,Framework捕获到binder通信的异常,并不将异常再抛给应用端,而6.0之后,Framework捕获到binder通信异常,转而重新包装一下该异常为新的异常,再将新异常重新抛出,其目的是让应用端能更好地更合理地对Binder通信异常的处理。

       图7ActivityManager.getRunningAppProcesses方法的7.1版本实现,很明显方法在捕获到RemoteExcetion后转抛出DeadSystemExcetion

       图8ActivityManager.getRunningAppProcesses方法的6.0版本实现,方法捕获到RemoteException后返回null

技术分享图片

7 AcitivityManager方法的anroid 7.1版本

技术分享图片

8 AcitivityManager方法的anroid 6.0版本

       图9分析了图6崩溃日志更深一些的方法调用栈,DeadObjectException等异常抛出点在android_util_Binder.cpp中的signalExceptionForError函数。

技术分享图片

9 Binder崩溃方法调用栈示例


       图10描述了android_util_Binder.cpp中的signalExceptionForError函数定义。

技术分享图片

10 Binder异常产生源头代码


       图11描述了android_util_Binder.cpp中的android_os_BinderProxy_transact函数,即Binder.javaBinderProxy.transactNativeC/C++实现。

技术分享图片

11 Binder异常产生源头代码


3Binder问题汇总

       图12描述了Binder问题的汇总状态。从反馈的log信息归纳,白牌项目遇到的崩溃集中在DeadObject,为图12中黄色背景部分的内容,另外一个Binder异常是TransactionTooLargeException,是我们自己为重现Binder崩溃而遇到的异常。

技术分享图片

12 Binder问题汇总


      Binder崩溃问题之所以复杂,除了Binder本身设计及架构的复杂,还有使用模块及使用方式的复杂性,对于排错、寻找解决方案增加了很大的困难。图13显示了App代码三种主要的调用Binder路径,第一种是App直接Binder调用系统服务;第二种方式App先调用Android SDKSDKBinder调用系统服务,而系统服务可能Binder回调App接口,也有可能Binder调用其他系统服务,或者调用SDK API;第三种是App调用自己定义的Binder服务,自定义服务再Binder调用系统服务,或者调用SDK API

          图13展示了Binder调用的广度和调用层次的深度,其调用深度甚至可达到无究大,意味着圈定Binder问题可能的发生地或统筹Binder问题发生地分布,几乎是件不可能的事。因此,缓解Binder问题比较好的思路是拦截思路或在dalvik修改相关类的字节码。

技术分享图片

13 Binder问题产生路径

4、解决方案演变历程

4.1方案一

采用IBinder.linkToDeath注册IBinder.DeathRecipient回调。

       大致思路是Binder客户端调用IBinderlinkToDeath方法注册回调IBinder.DeathRecipient,以便于当Binder服务端挂掉时,可即时收到Binder服务端崩溃的消息,此后Binder客户端可启动错误处理机制,或等待Binder服务端恢复运行,然后再继续访问。但该方案有一个盲点,如图14所示,当Binder服务端崩溃时,由于是C/S架构,服务端崩溃消息到达客户端需要一定的时间,如果客户端在服务端崩溃消息到达前,仍继续IPC调用服务端接口,则仍然有可能收到DeadObjectException,这是该方法的盲点。另外,使用此方案,也要求App在设计之初就要做相应的容错处理机制,而这种机制是大多数老应用程序所不俱备的,究其原因是Binder机制虽是系统重要的机制,但其被各种上层封装所掩埋,大多数程序员不易直接接触Binder机制或Binder机制引出的问题,所以在设计、编写程序时不会考虑到Binder服务端挂掉时的错误处理,这种处理和一般的异常处理是不一样的,涉及到跨进程访问,以及UI即时响应,以免ANR等方面。

        系统服务间大多也采用该方案监听远端服务的运行状态,比如SurfaceFlinger监听WindowManagerService

        该方案因引入成本太高,目前暂不引入项目,不过对App后续新模块的开发有指导意义,可基于方案写出一套完善的、基于IPC调用的适于android系统的较强鲁棒性的功能模块。

技术分享图片

14 Binder C/S架构蔽端

       图15为方案一的代码示意。

技术分享图片

15方案一代码示意


4.2方案二

         使用Hook拦截Framework层服务异常。

思路

         使用InvocationHandler动态代理系统服务类,在此基础上try-catch住系统服务的RemoteException。图16中类ServiceInterceptorinvoke方法为本方案核心,主要思路是在IPC调用前使用Binder.flushPendingCommands释放Binder内存,然后进入try-catch内动态代理调用系统服务方法,如果系统服务抛出异常,流程则转至catch(RemoteException rex)处理,最后在invoke方法返回前再次调用Binder.flushPendingCommands清理Binder内存。


方案优点

         可以捕获系统服务异常并处理。


方案缺点

   Invoke方法在捕获到异常后,返回null值或0值给调用者,如果调用者没有对返回结果进行校验,有可能会导致NullPointerException或业务逻辑不正确。

技术分享图片

16方案二代码示意



4.3展望

  • 方案三

同方法二,只是拦截异常地点不同,是在BinderProxytransact方法进行拦截。

BinderProxytransact方法是Binder客户端的消息集散地,如同HandlerhandleMessage,在此截获消息,首先是可以掌握进出本应用的消息流动态,其次是方便拦截和善后处理。

17BinderProxy的类定义,对于Binder崩溃问题,transact方法是关键,DeadObjecException是由transact内部部调用JNI方法transactNative产生,在transact捕获DeadObjectException,实为java层第一时间捕获,防止该异常继续向java层发散。

技术分享图片

17 BinderProxy类定义

        图17AIDL封装Binder客户端的代码示意,mRemoteBinderProxy实例,从图18中可看出,AIDL封装并不关心mRemote.transact的返回状态值,具体值包装在_reply中。

技术分享图片

18 AIDL客户端方法示意


19、图20为如何拦截AmsBinderProxy.transact方法的代码示意。注意,每个服务的编码结构不一样,需要适当调整代码以拦截BinderProxy异常。

技术分享图片

19拦截代码示意

技术分享图片

20 拦截代码示例

  • 方案四:

该方法是逆向工程的思路,需要了解Dalvik内部运行机制,采用静态/动态修改dex字节码方式,修改BinderProxytransact方法。


5、总结

Binder崩溃问题,反映的是Android6.0以后的版本一改之前各版本处理方式,GoogleFrameworkBinder调用产生的异常直接抛给App层,让Android6.0以前的写好的App猝不及防,而且google也没有明确提出这方面的最佳实践建议或解决方案,目前为止,尚未找到行业内的公认解决方案,可能原因是Android6.0之前,市面上主要互联网公司的产品早已面世,现在无非是迁移到新的系统版本而已,完全基于Android6.0app或新产品很少。各App厂商或多或少都会遇到Binder问题,取决于手机硬件环境和手机厂商对android系统的定制深度。我感觉各App厂商现在都在寻求一种解决Binder崩溃的方案,只不过在大多数App厂商来看,Binder问题崩溃的情况较少,其涉及模块和原因比较复杂,和系统软硬件有关,所以他们暂时将Binder问题纳为待研究问题状态,可以暂时容忍少量的App崩溃现像,因为不像白牌项目受到客户的严格稳定性指标所限。

 















以上是关于Binder问题分享的主要内容,如果未能解决你的问题,请参考以下文章

Binder 机制Native 层 Binder 机制分析 ( service_manager.c | 开启 Binder | 注册 Binder 进程上下文 | 开启 Binder 循环 )(代码片

Android Binder 跟踪

android Binder机制

Android Binder实现浅析-Binder驱动

系统稳定性 - 调优3.4 常见Binder相关异常调优总结

Binder 机制分析 Android 内核源码中的 Binder 驱动源码 binder.c ( googlesource 中的 Android 内核源码 | 内核源码下载 )