Binder驱动调试节点
Binder 调试节点不是把内核结构体静态 dump 到文件。驱动初始化时创建全局 debugfs 入口,进程打开 Binder 设备时创建按 PID 命名的 proc 文件;真正读取时,seq_file 输出函数才在多把锁和临时引用保护下遍历 thread、node、ref、transaction、buffer 与统计数据。
本文面向已经读过 Binder设备打开、Binder事务发送、Binder死亡通知 和 binder_buffer管理 的读者。本文不重复事务算法,而是回答每个调试入口由谁创建、字段来自哪个 owner、读取时锁如何切换,以及如何从输出反查真实源码状态。
调试输出最容易造成的误解,是把一次 cat 当成冻结全驱动后的原子快照。下面的时序图说明 seq_file 读取如何在全局 proc 锁、进程 inner/outer 锁和 node 临时引用之间切换;它对应后文的 print_binder_state()、print_binder_proc() 与 print_next_binder_node_ilocked()。
因此,输出的可信边界不是“所有行属于同一瞬间”,而是遍历期间对象不会因并发释放而被解引用。诊断时应使用 debug_id、PID/TID、node/handle 和连续多次采样建立关联,不能把不同节点、不同文件的一次读取强行拼成事务级一致视图。
1. 节点创建
1.1 全局目录
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_init()
binder_debugfs_dir_entry_root =
debugfs_create_dir("binder", NULL);
binder_for_each_debugfs_entry(db_entry)
debugfs_create_file(
db_entry->name,
db_entry->mode,
binder_debugfs_dir_entry_root,
db_entry->data,
db_entry->fops);
binder_debugfs_dir_entry_proc =
debugfs_create_dir(
"proc",
binder_debugfs_dir_entry_root);全局节点在驱动初始化阶段创建。它们只登记文件名、权限、private data 和 file operations;读取内容在用户打开文件后才动态生成。debugfs 没有挂载或访问权限不足,并不能推出 Binder driver 没有运行。
1.2 进程文件
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_open()
if (binder_debugfs_dir_entry_proc &&
!existing_pid) {
snprintf(strbuf, sizeof(strbuf),
"%u", proc->pid);
proc->debugfs_entry =
debugfs_create_file(
strbuf, 0444,
binder_debugfs_dir_entry_proc,
(void *)(unsigned long)proc->pid,
&proc_fops);
}
if (binder_binderfs_dir_entry_proc &&
!existing_pid) {
binderfs_entry = binderfs_create_file(
binder_binderfs_dir_entry_proc,
strbuf, &proc_fops,
(void *)(unsigned long)proc->pid);
}同一 PID 的多个 Binder context 只创建一个名字相同的 proc 文件,proc_show() 会遍历全局 proc 表并输出该 PID 的所有匹配 context。binderfs proc 文件创建失败只记录 warning,binder_open() 仍然成功。
1.3 删除时机
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_release()、binder_deferred_release()
debugfs_remove(proc->debugfs_entry);
if (proc->binderfs_entry) {
simple_recursive_removal(
proc->binderfs_entry, NULL);
proc->binderfs_entry = NULL;
}
binder_defer_work(
proc, BINDER_DEFERRED_RELEASE);调试文件先删除,线程、node、ref、transaction 和 allocator 随 deferred release 清理。因此“PID 文件消失”和“该 proc 的全部 Binder 对象已经释放”不是同一同步事件。
2. 输出入口
2.1 Show映射
源码文件:kernel/common/drivers/android/binder.c
相关函数:state_show()、stats_show()、transactions_show()、proc_show()
static int state_show(
struct seq_file *m, void *unused)
{
print_binder_state(m, false);
return 0;
}
static int transactions_show(
struct seq_file *m, void *unused)
{
print_binder_transactions(m, false);
return 0;
}
static int proc_show(
struct seq_file *m, void *unused)
{
int pid = (unsigned long)m->private;
/* 遍历 binder_procs 并输出相同 PID。 */
return 0;
}| 节点 | 输出入口 | 主要用途 |
|---|---|---|
state | print_binder_state(false) | 全部 proc、dead node、thread、node、ref、buffer 与 work |
state_hashed | print_binder_state(true) | 与 state 相同,但隐藏原始指针格式 |
transactions | print_binder_transactions(false) | 过滤掉无 transaction/async 状态的空 proc |
transactions_hashed | hashed transactions | transactions 的指针哈希版本 |
stats | print_binder_stats()、proc stats | BC/BR、对象、线程、buffer 与 page 计数 |
transaction_log | transaction log show | 最近普通事务环形日志 |
failed_transaction_log | 同一 show、不同 data | 最近失败事务日志 |
proc/<pid> | proc_show() | 指定 PID 的 state 风格输出 |
2.2 哈希版本
state_hashed 和 transactions_hashed 只改变 node ptr/cookie 等 binder_uintptr_t 字段的显示方式。PID、TID、debug ID、handle、size 和状态文字仍可关联;哈希版不是另一份状态,也不提供更强的一致性。
3. Proc快照
3.1 Thread字段
源码文件:kernel/common/drivers/android/binder.c
相关函数:print_binder_thread_ilocked()
seq_printf(m,
" thread %d: l %02x "
"need_return %d tr %d\n",
thread->pid,
thread->looper,
thread->looper_need_return,
atomic_read(&thread->tmp_ref));l 是 looper 状态位图,need_return 表示线程需要离开 driver loop,tr 是 temporary reference。它们不能直接解释为“线程正在执行服务方法”或“线程一定能够消费 proc work”。
3.2 Transaction栈
源码文件:kernel/common/drivers/android/binder.c
相关函数:print_binder_transaction_ilocked()、print_binder_thread_ilocked()
seq_printf(m,
"%s %d: %pK from %d:%d "
"to %d:%d code %x flags %x "
"pri %d:%d a%d r%d "
"elapsed %lldms",
prefix, t->debug_id, t,
t->from_pid, t->from_tid,
to_proc ? to_proc->pid : 0,
t->to_thread ?
t->to_thread->pid : 0,
t->code, t->flags,
t->priority.sched_policy,
t->priority.prio,
t->is_async, t->is_reply,
elapsed);thread stack 按 t->from == thread 或 t->to_thread == thread 标记 outgoing/incoming;不满足关系时打印 bad transaction。a1 表示 async,r1 表示 reply,elapsed 是 transaction 创建到读取 dump 的墙钟差,不是服务端方法耗时。
3.3 Buffer字段
源码文件:kernel/common/drivers/android/binder.c
相关函数:print_binder_transaction_ilocked()
if (buffer == NULL) {
seq_puts(m, " buffer free\n");
return;
}
if (buffer->target_node)
seq_printf(m, " node %d",
buffer->target_node->debug_id);
seq_printf(m,
" size %zd:%zd offset %lx\n",
buffer->data_size,
buffer->offsets_size,
buffer->user_data -
proc->alloc.vm_start);buffer free 只表示 t->buffer == NULL;它不能证明 allocator 地址已经回到 free tree。offset 是相对 Binder VMA 起点的地址,不是发送方用户地址。
3.4 Pending work
源码文件:kernel/common/drivers/android/binder.c
相关函数:print_binder_work_ilocked()
switch (w->type) {
case BINDER_WORK_TRANSACTION:
print_binder_transaction_ilocked(
m, proc,
transaction_prefix, t);
break;
case BINDER_WORK_RETURN_ERROR:
seq_printf(m,
"%stransaction error: %u\n",
prefix, e->cmd);
break;
case BINDER_WORK_DEAD_BINDER:
seq_printf(m,
"%shas dead binder\n",
prefix);
break;
}work 输出区分 transaction、completion、return error、node、death 和 freeze work。它展示读取瞬间 work 所在队列,不展示该 work 之前已经经历的完整历史。
4. Node与Ref
4.1 Node状态
源码文件:kernel/common/drivers/android/binder.c
相关函数:print_binder_node_nilocked()
seq_printf(m,
" node %d: ... pri %d:%d "
"hs %d hw %d ls %d lw %d "
"is %d iw %d tr %d",
node->debug_id,
node->sched_policy,
node->min_priority,
node->has_strong_ref,
node->has_weak_ref,
node->local_strong_refs,
node->local_weak_refs,
node->internal_strong_refs,
count, node->tmp_refs);hs/hw 是已通知用户态的强弱引用状态,ls/lw 是本地引用,is 是 internal strong refs,iw 是远端 ref 数量,tr 是 temporary refs。它们属于不同所有者,不能简单相加为一个“总引用计数”。
4.2 Async队列
if (node->proc) {
list_for_each_entry(w,
&node->async_todo, entry)
print_binder_work_ilocked(
m, node->proc, " ",
" pending async transaction",
w, hash_ptrs);
}pending async 表示同一 node 的后续 oneway work 正在等待 active buffer 释放。输出没有直接打印 has_async_transaction 文本;需要结合是否出现 async todo、transaction/buffer 和源码不变量判断。
4.3 Ref状态
源码文件:kernel/common/drivers/android/binder.c
相关函数:print_binder_ref_olocked()
binder_node_lock(ref->node);
seq_printf(m,
" ref %d: desc %d %snode %d "
"s %d w %d d %pK\n",
ref->data.debug_id,
ref->data.desc,
ref->node->proc ? "" : "dead ",
ref->node->debug_id,
ref->data.strong,
ref->data.weak,
ref->death);
binder_node_unlock(ref->node);dead node 表示目标 node 已脱离 owner proc;d 是 death attachment 指针,不等于 death callback 已交付。要判断握手阶段还需看 proc todo、delivered_death 和用户态 BC_DEAD_BINDER_DONE。
5. Stats读法
5.1 BC与BR
源码文件:kernel/common/drivers/android/binder.c
相关函数:print_binder_stats()
for (i = 0;
i < ARRAY_SIZE(stats->bc); i++) {
int temp =
atomic_read(&stats->bc[i]);
if (temp)
seq_printf(m, "%s%s: %d\n",
prefix,
binder_command_strings[i],
temp);
}stats 只打印非零计数。它适合确认某类 BC/BR 是否发生、比较前后增量,不适合恢复单次 transaction 的先后关系。
5.2 对象计数
seq_printf(m,
"%s%s: active %d total %d\n",
prefix,
binder_objstat_strings[i],
created - deleted,
created);active 来自 created minus deleted 原子计数,total 是累计创建量;它不是现场重新遍历所有对象得到的强一致数量。读取期间仍可能创建或删除对象。
5.3 Proc统计
源码文件:kernel/common/drivers/android/binder.c
相关函数:print_binder_proc_stats()
seq_printf(m,
" requested threads: %d+%d/%d\n"
" ready threads %d\n"
" free async space %zd\n",
proc->requested_threads,
proc->requested_threads_started,
proc->max_threads,
ready_threads,
free_async_space);这里还能看到 nodes、refs、buffers、pages 和 pending transactions。free async space 是 allocator 地址配额;node async_todo 是 work 队列,两者下降或增长的原因并不相同。
6. Transaction日志
6.1 环形容量
源码文件:kernel/common/drivers/android/binder.c
相关类型:binder_transaction_log
struct binder_transaction_log {
atomic_t cur;
bool full;
struct binder_transaction_log_entry
entry[32];
};普通日志和失败日志各维护一个固定长度环形数组,环绕后覆盖旧记录。它不是持久审计日志,也不能证明“没有记录就没有发生”,高频事务可能在读取前覆盖目标条目。
6.2 条目字段
源码文件:kernel/common/drivers/android/binder.c
相关函数:print_binder_transaction_log_entry()
int debug_id =
READ_ONCE(e->debug_id_done);
smp_rmb();
seq_printf(m,
"%d: %s from %d:%d to %d:%d "
"context %s node %d handle %d "
"size %d:%d ret %d/%d l=%d",
e->debug_id, call_type,
e->from_proc, e->from_thread,
e->to_proc, e->to_thread,
e->context_name, e->to_node,
e->target_handle,
e->data_size, e->offsets_size,
e->return_error,
e->return_error_param,
e->return_error_line);read barrier 用于避免先读字段再读到未完成的 done 标识。return_error_line 只能在相同源码 release 中定位错误标签;升级内核后行号会漂移。
6.3 关联方法
用 debug_id 连接 failed log、transaction log、state transaction 行和 kernel log,再根据 call type、from/to、node、handle、size 与错误码缩小范围。日志只有结果摘要,最终原因仍需回到 binder_transaction()、allocator 或 read/write 分支。
7. 快照一致性
7.1 Proc遍历
源码文件:kernel/common/drivers/android/binder.c
相关函数:print_binder_state()、print_binder_proc()
mutex_lock(&binder_procs_lock);
hlist_for_each_entry(proc,
&binder_procs, proc_node)
print_binder_proc(
m, proc, true, hash_ptrs);
mutex_unlock(&binder_procs_lock);全局 proc list 有独立 mutex;单个 proc 的 threads/nodes/todo 用 inner lock,refs 用 proc outer lock,node 字段又用 node lock。因此一个 dump 会在多个锁区间之间前进,不是冻结全驱动的原子快照。
7.2 Node临时引用
源码文件:kernel/common/drivers/android/binder.c
相关函数:print_next_binder_node_ilocked()
binder_inc_node_tmpref_ilocked(node);
binder_inner_proc_unlock(proc);
if (prev_node)
binder_put_node(prev_node);
binder_node_inner_lock(node);
print_binder_node_nilocked(
m, node, hash_ptrs);
binder_node_inner_unlock(node);
binder_inner_proc_lock(proc);打印 node 时先增加 tmp ref,再释放 proc/dead-node 遍历锁并取得 node lock。这样避免长时间嵌套锁和 node 被并发释放;代价是不同字段可能来自相邻时刻。
7.3 观测边界
state、stats、transactions 和日志文件是分别读取的,每次都可能看到不同时间点。诊断时应该关注稳定关系和连续变化,例如某 transaction 的 elapsed 持续增加、某 proc buffers 持续增长,而不是把四个文件拼成一个绝对一致的全局状态。
8. 排查实例
8.1 线程堆积
- 在
proc/<pid>看 thread looper、ready threads 和 requested threads; - 在
transactions看 incoming/outgoing stack 和 elapsed; - 在 proc todo 看 pending transaction;
- 若 Binder 状态显示线程被占用,再结合用户态线程栈定位服务方法、锁或 I/O。
调试节点能证明 work/transaction 的驱动状态,不能证明用户代码具体卡在哪一行。
8.2 Buffer压力
stats看 buffers、pages、free async space;state看 transaction 的 size/offset 和 buffer 是否仍关联;proc/<pid>看 pending transaction、async todo;- 连续读取判断 buffers 是否回落;
- 回到
BC_FREE_BUFFER和 allocator free 路径确认回收。
buffer free 不是 allocator 空闲的充分条件,page LRU 也不等于 page 已被 shrinker 释放。
8.3 死亡通知
观察 ref 的 dead node/death attachment、proc todo 的 dead binder work、has delivered dead binder,再结合 BR_DEAD_BINDER 与 BC_DEAD_BINDER_DONE。单次 state 只能说明握手所处阶段,不能判定 callback 已在业务线程完成。
9. 源码导航
rg -n "binder_init|binder_open|binder_release|debugfs_create|binderfs_create_file" \
kernel/common/drivers/android/binder.c
rg -n "state_show|stats_show|transactions_show|proc_show|print_binder_proc" \
kernel/common/drivers/android/binder.c
rg -n "print_binder_thread|print_binder_node|print_binder_transaction|print_binder_work" \
kernel/common/drivers/android/binder.c
rg -n "print_binder_transaction_log|binder_transaction_log_failed" \
kernel/common/drivers/android/binder.c如果能说明为什么 proc 文件消失不等于 deferred release 完成、为什么 node 打印需要 tmp ref、为什么 stats 不能重建单次事务、为什么 buffer free 不等于 allocator 区间已回收,就已经能把 Binder 调试节点当作源码状态窗口,而不是字段词典。
