Binder通信机制测试验证与性能调优入门学习 - 第2节

2.1 Binder 驱动的核心角色

Binder 驱动是 Android 系统中运行于内核空间的关键模块,它作为 Binder 通信机制的中枢神经系统,负责协调所有跨进程通信。与传统的 Linux IPC 机制(如管道、消息队列、共享内存)不同,Binder 驱动不仅承担数据传输的职责,还深度参与了对象引用管理、线程调度和安全性控制。

核心定位:Binder 驱动是一个字符设备驱动,它通过 /dev/binder 设备节点暴露给用户空间,是用户态 Binder 框架与内核态通信的唯一桥梁。

2.1.1 驱动承担的三大职责

职责领域 具体功能 关键作用
数据传输 在进程间拷贝数据,管理内核缓冲区 一次拷贝机制,避免两次用户态与内核态切换
对象管理 维护 Binder 实体(binder_node)与引用(binder_ref) 实现跨进程对象传递与引用计数
线程调度 管理 Binder 线程池,分发事务到目标线程 确保并发请求有序处理,避免死锁
Binder 驱动核心角色示意图 Binder 驱动 数据传输 对象管理 线程调度 Binder 驱动是唯一运行在内核态的 Binder 组件,所有跨进程数据必须经过它中转 用户态通过 ioctl() 系统调用与驱动交互

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 打开设备节点的典型流程

  1. 进程调用 open("/dev/binder", O_RDWR)
  2. 驱动调用 binder_open() 函数,创建 binder_proc 实例
  3. 进程调用 mmap() 映射内核缓冲区,用于接收 Binder 事务数据
  4. 进程通过 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 图展示它们之间的核心关系:

Binder 驱动核心数据结构关系图 binder_proc (代表进程) binder_thread (代表线程) binder_node (服务端实体) binder_ref (客户端引用) binder_transaction (事务) binder_buffer (内核缓冲区) 1:N 1:N 1:N 1:N 目标 1:1 数据结构关系要点 • 每个 binder_proc 包含多个 binder_thread、binder_node、binder_ref • binder_node 与 binder_ref 通过红黑树快速查找 • binder_transaction 关联源/目标 proc、thread 以及数据缓冲区

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 调用在驱动层的数据流如下:

  1. 客户端线程通过 ioctl(BINDER_WRITE_READ) 提交事务请求
  2. 驱动根据 handle 查找 binder_ref,进而找到 binder_node 和目标 binder_proc
  3. 驱动在目标进程的 mmap 区域分配 binder_buffer,将数据从客户端拷贝过来
  4. 驱动创建 binder_transaction,挂入目标线程的工作队列
  5. 目标线程被唤醒,从工作队列取出事务,处理请求
  6. 回复时流程反向,驱动将回复数据拷贝回客户端的缓冲区
性能关键点:Binder 驱动在整个通信过程中只进行一次数据拷贝(从源进程用户空间到目标进程内核缓冲区),这是 Binder 相比传统 IPC 机制(如 Socket 需要两次拷贝)的核心性能优势。
Binder 驱动层一次拷贝数据流 客户端进程 用户空间 内核空间(Binder 驱动) 目标进程 用户空间 binder_buffer (目标进程 mmap 区域) ioctl() 一次拷贝 唤醒目标线程 数据直接从客户端用户空间拷贝到目标进程的内核缓冲区,无需中间缓冲区

2.5 本章小结

  • Binder 驱动是运行在内核态的字符设备驱动,通过 /dev/binder 节点与用户态交互
  • 驱动承担数据传输、对象管理、线程调度三大核心职责
  • 核心数据结构包括 binder_proc、binder_node、binder_ref、binder_transaction、binder_buffer、binder_thread
  • 驱动通过红黑树实现高效的对象查找,通过一次拷贝机制实现高性能数据传输
  • 理解这些数据结构及其关系,是后续分析 Binder 通信流程和性能调优的基础