3. Binder驱动基础:设备节点、打开与mmap操作、Binder协议概述

Binder IPC 的核心实现位于 Linux 内核驱动层。用户空间的进程通过标准的文件操作接口与 Binder 驱动交互,驱动则负责管理进程间通信的底层细节。本章将深入剖析 Binder 驱动的三大基础:设备节点 /dev/binder、打开与内存映射(mmap)操作,以及 Binder 协议的基本框架。

3.1 设备节点 /dev/binder

在 Android 系统中,Binder 驱动被注册为一个标准的 misc 设备(杂项设备)。其主设备号为 10,次设备号动态分配,并在文件系统中创建名为 /dev/binder 的设备节点。

关键点: Binder 驱动不是字符设备或块设备,而是 misc 设备。这使得它无需占用一个独立的主设备号,便于集成到内核中。

设备节点的创建过程发生在内核初始化阶段。Binder 驱动调用 misc_register() 函数向内核注册自己,该函数会自动在 /dev 目录下创建设备文件。用户空间的服务(如 servicemanager)通过打开此设备文件来获取与 Binder 驱动通信的句柄。

属性 值 说明
设备类型 misc (杂项) 主设备号 10,次设备号动态分配
设备路径 /dev/binder 用户空间访问 Binder 驱动的入口
文件操作 binder_fops 定义 open、release、mmap、ioctl 等回调函数
权限 0666 或更严格 允许系统服务与普通应用进程访问
Binder 设备节点注册与访问流程 内核空间 (Kernel Space) Binder 驱动模块 misc_register() 创建设备节点 /dev/binder 注册 binder_fops (open, mmap, ioctl) 用户空间 (User Space) ServiceManager open("/dev/binder", O_RDWR) 获取文件描述符 fd mmap(fd, ...) ioctl(fd, BINDER_WRITE_READ, ...) 系统调用

3.2 打开操作:binder_open

当用户空间进程调用 open("/dev/binder", O_RDWR) 时,内核会执行 Binder 驱动注册的 binder_open 函数。这个函数是每个进程与 Binder 驱动建立联系的起点。

binder_open 的核心工作是为当前进程分配并初始化一个 binder_proc 结构体。该结构体是 Binder 驱动视角下的“进程”抽象,用于管理该进程的所有 Binder 相关资源。

核心数据结构: struct binder_proc 是 Binder 驱动中最重要的结构体之一,它包含了进程的线程池、待处理事务、已映射内存区域、节点引用等信息。

binder_open 函数的主要步骤:

  1. 分配 binder_proc 结构体:使用 kzalloc 在堆上分配内存并清零。
  2. 初始化链表头:初始化进程内的线程列表、待处理事务列表、节点列表等。
  3. 设置进程 PID:记录当前进程的 PID,用于调试和统计。
  4. 关联文件描述符:将 binder_proc 指针存入 file->private_data,以便后续操作(如 mmap、ioctl)能够快速获取该结构体。
  5. 将进程加入全局链表:将新创建的 binder_proc 添加到 Binder 驱动的全局进程链表中。
// 简化后的 binder_open 核心逻辑
static int binder_open(struct inode *nodp, struct file *filp)
{
    struct binder_proc *proc;

    // 1. 分配并初始化 binder_proc 结构体
    proc = kzalloc(sizeof(*proc), GFP_KERNEL);
    if (proc == NULL)
        return -ENOMEM;

    // 2. 初始化各种链表头
    INIT_LIST_HEAD(&proc->todo);
    INIT_LIST_HEAD(&proc->nodes);
    INIT_LIST_HEAD(&proc->refs_by_desc);
    INIT_LIST_HEAD(&proc->refs_by_node);
    INIT_LIST_HEAD(&proc->threads);

    // 3. 记录进程 PID
    proc->pid = current->pid;

    // 4. 将 proc 关联到文件描述符
    filp->private_data = proc;

    // 5. 将进程加入全局链表
    binder_lock(__func__);
    hlist_add_head(&proc->proc_node, &binder_procs);
    binder_unlock(__func__);

    return 0;
}

3.3 mmap 操作:内存映射与缓冲区管理

Binder IPC 的高效性很大程度上得益于其内存映射机制。在打开设备文件后,用户空间进程必须调用 mmap 来分配用于数据传输的缓冲区。这是 Binder 驱动初始化的关键步骤。

mmap 操作的核心目的是在内核空间分配一块物理内存,并将其同时映射到内核空间和用户空间。这样,当 Binder 驱动需要将数据从一个进程传输到另一个进程时,只需在内核空间进行一次拷贝,而无需在用户空间和内核空间之间进行两次拷贝(一次从发送进程的用户空间拷贝到内核,一次从内核拷贝到接收进程的用户空间)。

重要限制: Binder 驱动对 mmap 的大小有严格限制。默认情况下,每个进程最多可以映射 4 个物理页(通常为 16KB,具体大小取决于内核配置)。这个限制是为了防止某个进程消耗过多的内核内存。

binder_mmap 函数的主要步骤:

  • 检查映射大小:确保映射大小不超过允许的最大值(通常为 4 页)。
  • 分配物理页面:调用 alloc_page 分配所需的物理页。
  • 建立页表映射:使用 remap_pfn_range 或 vm_insert_page 将分配的物理页映射到用户空间的虚拟地址。
  • 初始化缓冲区管理结构:创建 binder_buffer 链表,用于管理已映射内存中的空闲块和已分配块。
  • 记录映射信息:将映射的虚拟地址、大小等信息存入 binder_proc 结构体。
Binder mmap 内存映射架构 用户空间 进程 A 虚拟地址空间 mmap 返回的虚拟地址 (用户可直接读写) 内核空间 Binder 驱动缓冲区 binder_buffer 链表管理 (内核可直接读写) 物理内存页 alloc_page() 分配 同一块物理内存 被映射到两个虚拟地址空间 页表映射 页表映射 零拷贝原理:用户空间与内核空间共享同一块物理内存,数据传输时无需拷贝

3.4 Binder 协议概述

Binder 协议定义了用户空间进程与 Binder 驱动之间通信的规范。它主要基于 ioctl 系统调用实现,核心命令是 BINDER_WRITE_READ。该命令允许进程在一次系统调用中同时发送数据(write)和接收数据(read)。

Binder 协议的数据单元称为“事务”(Transaction)。一个事务包含一个 binder_transaction_data 结构体,其中封装了目标对象、代码、数据负载等信息。

协议命令 方向 功能描述
BINDER_WRITE_READ 双向 核心命令,同时执行写入和读取操作
BINDER_SET_MAX_THREADS 用户→驱动 通知驱动该进程允许的最大线程数
BINDER_SET_CONTEXT_MGR 用户→驱动 将当前进程注册为 ServiceManager
BINDER_THREAD_EXIT 用户→驱动 通知驱动某个 Binder 线程即将退出
BINDER_VERSION 用户→驱动 获取 Binder 驱动版本号

一个典型的 Binder 事务流程如下:

  1. 发送方:填充 binder_transaction_data 结构体,指定目标句柄、事务代码和数据。
  2. ioctl 调用:发送方调用 ioctl(fd, BINDER_WRITE_READ, &bwr),其中 bwr 包含写入缓冲区和读取缓冲区。
  3. 驱动处理:Binder 驱动解析写入缓冲区,找到目标进程,并将事务数据拷贝到目标进程的 mmap 缓冲区中。
  4. 唤醒接收方:驱动将事务添加到目标进程的待处理队列,并唤醒目标进程中等待的 Binder 线程。
  5. 接收方:被唤醒的线程通过 ioctl 读取返回的数据,完成一次 IPC 调用。
Binder 协议事务处理流程 发送方进程 1. 构造事务数据 binder_transaction_data 2. ioctl(BINDER_WRITE_READ) Binder 驱动 3. 解析事务数据 查找目标进程 拷贝数据到目标缓冲区 接收方进程 4. 事务加入待处理队列 5. 唤醒接收线程 6. ioctl 读取返回数据 数据流方向 发送方用户空间 → 发送方内核缓冲区 → 接收方内核缓冲区 → 接收方用户空间 由于 mmap 映射,从发送方内核缓冲区到接收方内核缓冲区的拷贝是唯一的数据拷贝 binder_transaction_data 结构体关键字段 target.handle code data.ptr.buffer data.ptr.offsets 目标 Binder 引用句柄 事务代码 (方法 ID) 数据缓冲区指针 偏移数组指针 偏移数组用于定位数据缓冲区中的 Binder 对象引用
协议设计要点: Binder 协议采用“命令+数据”的混合模式。写入缓冲区包含一系列命令(如 BC_TRANSACTION、BC_REPLY)和数据;读取缓冲区则包含驱动返回的事件(如 BR_TRANSACTION、BR_REPLY)。这种设计使得一次 ioctl 调用可以处理多个事务,提高了通信效率。