3. Binder核心数据结构(上):binder_proc、binder_thread、binder_node结构体详解

Binder IPC 的底层实现依赖于内核空间中的三组核心数据结构:binder_proc、binder_thread 和 binder_node。它们分别描述了进程上下文、线程执行环境和Binder实体节点,构成了整个Binder通信的骨架。理解这三个结构体是掌握Binder驱动工作原理的第一步。

学习提示: 本章聚焦于“进程-线程-节点”的静态结构定义。后续章节将在此基础上讲解binder_ref(引用)和binder_transaction(事务)的动态流转。

3.1 binder_proc:进程级Binder上下文

binder_proc 是Binder驱动中最重要的结构体之一,它关联到一个用户空间进程,并管理该进程在Binder世界中的所有资源。每当一个进程通过 open() 打开Binder设备节点(/dev/binder)时,内核就会为其创建一个 binder_proc 实例。

3.1.1 结构体定义与核心字段

struct binder_proc {
    struct hlist_node proc_node;          // 全局binder_proc哈希链表节点
    struct rb_root threads;               // 红黑树:管理该进程内的binder_thread
    struct rb_root nodes;                 // 红黑树:管理该进程内的binder_node
    struct rb_root refs_by_desc;          // 红黑树:按描述符索引binder_ref
    struct rb_root refs_by_node;          // 红黑树:按节点指针索引binder_ref
    struct list_head waiting_threads;     // 等待空闲线程的列表
    int pid;                              // 进程PID
    struct task_struct *tsk;              // 指向进程的task_struct
    struct files_struct *files;           // 文件描述符表
    struct mutex inner_lock;              // 进程级互斥锁
    struct binder_context *context;       // Binder上下文(与驱动实例关联)
    int max_threads;                      // 最大Binder线程数
    int requested_threads;                // 已请求的线程数
    int requested_threads_started;        // 已启动的线程数
    unsigned long buffer_size;            // 映射的缓冲区总大小
    struct list_head buffers;             // 已分配的缓冲区列表
    struct list_head delivered_death;     // 待传递的死亡通知
    struct list_head todo;                // 进程级待处理事务队列
    wait_queue_head_t wait;               // 进程等待队列
    // ... 其他字段
};

3.1.2 关键字段功能解析

字段 类型 功能描述
proc_node hlist_node 将本proc挂入驱动全局的binder_procs哈希表,用于驱动快速查找所有已注册进程
threads rb_root 红黑树根,以线程ID为键,管理该进程中所有活跃的binder_thread
nodes rb_root 红黑树根,以Binder节点指针为键,管理该进程导出的所有binder_node
refs_by_desc rb_root 红黑树根,以整型描述符为键,管理该进程持有的所有Binder引用
todo list_head 进程级待处理事务链表,当没有空闲线程时,事务会暂存于此
max_threads int 用户空间通过BINDER_SET_MAX_THREADS ioctl设置的最大线程数上限
buffer_size unsigned long 通过mmap映射的内核缓冲区总大小,默认通常为4MB
设计要点: binder_proc 使用红黑树(rb_tree)管理线程、节点和引用,而非链表。红黑树保证了O(log n)的查找效率,这对于高并发场景下的Binder通信至关重要。

3.1.3 binder_proc 生命周期与资源管理

一个 binder_proc 的生命周期与进程对Binder设备文件的打开/关闭操作绑定:

  • 创建: 进程调用 open("/dev/binder") → 驱动调用 binder_open() → 分配并初始化 binder_proc → 插入全局哈希表。
  • 初始化: 进程调用 mmap() → 驱动分配内核缓冲区 → 建立用户态与内核态的内存映射。
  • 销毁: 进程退出或调用 close() → 驱动调用 binder_release() → 清理所有节点、引用、待处理事务 → 释放缓冲区 → 从全局哈希表移除。
binder_proc 内部结构示意图 binder_proc threads (rb_root) nodes (rb_root) refs_by_desc refs_by_node todo (待处理事务链表) waiting_threads buffers (缓冲区列表) inner_lock 关键说明: 1. threads/nodes/refs_by_desc/refs_by_node 均为红黑树根,驱动通过红黑树实现高效查找。 2. todo 链表存储当前没有空闲线程处理的事务,等待线程唤醒后取走。 3. inner_lock 保护整个 binder_proc 内部数据结构的并发访问。 4. buffers 链表管理通过 mmap 分配的内核缓冲区块,用于存储事务数据。

3.2 binder_thread:Binder线程执行上下文

binder_thread 代表进程内一个参与Binder通信的线程。每个线程在首次执行Binder操作(如 ioctl())时,驱动会为其创建对应的 binder_thread 实例,并插入到所属 binder_proc 的 threads 红黑树中。

3.2.1 结构体定义

struct binder_thread {
    struct binder_proc *proc;             // 所属的binder_proc
    struct rb_node rb_node;               // 红黑树节点(以线程ID排序)
    struct list_head waiting_thread_node; // 等待线程链表节点
    int pid;                              // 线程ID(用户态tid)
    struct binder_transaction *transaction_stack; // 当前正在处理的事务栈
    struct list_head todo;                // 线程级待处理事务链表
    uint32_t return_error;                // 上次操作的错误码
    uint32_t return_error2;               // 辅助错误码
    wait_queue_head_t wait;               // 线程等待队列
    struct binder_stats stats;            // 线程级统计信息
    // ... 其他字段
};

3.2.2 线程状态与事务处理

字段 说明
proc 反向指针,指向该线程所属的进程上下文
rb_node 红黑树节点,以线程PID为键,用于在proc的threads树中快速查找
transaction_stack 事务栈指针,用于处理嵌套事务(如oneway回调场景)
todo 线程私有的待处理事务链表,优先级高于proc->todo
wait 等待队列,当线程无事务可处理时,在此休眠等待唤醒
注意: binder_thread 的 todo 链表与 binder_proc 的 todo 链表存在优先级关系。驱动在分配事务时,优先将事务放入目标线程的 todo 链表;只有当目标线程繁忙时,才将事务放入进程级的 todo 链表等待空闲线程处理。

3.2.3 线程的创建与销毁时机

  • 创建: 线程首次调用 BINDER_WRITE_READ ioctl → 驱动检查 threads 红黑树中是否存在该线程ID → 不存在则创建新的 binder_thread → 插入红黑树。
  • 销毁: 线程退出或调用 BINDER_THREAD_EXIT → 驱动清理该线程的待处理事务 → 从红黑树移除 → 释放内存。
binder_thread 与 binder_proc 的关系 binder_proc threads (rb_root) binder_thread (tid=100) binder_thread (tid=200) binder_thread (tid=300) todo (线程私有) todo (线程私有) todo (线程私有) 线程事务分配优先级: 1. 优先放入目标线程的 todo 链表 2. 线程繁忙时,放入 proc->todo 进程级链表 3. 空闲线程从 proc->todo 取走事务处理

3.3 binder_node:Binder实体节点

binder_node 是Binder通信中的“服务端实体”。当一个进程(服务端)通过 BC_ENTER_ENTERPRISE 或 BC_REGISTER_LOOPER 等协议向Binder驱动注册一个Binder对象时,驱动会创建一个 binder_node 实例,并将其插入到该进程的 nodes 红黑树中。

3.3.1 结构体定义

struct binder_node {
    struct binder_proc *proc;             // 创建该节点的进程(服务端进程)
    struct rb_node rb_node;               // 红黑树节点(在proc->nodes树中)
    struct hlist_node dead_node;          // 节点死亡后的链表节点
    struct binder_work work;              // 节点相关的工作项
    struct list_head async_todo;          // 异步事务待处理链表
    __u32 ptr;                            // 用户空间Binder对象指针(flat_binder_object.binder)
    __u32 cookie;                         // 用户空间附加数据(通常指向Java对象或C++ BBinder)
    __u32 flags;                          // 节点标志位(如:oneway、accept_fds等)
    __u8 has_async_transaction;           // 是否有未完成的异步事务
    int internal_strong_refs;             // 内部强引用计数
    int local_strong_refs;                // 本地强引用计数
    int local_weak_refs;                  // 本地弱引用计数
    // ... 其他字段
};

3.3.2 节点引用与生命周期

字段 说明
proc 指向创建该节点的服务端进程,用于驱动定位服务端
ptr 用户空间的Binder对象地址,驱动通过此值将事务路由到正确的服务端对象
cookie 用户空间附加的上下文数据,通常用于标识具体的服务接口
internal_strong_refs 驱动内部维护的强引用计数,防止节点在事务处理中被销毁
local_strong_refs 服务端进程自身持有的强引用计数
async_todo 异步事务(oneway)的待处理链表,与同步事务分离
核心概念: binder_node 是Binder通信中“服务端对象”在内核中的影子。客户端通过binder_ref(引用)指向binder_node,驱动通过binder_node找到服务端进程和具体的Binder对象(ptr/cookie)。一个binder_node可以被多个客户端引用,但只属于一个服务端进程。

3.3.3 节点状态与死亡通知

当服务端进程退出时,驱动会遍历其 nodes 红黑树,将所有 binder_node 标记为“死亡”状态,并触发死亡通知机制:

  • 驱动将 binder_node 移入 dead_node 哈希链表。
  • 遍历所有引用该节点的 binder_ref,向注册了死亡通知的客户端发送 BR_DEAD_BINDER 消息。
  • 客户端收到死亡通知后,清理对该节点的引用。
binder_node 与客户端/服务端的关系 服务端进程 (Server) binder_node ptr=0x7f123456, cookie=0x... 客户端进程1 (Client A) 客户端进程2 (Client B) binder_ref (desc=1) binder_ref (desc=2) 关键说明: 一个 binder_node 可以被多个客户端通过 binder_ref 引用。驱动通过 binder_node 找到服务端进程,通过 ptr/cookie 定位到具体的Binder对象。

3.4 三大结构体的协作关系

在Binder驱动的实际运行中,binder_proc、binder_thread 和 binder_node 三者紧密协作,共同完成一次Binder事务的寻址与处理:

场景 binder_proc 角色 binder_thread 角色 binder_node 角色
客户端发起请求 提供客户端进程上下文,管理引用和缓冲区 当前发起请求的线程,持有事务栈 目标服务端实体,驱动通过它找到服务端进程
驱动路由事务 通过refs_by_desc找到binder_ref,再找到binder_node 将事务放入目标线程的todo链表 提供proc指针定位服务端进程
服务端处理请求 提供服务端进程的缓冲区用于读取数据 从todo链表取出事务并处理 提供ptr/cookie供服务端识别具体对象
进程退出清理 遍历nodes/refs树,触发死亡通知 清理线程级待处理事务 移入dead_node链表,通知所有引用者
学习路径提示: 掌握 binder_proc、binder_thread、binder_node 的静态结构后,下一章将学习 binder_ref(引用)和 binder_buffer(缓冲区)的详细设计,以及它们如何与本章的三个结构体配合完成完整的事务处理流程。