Skip to content

Binder驱动调试节点

追踪 Binder debugfs/binderfs 节点创建、seq_file 输出、锁快照和事务状态排查路径。

基于android-17.0.0_r1
AndroidBinderdebugfsbinderfs源码阅读

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()

c
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()

c
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()

c
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()

c
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;
}
节点输出入口主要用途
stateprint_binder_state(false)全部 proc、dead node、thread、node、ref、buffer 与 work
state_hashedprint_binder_state(true)与 state 相同,但隐藏原始指针格式
transactionsprint_binder_transactions(false)过滤掉无 transaction/async 状态的空 proc
transactions_hashedhashed transactionstransactions 的指针哈希版本
statsprint_binder_stats()、proc statsBC/BR、对象、线程、buffer 与 page 计数
transaction_logtransaction 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()

c
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()

c
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()

c
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()

c
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()

c
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队列 ​

c
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()

c
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()

c
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 对象计数 ​

c
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()

c
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

c
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()

c
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()

c
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()

c
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 线程堆积 ​

  1. 在 proc/<pid> 看 thread looper、ready threads 和 requested threads;
  2. 在 transactions 看 incoming/outgoing stack 和 elapsed;
  3. 在 proc todo 看 pending transaction;
  4. 若 Binder 状态显示线程被占用,再结合用户态线程栈定位服务方法、锁或 I/O。

调试节点能证明 work/transaction 的驱动状态,不能证明用户代码具体卡在哪一行。

8.2 Buffer压力 ​

  1. stats 看 buffers、pages、free async space;
  2. state 看 transaction 的 size/offset 和 buffer 是否仍关联;
  3. proc/<pid> 看 pending transaction、async todo;
  4. 连续读取判断 buffers 是否回落;
  5. 回到 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. 源码导航 ​

bash
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 调试节点当作源码状态窗口,而不是字段词典。