Binder通信机制测试验证与性能调优入门学习 - 第2节
2.1 Binder 驱动的核心角色
Binder 驱动是 Android 系统中运行于内核空间的关键模块,它作为 Binder 通信机制的中枢神经系统,负责协调所有跨进程通信。与传统的 Linux IPC 机制(如管道、消息队列、共享内存)不同,Binder 驱动不仅承担数据传输的职责,还深度参与了对象引用管理、线程调度和安全性控制。
核心定位:Binder 驱动是一个字符设备驱动,它通过
/dev/binder 设备节点暴露给用户空间,是用户态 Binder 框架与内核态通信的唯一桥梁。
2.1.1 驱动承担的三大职责
| 职责领域 | 具体功能 | 关键作用 |
|---|---|---|
| 数据传输 | 在进程间拷贝数据,管理内核缓冲区 | 一次拷贝机制,避免两次用户态与内核态切换 |
| 对象管理 | 维护 Binder 实体(binder_node)与引用(binder_ref) | 实现跨进程对象传递与引用计数 |
| 线程调度 | 管理 Binder 线程池,分发事务到目标线程 | 确保并发请求有序处理,避免死锁 |
2.2 设备节点 /dev/binder
/dev/binder 是 Binder 驱动在用户空间暴露的字符设备节点。所有用户态进程(包括 ServiceManager、系统服务、应用进程)都通过打开这个设备节点来建立与 Binder 驱动的通信通道。
2.2.1 设备节点的关键属性
- 主设备号:Binder 驱动注册时动态分配,通常为 10(misc 设备)
- 次设备号:固定为 0,对应
/dev/binder - 文件操作:驱动实现了
open()、release()、ioctl()、mmap()等标准文件操作接口 - 权限控制:通常设置为
0666或0600,由 SELinux 策略进一步约束
实践要点:每个进程打开
/dev/binder 后,驱动会为其分配一个 binder_proc 结构体,用于维护该进程的 Binder 通信状态。一个进程通常只打开一次该设备节点。
2.2.2 打开设备节点的典型流程
- 进程调用
open("/dev/binder", O_RDWR) - 驱动调用
binder_open()函数,创建binder_proc实例 - 进程调用
mmap()映射内核缓冲区,用于接收 Binder 事务数据 - 进程通过
ioctl()发送 BINDER_WRITE_READ 命令进行实际通信
// 用户态打开 /dev/binder 的典型代码片段
int fd = open("/dev/binder", O_RDWR);
if (fd < 0) {
// 错误处理
}
// 映射 128KB 内核缓冲区用于接收数据
void *mapped = mmap(NULL, 128 * 1024, PROT_READ, MAP_SHARED, fd, 0);
if (mapped == MAP_FAILED) {
// 错误处理
}
// 后续通过 ioctl 进行通信
struct binder_write_read bwr = {0};
// ... 填充 bwr 结构
ioctl(fd, BINDER_WRITE_READ, &bwr);
2.3 驱动层主要数据结构
Binder 驱动在内核态维护了多个核心数据结构,它们共同构成了 Binder 通信的骨架。理解这些结构是深入掌握 Binder 机制的基础。
2.3.1 核心数据结构总览
| 数据结构 | 内核文件位置 | 核心作用 | 关键字段 |
|---|---|---|---|
binder_proc |
drivers/android/binder.c | 代表一个打开 /dev/binder 的进程 | pid, tsk, nodes, refs_by_desc, refs_by_node |
binder_node |
drivers/android/binder.c | 代表一个 Binder 实体(服务端对象) | ptr, cookie, proc, refs, has_async_transaction |
binder_ref |
drivers/android/binder.c | 代表客户端对 Binder 实体的引用 | desc, node, death, proc |
binder_transaction |
drivers/android/binder.c | 代表一次 Binder 事务(请求或回复) | from, to, code, flags, buffer, priority |
binder_buffer |
drivers/android/binder.c | 管理内核缓冲区中的数据 | data, data_size, offsets_size, transaction |
binder_thread |
drivers/android/binder.c | 代表一个参与 Binder 通信的线程 | proc, pid, looper, transaction_stack |
2.3.2 数据结构之间的关系
这些数据结构通过指针和链表相互关联,形成了一张复杂的网络。下面通过 SVG 图展示它们之间的核心关系:
2.3.3 关键数据结构详解
binder_proc
每个打开 /dev/binder 的进程在内核中对应一个 binder_proc 实例。它是驱动管理进程级 Binder 状态的入口。
// 简化后的 binder_proc 结构(内核 5.10+)
struct binder_proc {
struct hlist_node proc_node; // 全局 proc 链表节点
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_alloc alloc; // 内存分配器
};
binder_node
当服务端进程注册一个 Binder 对象时,驱动会创建一个 binder_node。它代表了服务端暴露给其他进程的远程对象。
struct binder_node {
struct hlist_node dead_node; // 死亡通知链表
struct binder_proc *proc; // 所属进程
struct rb_node rb_node; // 红黑树节点
binder_uintptr_t ptr; // 服务端对象的用户空间地址
binder_uintptr_t cookie; // 服务端附加数据
unsigned int has_async_transaction : 1; // 是否有异步事务
unsigned int accept_fds : 1; // 是否接受文件描述符
int internal_strong_refs; // 内部强引用计数
int local_weak_refs; // 本地弱引用计数
struct list_head refs; // 指向该节点的所有 binder_ref 列表
};
binder_ref
客户端进程通过 binder_ref 来引用服务端的 binder_node。每个 binder_ref 在客户端进程内有一个唯一的整数描述符(handle)。
struct binder_ref {
struct binder_proc *proc; // 所属客户端进程
struct binder_node *node; // 指向的 binder_node
struct rb_node rb_node_desc; // 按描述符排序的红黑树节点
struct rb_node rb_node_node; // 按节点排序的红黑树节点
uint32_t desc; // 客户端看到的句柄(handle)
int strong; // 强引用计数
int weak; // 弱引用计数
struct binder_ref_death *death; // 死亡通知
};
关键理解:描述符(desc)是客户端视角的整数句柄,类似于文件描述符。驱动通过红黑树实现从 desc 到 binder_ref 的 O(log n) 查找。服务端本身没有 desc,它通过 ptr 和 cookie 标识自己的对象。
2.3.4 事务与缓冲区管理
Binder 通信的核心是事务(transaction)。每次跨进程调用都对应一个 binder_transaction 实例,而实际的数据则存储在内核缓冲区中。
| 数据结构 | 生命周期 | 内存位置 | 关键特性 |
|---|---|---|---|
binder_transaction |
从请求发出到回复完成 | 内核堆内存 | 包含 code、flags、sender_pid、sender_euid |
binder_buffer |
从分配事务数据到释放 | 目标进程 mmap 区域 | 一次拷贝:数据直接从源进程拷贝到目标进程的缓冲区 |
// binder_transaction 结构(关键字段)
struct binder_transaction {
int debug_id; // 调试 ID
struct binder_work work; // 工作队列节点
struct binder_thread *from; // 发起事务的线程
struct binder_transaction *from_parent; // 父事务(嵌套调用)
struct binder_proc *to_proc; // 目标进程
struct binder_thread *to_thread; // 目标线程
struct binder_buffer *buffer; // 数据缓冲区
unsigned int code; // 事务代码(方法 ID)
unsigned int flags; // 事务标志
binder_uintptr_t sender_pid; // 发送者 PID
binder_uintptr_t sender_euid; // 发送者 EUID
struct list_head fd_fixups; // 文件描述符修复列表
};
2.4 驱动层数据流全景
综合以上数据结构,一次典型的 Binder 调用在驱动层的数据流如下:
- 客户端线程通过
ioctl(BINDER_WRITE_READ)提交事务请求 - 驱动根据 handle 查找
binder_ref,进而找到binder_node和目标binder_proc - 驱动在目标进程的 mmap 区域分配
binder_buffer,将数据从客户端拷贝过来 - 驱动创建
binder_transaction,挂入目标线程的工作队列 - 目标线程被唤醒,从工作队列取出事务,处理请求
- 回复时流程反向,驱动将回复数据拷贝回客户端的缓冲区
性能关键点:Binder 驱动在整个通信过程中只进行一次数据拷贝(从源进程用户空间到目标进程内核缓冲区),这是 Binder 相比传统 IPC 机制(如 Socket 需要两次拷贝)的核心性能优势。
2.5 本章小结
- Binder 驱动是运行在内核态的字符设备驱动,通过
/dev/binder节点与用户态交互 - 驱动承担数据传输、对象管理、线程调度三大核心职责
- 核心数据结构包括
binder_proc、binder_node、binder_ref、binder_transaction、binder_buffer、binder_thread - 驱动通过红黑树实现高效的对象查找,通过一次拷贝机制实现高性能数据传输
- 理解这些数据结构及其关系,是后续分析 Binder 通信流程和性能调优的基础