问题和解决方案
想要在自定义安卓系统服务中传输较大的数据,由于系统服务使用binder进行IPC,而binder最大只能传输(1mb-8kb)的数据,目前想到的解决方案是把数据拆分为多份,然后多次传输。本文记录这个解决方案遇到的一些坑
先放上关键的代码:
auto myService = aidl::android::MyService::fromBinder(ndk::SpAIBinder(AServiceManager_getService("myservice")));
int pack_size = 0x7E000; // 必须为4kb整数倍,且长度最好在1mb-8kb的一半以内
int count = (size / pack_size); // size是数据长度
if(count >= 255){
return false;
}
// 分块传输, binder限制,每次只能传输1mb不到的数据
for(int i=0; i < count; i++){
// begin是要传输的数据的首地址, size是数据长度
std::vector buff(begin + i*pack_size, begin + (i+1)*pack_size);
// 传输pid、begin等参数,用于标记具体数据来源,传输参数i,用于标记块的顺序,方便服务端进行文件重组
myService->trans_data(pid, begin, size, i, buff);
usleep(5); //第二章会说明为什么要sleep
}
std::vector buff(begin + count*pack_size, begin + size);
// 这里别忘了把额外的数据也补齐到4kb的整数倍,这里略过了补齐的代码
myService->trans_data(pid, begin, size, count, buff);
usleep(5);
数据必须为4kb的整数倍
刚开始我把数据拆分为每块1000000byte,可是服务端只接收到了999424byte数据,接收到的部分数据我保存为文件之后,如下所示:
-rw-rw---- 1 root root 999424 2023-08-09 16:24 part_2
-rw-rw---- 1 root root 999424 2023-08-09 16:24 part_3
-rw-rw---- 1 root root 999424 2023-08-09 16:24 part_4
-rw-rw---- 1 root root 999424 2023-08-09 16:24 part_5
999424换成16进制,就是0xF4000,是4kb的整数倍,这里我没有进一步确认是vector的问题还是binder的问题(也不知道为啥要舍弃掉多余的字符,而不是填充0补齐到整数倍,想弄懂原理估计很麻烦,这里先放着了),总之先把每块的数据调整为4kb的倍数。
调整后客户端发送的和服务端接收到的数据都是999424byte,保持一致,这个问题解决。
buffer释放问题
服务端处理数据的速度过慢时,会导致buffer来不及释放的问题。
比如每次只传999504byte数据,此时一个binder的buffer还会剩下40880byte内存,如果下一条数据已经准备开始传输了,同时上一条数据还没有处理完,buffer没有释放,这时只能在剩下的40880byte内存中申请999504byte的数据,很明显是不够的
注:我每块大小为999424byte,但是log显示要申请999504byte数据,猜测是其他的参数或者某些额外的数据结构多占了部分空间,这个对我最终目标没影响所以也不过多研究了
仔细观察报错,可以发现正常传输数据的间隔大概在3ms,而报错的地方与上一条log只差了1ms,于是修改代码,在每次循环中加入5ms的等待时间
15:46:26.473 1258 1310 W ==Mytag==
15:46:26.476 1258 1310 W ==Mytag==
15:46:26.475 0 0 E : c4 5038 binder_alloc: 1258: binder_alloc_buf size 999504 failed, no address space
15:46:26.475 0 0 E : c4 5038 binder_alloc: allocated: 999504 (num: 1 largest: 999504), free: 40880 (num: 1 largest: 40880)
15:46:26.475 0 0 I : c4 5038 binder: 5038:5038 transaction failed 29201/-28, size 999504-0 line 3317
15:46:26.480 1258 1310 W ==Mytag==
15:46:26.483 1258 1310 W ==Mytag==
15:46:26.486 1258 1310 W ==Mytag==
但是修改之后还是会报同样的错,只不过频率低了很多,连续多次传输只报了一次错误,没修改之前几乎每次都会报错。而且观察修改后的log,其中的Mytag的时间间隔依旧在3ms,那么剩下的问题肯定不在我们客户端的数据传输间隔上了
重点转移到服务端,众所周知,binder服务端是可以多线程处理任务的,binder多个线程是共用一块buffer内存,所以减少多线程数量,即可减少多个线程争抢一块内存的情况。我这里用的是android系统服务的ndk后端,使用ABinderProcess_setThreadPoolMaxThreadCount
来设置最大线程数,我这里设定为5,但是依旧会出现上面的报错。
最后想到一个办法:把每块的大小限制在(1mb-8kb)的一半以内(一半是0x7F000,我最终设定为0x7E000),那么即使出现了上面没有及时释放buffer的情况,剩下的一半空间也能够支持一次数据传输。在循环加入等待时间之后,出现报错的频率已经大幅减少,在加上大小限制在一半的操作,最后终于稳定的通过了测试,多轮数据传输测试下来,基本没有再出现过报错了。