标签:lin 访问 mes 自定义 systems 手机厂商 鲁棒性 现在 机制
Binder是一种IPC通信机制,在android系统中处于核心地位,几乎所有的跨进程通信,或者进程内部系统组件通信都是通过Binder进行交互。 Binder如同其字面意思,将系统各部件粘结起来,形成一个有机的整体。如果不能很好地理解Binder,就肯定不能很好地理解android系统内部运行机制。Binder基于C/S架构而设计,Binder服务端注重优质服务的实现,Binder客户端只需向服务端按事先约定好的协议访问Binder服务端即可,而具体的通信通道建立、请求发起、请求接入、呼应返回由相应的Binder通信模块完成。
图1描述了Binder架构简图,图中将Binder架构分为Framwork、Library及Kernel三大部分,该三大部分也是对应于Android系统的Framework、Library和Kernel。
图1 Binder架构简图
Binder Framwork
包含Camera服务、Activity服务、Window服务、Power管理服务等,是App开发人员最熟悉的一层。
Framework左边包括ActivityManager、WindowManager等系统服务客户端,通过Context.getSystemService方法取得,每个Manager内部定义了一个BinderProxy,用于向远端Binder服务发起IPC调用,并接收处理结果。
Framework右边所括ActivityManagerService、WindowManagerService、PowerManagerService等Binder服务端,用于接收BinderProxy发来的IPC调用请求,并返回处理结果,这些service运行于SystemServer进程。
Binder Framework使用Java语言编写成,主要是为Application层应用提供API及系统服务。Binder Framework经编译打包后,位于framework.jar中。
Binder Library
包含BpBinder、BBinder/JavaBBinder、ProcessState、IPCThreadState等组件。
其中BpBinder对应Binder Framework的BinderProxy,由JNI层的android_util_BinderProxy进行联结,作用是将Binder启用Binder驱动传输调用方法代码、调用参数以及等待IPC调用返回结果。
BBinder (JavaBBinder)对应Binder Framework的Binder,当BBinder接收到Binder客户端调用请求后,通过JNI
调用将调用请求转交给Binder Framework的Binder对象,然后等待Binder对象返回处理结果,再将结果转交给底层传输回Binder客户端。
ProcessState和IPCThreadState是Binder框架底层通信重要的两个类,可归属于Binder HAL层的内容。
每一个App或进程,对应唯一一个ProcessState实例,该实例持有当前进程与Binder驱动的通信状态,定义了非系统App的Binder空间大小、Binder线程数等信息,负责打开、维护、关闭/dev/binder设备。ProcessState在android_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 Library与Binder Kernel使用ioctl进行读写操作。
相比较其他的IPC通信,比如消息机制、共享内存、管道、信号量等,Binder仅需一次内存拷贝,即可让目标进程读取到更新数据,同共享内存一样相当高效,其他的IPC通信机制大多需要2次内存拷贝。
图5描述了Binder内存拷贝的原理示意图,进程A为Binder客户端,在IPC调用前,需将其用户空间的数据拷贝到Binder驱动的内核空间,由于进程B在打开Binder设备(/dev/binder)时,已将Binder驱动的内核空间映射(mmap)到自己的进程空间,所以进程B可以
直接看到Binder驱动内核空间的内容改动。因为Windows/Linux系统,内核空间范围大都为1GB,所以内核空间一般比较有限,所以Binder驱动的内存地址空间也相对较小。
图5 Binder内存拷贝示意图
在XX项目中,Binder异常导致的崩溃时不时会出现一次,图6为一例Binder崩溃日志。
图6 Binder崩溃日志
Binder崩溃,究其原因是Google在android 6.0后,调整了Framework层在捕获到Binder通信过程中产生的异常的错误处理机制,android 6.0之前,Framework捕获到binder通信的异常,并不将异常再抛给应用端,而6.0之后,Framework捕获到binder通信异常,转而重新包装一下该异常为新的异常,再将新异常重新抛出,其目的是让应用端能更好地更合理地对Binder通信异常的处理。
图7为ActivityManager.getRunningAppProcesses方法的7.1版本实现,很明显方法在捕获到RemoteExcetion后转抛出DeadSystemExcetion。
图8为ActivityManager.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.java中BinderProxy.transactNative的C/C++实现。
图11 Binder异常产生源头代码
图12描述了Binder问题的汇总状态。从反馈的log信息归纳,白牌项目遇到的崩溃集中在DeadObject,为图12中黄色背景部分的内容,另外一个Binder异常是TransactionTooLargeException,是我们自己为重现Binder崩溃而遇到的异常。
图12 Binder问题汇总
Binder崩溃问题之所以复杂,除了Binder本身设计及架构的复杂,还有使用模块及使用方式的复杂性,对于排错、寻找解决方案增加了很大的困难。图13显示了App代码三种主要的调用Binder路径,第一种是App直接Binder调用系统服务;第二种方式App先调用Android SDK,SDK再Binder调用系统服务,而系统服务可能Binder回调App接口,也有可能Binder调用其他系统服务,或者调用SDK API;第三种是App调用自己定义的Binder服务,自定义服务再Binder调用系统服务,或者调用SDK API。
图13展示了Binder调用的广度和调用层次的深度,其调用深度甚至可达到无究大,意味着圈定Binder问题可能的发生地或统筹Binder问题发生地分布,几乎是件不可能的事。因此,缓解Binder问题比较好的思路是拦截思路或在dalvik修改相关类的字节码。
图13 Binder问题产生路径
采用IBinder.linkToDeath注册IBinder.DeathRecipient回调。
大致思路是Binder客户端调用IBinder的linkToDeath方法注册回调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方案一代码示意
使用Hook拦截Framework层服务异常。
思路
使用InvocationHandler动态代理系统服务类,在此基础上try-catch住系统服务的RemoteException。图16中类ServiceInterceptor的invoke方法为本方案核心,主要思路是在IPC调用前使用Binder.flushPendingCommands释放Binder内存,然后进入try-catch内动态代理调用系统服务方法,如果系统服务抛出异常,流程则转至catch(RemoteException rex)处理,最后在invoke方法返回前再次调用Binder.flushPendingCommands清理Binder内存。
方案优点
可以捕获系统服务异常并处理。
方案缺点
Invoke方法在捕获到异常后,返回null值或0值给调用者,如果调用者没有对返回结果进行校验,有可能会导致NullPointerException或业务逻辑不正确。
图16方案二代码示意
方案三
同方法二,只是拦截异常地点不同,是在BinderProxy的transact方法进行拦截。
BinderProxy的transact方法是Binder客户端的消息集散地,如同Handler的handleMessage,在此截获消息,首先是可以掌握进出本应用的消息流动态,其次是方便拦截和善后处理。
图17为BinderProxy的类定义,对于Binder崩溃问题,transact方法是关键,DeadObjecException是由transact内部部调用JNI方法transactNative产生,在transact捕获DeadObjectException,实为java层第一时间捕获,防止该异常继续向java层发散。
图17 BinderProxy类定义
图17为AIDL封装Binder客户端的代码示意,mRemote为BinderProxy实例,从图18中可看出,AIDL封装并不关心mRemote.transact的返回状态值,具体值包装在_reply中。
图18 AIDL客户端方法示意
图19、图20为如何拦截Ams中BinderProxy.transact方法的代码示意。注意,每个服务的编码结构不一样,需要适当调整代码以拦截BinderProxy异常。
图19拦截代码示意
图20 拦截代码示例
方案四:
该方法是逆向工程的思路,需要了解Dalvik内部运行机制,采用静态/动态修改dex字节码方式,修改BinderProxy的transact方法。
Binder崩溃问题,反映的是Android6.0以后的版本一改之前各版本处理方式,Google将Framework层Binder调用产生的异常直接抛给App层,让Android6.0以前的写好的App猝不及防,而且google也没有明确提出这方面的最佳实践建议或解决方案,目前为止,尚未找到行业内的公认解决方案,可能原因是Android6.0之前,市面上主要互联网公司的产品早已面世,现在无非是迁移到新的系统版本而已,完全基于Android6.0的app或新产品很少。各App厂商或多或少都会遇到Binder问题,取决于手机硬件环境和手机厂商对android系统的定制深度。我感觉各App厂商现在都在寻求一种解决Binder崩溃的方案,只不过在大多数App厂商来看,Binder问题崩溃的情况较少,其涉及模块和原因比较复杂,和系统软硬件有关,所以他们暂时将Binder问题纳为待研究问题状态,可以暂时容忍少量的App崩溃现像,因为不像白牌项目受到客户的严格稳定性指标所限。
标签:lin 访问 mes 自定义 systems 手机厂商 鲁棒性 现在 机制
原文地址:https://www.cnblogs.com/tgltt/p/9550113.html