该系列文章总纲链接:专题分纲目录 android 系统核心机制 binder
1binder调试基础解读
1.1 binder的设备节点简单解读
binder调试相关的设备节点再目录/sys/kernel/debug/binder目录下,节点内容如下所示:
这里可以看到binder下所有通信进程信息,打开后cat 其中一个,内容如下所示:
这里包含了该进程中binder相关内容,比如:Binder线程池(thread)、实体对象(binder_node)、引用对象(binder_ref)、内核缓冲区(buffer)等信息。
1.2 binder 调试日志开关解读
binder driver存在16类调试log开关,debug_mask表信息在binder.c文件中的定义为:
enum {
BINDER_DEBUG_USER_ERROR = 1U << 0,
BINDER_DEBUG_FAILED_TRANSACTION = 1U << 1,
BINDER_DEBUG_DEAD_TRANSACTION = 1U << 2,
BINDER_DEBUG_OPEN_CLOSE = 1U << 3,
BINDER_DEBUG_DEAD_BINDER = 1U << 4,
BINDER_DEBUG_DEATH_NOTIFICATION = 1U << 5,
BINDER_DEBUG_READ_WRITE = 1U << 6,
BINDER_DEBUG_USER_REFS = 1U << 7,
BINDER_DEBUG_THREADS = 1U << 8,
BINDER_DEBUG_TRANSACTION = 1U << 9,
BINDER_DEBUG_TRANSACTION_COMPLETE = 1U << 10,
BINDER_DEBUG_FREE_BUFFER = 1U << 11,
BINDER_DEBUG_INTERNAL_REFS = 1U << 12,
BINDER_DEBUG_BUFFER_ALLOC = 1U << 13,
BINDER_DEBUG_PRIORITY_CAP = 1U << 14,
BINDER_DEBUG_BUFFER_ALLOC_ASYNC = 1U << 15,
};
debug_mask表信息 详细解读如下:
debug_mask | mask值 | 解释 |
BINDER_DEBUG_USER_ERROR | 1 | 用户使用错误 |
BINDER_DEBUG_FAILED_TRANSACTION | 2 | transaction失败 |
BINDER_DEBUG_DEAD_TRANSACTION | 4 | transaction死亡 |
BINDER_DEBUG_OPEN_CLOSE | 8 | open/close/mmap |
BINDER_DEBUG_DEAD_BINDER | 16 | binder/node死亡 |
BINDER_DEBUG_DEATH_NOTIFICATION | 32 | binder死亡通知 |
BINDER_DEBUG_READ_WRITE | 64 | read/write |
BINDER_DEBUG_USER_REFS | 128 | binder引用计数 |
BINDER_DEBUG_THREADS | 256 | binder_thread信息 |
BINDER_DEBUG_TRANSACTION | 512 | transaction信息 |
BINDER_DEBUG_TRANSACTION_COMPLETE | 1024 | transaction完成 |
BINDER_DEBUG_FREE_BUFFER | 2048 | 可用buffer |
BINDER_DEBUG_INTERNAL_REFS | 4096 | binder内部引用计数 |
BINDER_DEBUG_BUFFER_ALLOC | 8192 | 同步内存分配信息 |
BINDER_DEBUG_PRIORITY_CAP | 16384 | 调整binder线程的nice值 |
BINDER_DEBUG_BUFFER_ALLOC_ASYNC | 32768 | 异步内存分配信息 |
一般是通过设置节点/sys/module/binder/parameters/debug_mask内的值
来动态控制选择开启上表中的debug log,比如要看binder的read/write信息,则直接执行命令:
echo 64 > /sys/module/binder/parameters/debug_mask
如果还需要更多信息,比如BINDER_DEBUG_OPEN_CLOSE的信息,那么就执行命令(这里的72=64+8,实际上就是对应的标识位置1即可):
echo 72 > /sys/module/binder/parameters/debug_mask
总之,只需将各个开关的mask值相加(对应标识位置1)写入debug_mask即可。
2 问题总结
说明:binder如果通信出现问题,那么多半是app或者自定义service写的有问题造成的(比如自己写的 或者 ODM/OEM厂商的某些增值服务。。。),因此binder很多时候主要是用于辅助分析和定位添加内容后导致的问题。
2.1 binder内存泄漏问题总结
binder内存泄漏(也称作binder leak),比如在binder通信中如果传输中使用的parcel没有有效释放(release),会造成buffer空间越来越少,积累到一定程度就会出现binder传输的消息在另外一端无法接收。正常使用IPCThread的API操作binder会自动往driver发送FREE_BUFFER的指令做释放操作,除非是单独对binder做了ioctl相关操作。那么如何确认是该问题呢?
使用cat命令查看transaction下的内容,如下所示:
这里一行buffer代表有一个binder通信在占用buffer的空间。如果该proc进程下的buffer的行数持续增长不减,基本上你就能判断是binder内存泄漏的问题了,接下来就定位到目标进程了,再接下来就要针对对应的进程进行详细分析了。
2.2 binder线程到达上限
如果app向service发起请求的频率过高,service端如果对所有的业务执行都加了锁的话,则会导致service端用于接收处理binder事件的线程全部卡住,当线程池(default 16个线程)耗尽之后,就无法再处理请求。如果这个时候app的主线程如果再调用该serivce提供的方法,就很容易出现ANR的问题。那么如何确认是该问题呢?
使用cat命令查看transaction下的内容,如下所示:
如果线程池耗尽则在transactions文件日志中 会连续显示16句。为解决这种线程池耗尽的问题,应对的方案是 从app端去限制请求频率,不要频繁的请求server。
2.3 oneway相关问题
有的自定义service端的方法 存在 间歇性的卡顿,但一般情况下并不会阻塞,但是在压力测试中有一定几率出现阻塞情况,关键是一旦出现阻塞就很容易导致部分关键功能整体失效。这时在编写aidl时需要添加oneway方法,使得即便发生了错误产生阻塞也不至于卡死。但这样做真的就可以了吗?这里不绕弯子了,事实上还需要调整APP请求server的频率,为什么呢?因为如果客户端你调用的频率过高,就会导致服务端binder线程到达上限的问题了。
这里详细解读下oneway的两个特性:异步调用和串行化处理。具体解读如下:
- 异步调用是指应用向 binder 驱动发送数据后不需要挂起线程等待 binder 驱动的回复,而是直接结束。像一些系统服务调用应用进程的时候就会使用 oneway,比如 AMS 调用应用进程启动 Activity,这样就算应用进程中做了耗时的任务,也不会阻塞系统服务的运行。
- 串行化处理是指对于一个服务端的 AIDL 接口而言,所有的 oneway 方法不会同时执行,binder 驱动会将他们串行化处理,排队一个一个调用。
@1 对于第1点,要注意的是 所谓异步指的是 binder 驱动发送数据后不需要挂起线程等待 binder 驱动的回复,(注意:是不需要,是协议流程发生变化),对比如下:
binder.c的源码中关键代码为:
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
//...
if (reply) {//有reply的情况
binder_pop_transaction(target_thread, in_reply_to);
} else if (!(t->flags & TF_ONE_WAY)) {//没有reply且没有oneway标志位的情况
t->need_reply = 1;
t->from_parent = thread->transaction_stack;
thread->transaction_stack = t;
} else {//没有reply且有oneway标志位的情况
if (target_node->has_async_transaction) {
target_list = &target_node->async_todo;
target_wait = NULL;//这里表示的是线程不用等待,所以在图上会没有后面的交互
} else
target_node->has_async_transaction = 1;
}
//...
}
@2 对于第2点,只有确切的回答了以下几个问题才算是真的理解。
假设server端进程A中有如下两个Binder服务A1和A2,这两个服务都有两个异步的接口AE1和AE2。如下所示:
interface A1 {
oneway void AE1();//异步执行2-3秒
oneway void AE2();//同上
}
interface A2 {
oneway void AE1();//同上
oneway void AE2();//同上
}
B进程和C进程均为server进程A的客户端,问题如下:
- 如果进程B和进程C同一时刻分别调用A1.AE1()和A2.AE1(),请问进程A能否同时响应这两次Binder调用并执行?
- 如果进程B和进程C同一时刻分别调用A1.AE1()和A1.AE1(),请问进程A能否同时响应这两次Binder调用并执行?
- 如果进程B和进程C同一时刻分别调用A1.AE1()和A1.AE2(),请问进程A能否同时响应这两次Binder调用并执行?
请思索片刻后再答案哦~,答案在最下方。
。。。
。。。
。。。
1答案为:可以同时执行,2和3 答案均为:需要一个个排队执行。如果你会打错了,说明你需要回头看下代码认真分析了。