2. Binder驱动源码结构分析

Binder驱动是Android系统中进程间通信(IPC)的核心组件,其源码位于Linux内核的drivers/android/目录下。本章将深入分析Binder驱动在内核源码中的位置、关键源文件的功能划分以及核心数据结构的设计原理,为后续的多平台移植适配奠定基础。

2.1 Binder驱动在内核源码中的位置

Binder驱动作为内核模块,其源码文件集中存放于Linux内核源码树的特定目录中。下表列出了Binder驱动相关文件在内核中的分布情况:

文件路径 说明
drivers/android/binder.c Binder驱动核心实现,包含设备操作、事务处理、节点管理
drivers/android/binder_alloc.c Binder内存分配器,管理进程间共享的内存缓冲区
drivers/android/binder_alloc.h 内存分配器头文件,定义缓冲区管理接口
drivers/android/binder_internal.h 内部数据结构定义,包含核心结构体声明
drivers/android/binder_trace.h 跟踪点定义,用于调试和性能分析
include/uapi/linux/android/binder.h 用户空间与内核空间的接口定义,包含ioctl命令和数据结构
include/uapi/linux/android/binderfs.h Binder文件系统接口定义
移植要点:在进行多平台移植时,drivers/android/目录下的所有源文件都需要完整复制到目标内核源码树中。同时,需要确保内核配置中启用了CONFIG_ANDROID_BINDER_IPC和CONFIG_ANDROID_BINDERFS选项。

2.2 关键源文件的功能划分

Binder驱动的核心功能由两个主要源文件承担:binder.c和binder_alloc.c。它们各自负责不同的职责,形成了清晰的模块化架构。

2.2.1 binder.c — 核心控制逻辑

binder.c是Binder驱动的主文件,负责以下关键功能:

  • 设备管理:实现binder_open()、binder_release()、binder_mmap()等文件操作接口
  • 事务处理:实现binder_ioctl(),处理BINDER_WRITE_READ等核心命令
  • 节点管理:维护binder_node的创建、查找和销毁
  • 引用管理:维护binder_ref的增删改查
  • 线程管理:管理binder_thread状态和事务分发
  • 死锁检测:实现循环依赖检测机制
// binder.c 核心函数示例:binder_ioctl 命令分发
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct binder_proc *proc = filp->private_data;
    struct binder_thread *thread;
    unsigned int size = _IOC_SIZE(cmd);
    void __user *ubuf = (void __user *)arg;

    binder_lock(__func__);
    thread = binder_get_thread(proc);
    // ... 根据 cmd 分发处理
    switch (cmd) {
    case BINDER_WRITE_READ:
        ret = binder_ioctl_write_read(filp, cmd, arg, thread);
        break;
    case BINDER_SET_MAX_THREADS:
        // ...
        break;
    // ... 其他命令
    }
    binder_unlock(__func__);
    return ret;
}

2.2.2 binder_alloc.c — 内存分配器

binder_alloc.c专注于Binder通信所需的内存管理,其核心职责包括:

  • 缓冲区分配:从预分配的虚拟地址空间中分配连续内存块
  • 缓冲区释放:回收不再使用的缓冲区
  • 缓冲区映射:管理用户空间与内核空间的内存映射关系
  • 碎片整理:通过binder_alloc_free_page()实现页面级回收
// binder_alloc.c 核心数据结构:缓冲区描述符
struct binder_buffer {
    struct list_head entry;         // 链表节点,链接到 proc->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;            // 偏移数组大小
    size_t extra_buffers_size;      // 额外缓冲区大小
    void *data;                     // 数据起始地址
};
功能划分优势:将内存管理独立为binder_alloc.c,使得在移植到不同架构时,只需调整内存分配相关的代码,而核心的事务处理逻辑保持不变。这种模块化设计显著降低了移植工作量。

2.3 核心数据结构解析

Binder驱动的运行依赖于四个核心数据结构:binder_proc、binder_node、binder_ref和binder_buffer。它们共同构成了Binder通信的骨架。

2.3.1 binder_proc — 进程上下文

binder_proc代表一个使用Binder通信的进程,是Binder驱动中最重要的数据结构之一。每个打开Binder设备的进程都对应一个binder_proc实例。

struct binder_proc {
    struct hlist_node proc_node;    // 全局进程哈希链表节点
    struct rb_root threads;         // 线程红黑树根
    struct rb_root nodes;           // 节点红黑树根
    struct rb_root refs_by_desc;    // 按描述符索引的引用红黑树
    struct rb_root refs_by_node;    // 按节点索引的引用红黑树
    struct list_head waiting_threads; // 等待线程链表
    int pid;                        // 进程ID
    struct task_struct *tsk;        // 进程任务结构体
    struct files_struct *files;     // 文件描述符表
    struct mutex inner_lock;        // 内部锁
    struct binder_alloc alloc;      // 内存分配器
    // ... 其他字段
};

下表总结了binder_proc中关键字段的作用:

字段 类型 作用
proc_node hlist_node 将进程挂入全局哈希表,用于快速查找
threads rb_root 管理进程内的所有Binder线程
nodes rb_root 管理进程导出的所有Binder节点
refs_by_desc rb_root 按描述符快速查找引用
refs_by_node rb_root 按节点快速查找引用
alloc binder_alloc 进程私有的内存分配器实例

2.3.2 binder_node — 服务节点

binder_node代表一个Binder服务实体,是服务端进程导出的通信端点。每个Binder服务在驱动中对应一个binder_node。

struct binder_node {
    struct hlist_node dead_node;    // 死亡通知链表节点
    struct rb_node rb_node;         // 红黑树节点
    struct hlist_node refs;         // 引用链表头
    int internal_strong_refs;       // 内部强引用计数
    int local_weak_refs;            // 本地弱引用计数
    unsigned int has_strong_ref:1;  // 是否有强引用
    unsigned int pending_strong_ref:1; // 待处理强引用
    unsigned int pending_weak_ref:1;   // 待处理弱引用
    unsigned int has_async_transaction:1; // 是否有异步事务
    unsigned int accept_fds:1;      // 是否接受文件描述符
    int min_priority;               // 最小优先级
    struct binder_proc *proc;       // 所属进程
    struct binder_ref __rcu *ref;   // 指向引用的指针
    binder_uintptr_t ptr;           // 用户空间服务句柄
    binder_uintptr_t cookie;        // 用户空间cookie
};
移植注意事项:binder_node中的ptr和cookie字段存储的是用户空间地址。在64位系统移植时,必须确保binder_uintptr_t类型与目标架构的指针宽度一致。32位系统使用uint32_t,64位系统使用uint64_t。

2.3.3 binder_ref — 引用描述符

binder_ref是客户端进程对服务端节点的引用,相当于一个代理对象。每个客户端通过binder_ref来访问远程服务。

struct binder_ref {
    struct rb_node rb_node_desc;    // 按描述符排序的红黑树节点
    struct rb_node rb_node_node;    // 按节点排序的红黑树节点
    struct hlist_node node_entry;   // 节点引用链表节点
    struct binder_proc *proc;       // 所属进程
    struct binder_node *node;       // 引用的目标节点
    uint32_t desc;                  // 描述符(句柄)
    int strong;                     // 强引用计数
    int weak;                       // 弱引用计数
    struct binder_ref_data data;    // 引用数据
};

下表对比了binder_node和binder_ref的角色差异:

特性 binder_node binder_ref
所属方 服务端进程 客户端进程
生命周期 服务存在期间 引用存在期间
唯一性 全局唯一 进程内唯一
标识方式 指针地址 描述符(句柄)
主要作用 代表服务实体 代表服务引用

2.3.4 binder_buffer — 数据缓冲区

binder_buffer是Binder通信中实际传输数据的载体,由binder_alloc模块管理。每个事务的数据都存储在一个或多个binder_buffer中。

struct binder_buffer {
    struct list_head entry;         // 链表节点
    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;            // 偏移数组大小
    size_t extra_buffers_size;      // 额外缓冲区大小
    void *data;                     // 数据起始地址
};
性能优化提示:binder_buffer的管理采用红黑树和链表相结合的方式。红黑树用于按地址快速查找和分配,链表用于遍历和回收。在移植时,如果目标架构的缓存行大小不同,可以考虑调整binder_buffer的对齐方式以提升性能。

2.4 核心数据结构关系图

下图展示了四个核心数据结构之间的关联关系,帮助理解Binder驱动的整体架构:

Binder驱动核心数据结构关系图 binder_proc 进程上下文 pid: 进程ID alloc: 内存分配器 binder_node 服务节点 ptr: 服务句柄 proc: 所属进程 binder_ref 引用描述符 desc: 句柄号 node: 目标节点 binder_buffer 数据缓冲区 data: 数据地址 transaction: 所属事务 binder_thread 线程上下文 pid: 线程ID proc: 所属进程 binder_transaction 事务描述 from: 发送线程 to: 接收进程 nodes红黑树 refs_by_desc红黑树 threads红黑树 alloc管理 node指针 transaction指针 from_thread 数据结构关系说明 一个进程(proc)管理多个节点(node)、引用(ref)、线程(thread)和缓冲区(buffer) 图例 进程上下文 服务节点 引用描述符 数据缓冲区 线程上下文 事务描述

2.5 数据结构间的协作流程

理解四个核心数据结构如何协同工作,是掌握Binder驱动源码的关键。以下是一个典型的Binder通信流程:

  1. 服务注册:服务端进程通过binder_node向驱动注册服务,驱动将节点加入binder_proc->nodes红黑树
  2. 获取引用:客户端进程通过服务名查询,驱动返回一个binder_ref,其中desc字段作为句柄
  3. 发起事务:客户端通过binder_ref.desc指定目标,驱动查找对应的binder_node
  4. 分配缓冲区:驱动调用binder_alloc分配binder_buffer,用于存储事务数据
  5. 数据拷贝:将用户空间数据拷贝到binder_buffer中,完成跨进程数据传输
源码阅读建议:在阅读Binder驱动源码时,建议按照以下顺序逐步深入:
1. 先从binder.h(UAPI头文件)了解用户空间接口
2. 再阅读binder_internal.h掌握内核数据结构定义
3. 然后分析binder_alloc.c理解内存管理机制
4. 最后深入binder.c掌握事务处理核心逻辑

2.6 数据结构大小与对齐要求

在进行多平台移植时,数据结构的对齐和大小是必须关注的关键点。下表列出了核心数据结构在不同架构下的典型大小:

数据结构 32位ARM (字节) 64位ARM (字节) 关键对齐要求
binder_proc 约 512 约 1024 8字节对齐
binder_node 约 128 约 256 8字节对齐
binder_ref 约 64 约 128 8字节对齐
binder_buffer 约 80 约 160 8字节对齐
binder_thread 约 96 约 192 8字节对齐
binder_transaction 约 112 约 224 8字节对齐
移植陷阱:在将Binder驱动从32位系统移植到64位系统时,指针类型从4字节变为8字节,会导致数据结构大小发生变化。必须检查所有涉及sizeof()运算的代码,特别是copy_from_user/copy_to_user中的数据大小计算。此外,struct binder_write_read等UAPI结构体中的__u32和__u64字段布局也需要重新验证。