3. Binder核心数据结构(上):binder_proc、binder_thread、binder_node结构体详解
Binder IPC 的底层实现依赖于内核空间中的三组核心数据结构:binder_proc、binder_thread 和 binder_node。它们分别描述了进程上下文、线程执行环境和Binder实体节点,构成了整个Binder通信的骨架。理解这三个结构体是掌握Binder驱动工作原理的第一步。
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 |
3.1.3 binder_proc 生命周期与资源管理
一个 binder_proc 的生命周期与进程对Binder设备文件的打开/关闭操作绑定:
- 创建: 进程调用
open("/dev/binder")→ 驱动调用binder_open()→ 分配并初始化binder_proc→ 插入全局哈希表。 - 初始化: 进程调用
mmap()→ 驱动分配内核缓冲区 → 建立用户态与内核态的内存映射。 - 销毁: 进程退出或调用
close()→ 驱动调用binder_release()→ 清理所有节点、引用、待处理事务 → 释放缓冲区 → 从全局哈希表移除。
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 |
等待队列,当线程无事务可处理时,在此休眠等待唤醒 |
todo 链表与 binder_proc 的 todo 链表存在优先级关系。驱动在分配事务时,优先将事务放入目标线程的 todo 链表;只有当目标线程繁忙时,才将事务放入进程级的 todo 链表等待空闲线程处理。
3.2.3 线程的创建与销毁时机
- 创建: 线程首次调用
BINDER_WRITE_READioctl → 驱动检查threads红黑树中是否存在该线程ID → 不存在则创建新的binder_thread→ 插入红黑树。 - 销毁: 线程退出或调用
BINDER_THREAD_EXIT→ 驱动清理该线程的待处理事务 → 从红黑树移除 → 释放内存。
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)的待处理链表,与同步事务分离 |
3.3.3 节点状态与死亡通知
当服务端进程退出时,驱动会遍历其 nodes 红黑树,将所有 binder_node 标记为“死亡”状态,并触发死亡通知机制:
- 驱动将
binder_node移入dead_node哈希链表。 - 遍历所有引用该节点的
binder_ref,向注册了死亡通知的客户端发送BR_DEAD_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链表,通知所有引用者 |