2. Binder驱动架构解析:内核模块结构与关键数据结构
Binder 驱动是 Android 系统中唯一运行在内核空间的 IPC 组件,它向上层提供设备文件接口(/dev/binder),向下管理进程间通信的节点、引用与缓冲区。理解 Binder 驱动的内核模块结构及其核心数据结构,是进行驱动移植与跨平台适配的基础。
2.1 Binder 驱动的内核模块结构
Binder 驱动以内核模块(drivers/android/binder.c)的形式存在,其模块结构遵循标准的 Linux 字符设备驱动模型。整个模块由初始化、设备操作、核心事务处理与内存管理四大部分组成。
| 模块组件 | 核心函数/结构 | 职责说明 |
|---|---|---|
| 模块初始化 | binder_init() |
注册 misc 设备、创建 proc 目录、初始化工作队列与 debugfs 接口 |
| 设备文件操作 | binder_open(), binder_release() |
创建/销毁 binder_proc 实例,关联到打开进程 |
| IOCTL 处理 | binder_ioctl() |
分发 BINDER_WRITE_READ、BINDER_SET_CONTEXT_MGR 等命令 |
| 事务引擎 | binder_thread_read(), binder_transaction() |
处理 BC_TRANSACTION/BC_REPLY,完成数据拷贝与节点查找 |
| 内存管理 | binder_alloc_new_buf(), binder_update_page_range() |
管理进程虚拟地址空间中的 Binder 缓冲区池 |
binder_init() 中的 misc_register 调用可能需要替换为平台特定的设备注册机制;binder_ioctl() 中的无锁/自旋锁策略也需要根据目标内核的抢占模型重新评估。
2.2 关键数据结构详解
Binder 驱动的核心逻辑围绕四个关键数据结构展开:binder_proc(进程上下文)、binder_node(服务节点)、binder_ref(引用描述)和 binder_buffer(数据缓冲区)。这四个结构共同构成了 Binder 通信的“寻址-映射-传输”链路。
2.2.1 binder_proc —— 进程上下文
每个打开 /dev/binder 设备的进程,在内核中对应一个 binder_proc 实例。它是 Binder 通信的“根对象”,管理该进程的所有线程、节点、引用和缓冲区。
struct binder_proc {
struct hlist_node proc_node; // 全局 proc 哈希链表节点
struct rb_root threads; // 红黑树:管理 binder_thread
struct rb_root nodes; // 红黑树:管理 binder_node(本地服务)
struct rb_root refs_by_desc; // 红黑树:按 handle 值索引 binder_ref
struct rb_root refs_by_node; // 红黑树:按 node 指针索引 binder_ref
struct list_head waiting_threads; // 等待线程链表
int pid; // 所属进程 PID
struct binder_alloc alloc; // 缓冲区分配器(核心内存管理单元)
struct list_head todo; // 待处理事务链表
spinlock_t inner_lock; // 进程级自旋锁
};
| 字段 | 数据结构 | 作用 |
|---|---|---|
nodes |
红黑树(key = binder_node 指针) | 记录本进程向 Binder 注册的所有服务节点 |
refs_by_desc |
红黑树(key = handle 整型) | 按 handle 快速查找远程服务引用 |
refs_by_node |
红黑树(key = node 指针) | 按 node 快速查找引用,用于去重 |
alloc |
binder_alloc 结构体 |
管理进程的 Binder 缓冲区虚拟地址空间 |
todo |
双向链表 | 存放发往本进程但尚未被线程处理的事务 |
binder_proc 中的 inner_lock 在旧版内核中使用自旋锁,但在 PREEMPT_RT 内核中可能导致优先级反转。移植到 RT 内核时,建议将 spinlock_t 替换为 raw_spinlock_t 或使用 mutex 配合等待队列重构。
2.2.2 binder_node —— 服务节点
当进程通过 BC_ENTER_LOOPER 注册为 Binder 服务端时,内核为其创建 binder_node。该结构描述了一个可被远程调用的服务实体,包含服务端进程信息、调试引用计数和强弱引用管理。
struct binder_node {
struct hlist_node dead_node; // 节点死亡链表
struct rb_node rb_node; // 红黑树节点(挂入 proc->nodes)
struct hlist_head refs; // 指向该节点的所有 binder_ref 链表
int internal_strong_refs; // 内部强引用计数
int local_weak_refs; // 本地弱引用计数
struct binder_proc *proc; // 所属进程
struct list_head worklist; // 节点相关的工作项链表
const void __user *ptr; // 用户空间的服务实体指针
const void __user *cookie; // 用户空间的附加数据
};
binder_node 的 refs 链表将所有引用该节点的 binder_ref 串接起来。当节点死亡(服务端进程退出)时,内核通过遍历此链表,向所有持有引用的客户端发送死亡通知(BC_DEAD_BINDER_DONE)。
2.2.3 binder_ref —— 引用描述
客户端进程通过 handle 值操作远程服务,而 binder_ref 就是 handle 到 binder_node 的映射桥梁。每个 binder_ref 唯一对应一个客户端-服务端引用关系。
struct binder_ref {
struct rb_node rb_node_desc; // 红黑树节点(挂入 proc->refs_by_desc)
struct rb_node rb_node_node; // 红黑树节点(挂入 proc->refs_by_node)
struct hlist_node node_entry; // 挂入 binder_node->refs 链表
struct binder_proc *proc; // 持有该引用的客户端进程
struct binder_node *node; // 指向的服务节点
uint32_t desc; // handle 值(客户端看到的引用编号)
int strong; // 强引用计数
int weak; // 弱引用计数
};
| 字段 | 说明 |
|---|---|
desc |
客户端进程内唯一的 handle 值,从 1 开始递增分配 |
node |
指向服务端 binder_node 的指针,实现 handle → 服务实体映射 |
strong/weak |
引用计数,控制服务节点的生命周期;强引用为 0 时触发节点清理 |
node_entry |
将本 ref 挂入 node->refs 链表,用于节点死亡时的广播通知 |
2.2.4 binder_buffer —— 数据缓冲区
Binder 通信的数据传输依赖内核管理的缓冲区池。每个 binder_proc 通过 binder_alloc 管理一段连续的虚拟地址空间,binder_buffer 描述其中的一个已分配或空闲的数据块。
struct binder_buffer {
struct list_head entry; // 链表节点(挂入 alloc->buffers 或 alloc->free_buffers)
struct rb_node rb_node; // 红黑树节点(按地址排序,用于快速查找)
unsigned int free : 1; // 空闲标志位
unsigned int allow_user_free : 1; // 允许用户空间释放
unsigned int async_transaction : 1; // 是否为异步事务
struct binder_transaction *transaction; // 关联的事务
struct binder_node *target_node; // 目标服务节点
size_t data_size; // 有效数据大小
size_t offsets_size; // 偏移数组大小
void *user_data; // 用户空间数据起始地址
};
free_buffers 红黑树中分配 binder_buffer。分配时优先选择大小最接近请求量的空闲块。移植到内存管理 API 不同的内核(如无 get_user_pages 的微内核)时,需要重写 binder_update_page_range() 中的物理页映射逻辑。
2.3 数据结构协作流程
一次典型的 Binder 跨进程调用,涉及上述四个数据结构的完整协作:
- 客户端发起: 客户端进程通过
ioctl(BINDER_WRITE_READ)传入handle和data。 - 引用查找: 内核在客户端
binder_proc->refs_by_desc红黑树中,以handle为 key 找到对应的binder_ref。 - 节点定位: 通过
binder_ref->node指针,定位到服务端进程的binder_node。 - 缓冲区分配: 在目标服务端进程的
binder_proc->alloc中,调用binder_alloc_new_buf()分配一个binder_buffer。 - 数据拷贝: 将客户端数据从用户空间拷贝到服务端
binder_buffer->user_data指向的内核缓冲区。 - 事务投递: 将事务挂入服务端
binder_proc->todo链表,唤醒服务端等待线程处理。
binder_ref 到 binder_node 的映射是 O(log n) 的红黑树查找;binder_buffer 的分配采用最佳适应算法,碎片率控制在 5% 以下。移植时,红黑树操作(rb_insert_color、rb_erase)必须使用目标内核提供的标准接口,不可自行实现。
2.4 数据结构与移植适配的关联
| 数据结构 | 移植适配关注点 | 典型问题 |
|---|---|---|
binder_proc |
自旋锁类型、等待队列实现、PID 命名空间 | RT 内核中 spinlock 导致优先级反转;容器环境下 PID 映射错误 |
binder_node |
用户空间指针 ptr/cookie 的地址空间布局 |
32/64 位兼容模式下指针截断;ASLR 导致地址空间不连续 |
binder_ref |
handle 分配策略、引用计数溢出保护 | handle 复用导致的安全漏洞;强引用计数溢出导致节点提前释放 |
binder_buffer |
物理页分配 API、虚拟地址映射方式 | 目标内核无 get_user_pages_fast;IOMMU 环境下地址映射失败 |
binder_buffer 的 user_data 字段需要从 void * 改为 unsigned long,以兼容 36 位物理地址扩展(LPAE)。同时,binder_alloc 中的页分配器需要从 alloc_pages 切换为 dma_alloc_coherent,以适配 IOMMU 直通模式。