从TransactionTooLargeException谈到binder的内存管理

前言

最近在Activity间传输数据时遇到了TransactionTooLargeException这个异常。了解过binder的看到transaction估计就能猜到这个异常应该跟binder有关。但还不敢百分之百确定,有两个猜测,第一是binder驱动的共享内存不足;第二是Parcel打包数据时有内存限制。网上搜到stackoverflow上有人说过binder驱动限制数据传输的内存为1m。这个异常到底是怎么来的,跟binder驱动的内存管理有什么关系?我们接下来就分析一下。

binder跨进程调用的大致流程

既然是跟binder有关,还是先把流程给弄清楚,方便代码跟踪。以ApplicationThread为例,见下图,蓝色为client端,绿色为server端,颜色较深的是native层,颜色较浅的是java层。

上面就是client端(AMS)到server端(目标Activity所在进程的ApplicationThread)的调用流程,以binder驱动为中心,两边都是一一对应的。

Intent的数据传输流程

我们的第二个猜测跟Parcel有关,也来理一下数据是怎么从Intent流向Parcel的。
这里写图片描述
顶部的代表着具体的类,其他的全是方法,方法与类通过颜色相关连。如果我们顺着这整个流程走下去,会发现其中根本没有内存的限制,也没见到在哪抛出了这个异常。可以暂时把这个猜测放一边了,继续回到binder上来。

TransactionTooLargeException异常

首先看一下这个异常的堆栈信息

android.os.TransactionTooLargeException
            at android.os.BinderProxy.transact(Native Method)
            at android.app.ApplicationThreadProxy.scheduleLaunchActivity(ApplicationThreadNative.java:755)
            at com.android.server.am.ActivityStackSupervisor.realStartActivityLocked(ActivityStackSupervisor.java:976)
            at com.android.server.am.ActivityStackSupervisor.attachApplicationLocked(ActivityStackSupervisor.java:394)
            at com.android.server.am.ActivityManagerService.attachApplicationLocked(ActivityManagerService.java:5027)
            at com.android.server.am.ActivityManagerService.attachApplication(ActivityManagerService.java:5087)
            at android.app.ActivityManagerNative.onTransact(ActivityManagerNative.java:395)

这个时候binder调用的流程图就派上用场了。up主一开始还没梳理过这个流程,很难找到这个异常是怎么产生的。首先去看binder驱动,在binder_alloc_buf函数中确实有内存的限制,如果超过了这个限制会返回一个错误代码BR_FAILED_REPLY,但异常并不是从这里抛出的。后来搜索TransactionTooLargeException的时候看到有网友提到这个异常在android_util_binder中有抛出。然后一直往前跟踪,最终回到了binder_alloc_buf。寻找的详细过程不多说,来理一下这个异常的产生过程:
binder驱动的binder_transaction函数中会调用binder_alloc_buf来分配一段合适的内存,其中有大小的限制,但是binder_transaction函数是void的,内部如果出错,只能通过参数thread的return_error(值为BR_FAILED_REPLY)变量来判断,然而调用它的binder_thread_write只用该变量来退出循环,当从此函数返回后,继续执行binder_thread_read,最后将thread的return_error值写到用户态,最终该错误值由IPCThreadState转换为FAILED_TRANSACTION后返回给android_util_Binder(中间经过BPBinder),由它抛出TransactionTooLargeException异常。
至此,这个异常的产生过程也大致清楚了,确实是因为binder驱动对内存的限制引起的。下面就来看下binder驱动是怎么管理内存的。

binder驱动的内存管理

  1. 分配
    用户程序通过ProcessState调用open_driver、mmap分配一段虚拟内存(ServiceManager申请128k内存,其他一般申请1M-8k内存),并只对这段虚拟内存映射了一个物理页的大小,然后将整段虚拟内存存入free_buffers红黑树和buffers链表中。实际需要使用到内存时,调用binder_alloc_buf方法进行分配,查找free_buffers中不小于所需空间的最佳匹配内存,调用binder_update_page_range进行映射,并将这段内存从free_buffers中删除,插入到allocated_buffers。如果这段新的内存比所需内存大,将剩余的内存再放回free_buffers中。free_buffers红黑树是按照内存的大小来排序的,而内存的大小是根据buffers链表中下一个内存段的首地址减去该内存段的首地址得到。这个方法好像不是很起眼,如果不仔细看,就不好理解binder驱动是怎么处理内存碎片问题的。
  2. 释放
    在binder驱动中有一个对应的函数binder_free_buf,它的触发流程大致如下:
Parcel.java#recycle --> Parcel.java#freeBuffer --> android_os_Parcel#android_os_Parcel_freeBuffer
 --> Parcel#freeData --> Parcel#freeDataNoInit --> IPCThreadState#freeBuffer --> BC_FREE_BUFFER --> binder_free_buf

以AMS启动一个Activity为例,相关的数据最初都放在Intent的Bundle中。随后,AMS调用目标Activity所在进程的ApplicationThreadProxy的scheduleLaunchActivity函数,在此方法中obtain一个Parcel并将Intent的数据打包到Parcel中,等待执行transact,最后调用Parcel的recycle方法。但这释放的并非binder驱动中分配的内存,继续往下走。当请求到达binder驱动时,就需要分配共享内存(目标Activity)来复制Intent的数据,最终数据经过IPCThreadState、JavaBBinder、Binder.java,在Binder.java#execTransact方法中obtain了一个Parcel,并将共享内存设置了进去,然后执行onTransact,最后终于见到了苦苦寻找的recycle方法。
看上面的流程我们知道,这个recycle方法最终触发了binder_free_buf,binder驱动的内存释放是在binder_free_buf操作的,这个函数会将需要释放的内存空间从allocated_buffers中删除,并且将它前后相邻的空闲内存段从free_buffers中一并删除,这样相邻空闲的内存段又合为一个整体(碎片合并),最后在插入到free_buffers中。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值