2. Binder 驱动基础:Binder 驱动在内核中的位置、设备节点 /dev/binder、驱动初始化流程
Binder 驱动是 Android 系统中 Binder IPC 机制在内核空间的核心实现。它作为一个字符设备驱动,运行在 Linux 内核中,负责管理进程间通信的数据传输、线程管理和对象引用计数。理解 Binder 驱动在内核中的位置、设备节点的作用以及驱动初始化流程,是掌握 Binder IPC 底层原理的第一步。
2.1 Binder 驱动在内核中的位置
Binder 驱动并非标准 Linux 内核的一部分,而是 Android 内核分支(Android Common Kernel / AOSP Kernel)中特有的驱动模块。它在内核源码树中的位置如下:
| 组件 | 路径(相对于内核源码根目录) | 说明 |
|---|---|---|
| 驱动主文件 | drivers/android/binder.c |
Binder 驱动核心实现,包含设备操作、事务处理、线程管理等 |
| 头文件 | drivers/android/binder_internal.h |
驱动内部数据结构定义,如 binder_proc、binder_thread、binder_node 等 |
| 用户空间接口头文件 | include/uapi/linux/android/binder.h |
用户空间与内核空间交互的 ioctl 命令、数据结构定义 |
| Makefile 配置 | drivers/android/Makefile |
编译控制,通常包含 obj-$(CONFIG_ANDROID_BINDER_IPC) += binder.o |
| Kconfig 配置 | drivers/android/Kconfig |
内核配置菜单项,控制 Binder 驱动的编译开关 |
内核配置提示: 在编译 Android 内核时,必须启用
CONFIG_ANDROID_BINDER_IPC 和 CONFIG_ANDROID_BINDERFS(可选,用于 binderfs 文件系统)选项。可以通过 make menuconfig 在 "Device Drivers" → "Android" 路径下找到相关配置。
Binder 驱动在内核中的层次关系如下图所示:
2.2 设备节点 /dev/binder
Binder 驱动在用户空间通过字符设备节点 /dev/binder 暴露服务。用户空间的进程通过打开该设备节点,并使用 ioctl() 系统调用与 Binder 驱动进行交互。
2.2.1 设备节点创建方式
设备节点的创建有两种方式:
- 传统方式(devtmpfs / udev): 驱动在初始化时注册设备号,由
devtmpfs或udev自动在/dev/目录下创建设备节点。主设备号通常为动态分配,次设备号为 0。 - binderfs 方式(推荐): Android 4.4+ 引入了
binderfs虚拟文件系统,可以挂载到/dev/binderfs目录,支持动态创建多个 Binder 设备节点(如binder、binder-control、binder-hwbinder、binder-vndbinder),用于隔离不同域(framework、vendor、hardware)的通信。
| 特性 | 传统 devtmpfs 方式 | binderfs 方式 |
|---|---|---|
| 设备节点路径 | /dev/binder |
/dev/binderfs/binder 等 |
| 多设备支持 | 仅单个设备节点 | 支持多个设备节点,用于域隔离 |
| 挂载命令 | 自动由 devtmpfs 创建 | mount -t binder binder /dev/binderfs |
| 内核配置 | CONFIG_ANDROID_BINDER_IPC |
CONFIG_ANDROID_BINDERFS |
| 适用场景 | 早期 Android 版本 | Android 10+ 多域隔离 |
注意事项: 在 Android 10 及更高版本中,强烈建议使用 binderfs 方式。如果内核未启用
CONFIG_ANDROID_BINDERFS,系统可能无法正确创建多个 Binder 设备节点,导致 vendor 和 hardware 域的 Binder 通信失败。
2.2.2 设备节点权限
/dev/binder 设备节点的权限控制至关重要:
- 通常设置为
0666(全局可读写),因为所有应用进程都需要使用 Binder 进行 IPC。 - 在 SELinux 强制访问控制策略中,需要为不同域(如
untrusted_app、system_app)配置对应的allow规则,允许它们打开 Binder 设备节点。 - 使用 binderfs 时,每个设备节点可以设置不同的权限和 SELinux 上下文,实现更细粒度的访问控制。
2.3 驱动初始化流程
Binder 驱动的初始化流程从内核启动时开始,经历模块加载、设备注册、文件操作接口设置等步骤。以下是完整的初始化流程:
2.3.1 关键初始化函数分析
Binder 驱动的初始化入口函数是 binder_init(),定义在 drivers/android/binder.c 中。以下是核心代码片段:
static int __init binder_init(void)
{
int ret;
struct binder_device *device;
// 1. 分配字符设备区域(主设备号)
ret = alloc_chrdev_region(&binder_first_dev, 0, BINDER_MAX_DEVICES,
"binder");
if (ret < 0)
return ret;
// 2. 为每个 Binder 设备初始化 cdev 并添加到内核
list_for_each_entry(device, &binder_devices, entry) {
cdev_init(&device->cdev, &binder_fops);
device->cdev.owner = THIS_MODULE;
ret = cdev_add(&device->cdev,
MKDEV(MAJOR(binder_first_dev), device->minor), 1);
if (ret < 0)
goto err_cdev_add;
// 3. 创建设备类
device->class = class_create(THIS_MODULE, device->name);
if (IS_ERR(device->class)) {
ret = PTR_ERR(device->class);
goto err_class_create;
}
// 4. 创建设备节点
device->device = device_create(device->class, NULL,
MKDEV(MAJOR(binder_first_dev),
device->minor),
NULL, "%s", device->name);
if (IS_ERR(device->device)) {
ret = PTR_ERR(device->device);
goto err_device_create;
}
}
// 5. 如果启用了 binderfs,则初始化 binderfs
ret = init_binderfs();
if (ret < 0)
goto err_init_binderfs;
return 0;
// 错误处理(略)
}
2.3.2 文件操作接口(binder_fops)
Binder 驱动通过 binder_fops 结构体向 VFS 层注册文件操作函数,用户空间通过 open()、ioctl()、mmap()、poll() 等系统调用触发这些函数:
| 文件操作 | 回调函数 | 功能说明 |
|---|---|---|
.open |
binder_open() |
创建 binder_proc 结构,关联到当前进程,初始化线程池 |
.release |
binder_release() |
释放进程关联的 Binder 资源,清理未完成的事务 |
.unlocked_ioctl |
binder_ioctl() |
核心接口,处理 BINDER_WRITE_READ、BINDER_SET_CONTEXT_MGR 等命令 |
.mmap |
binder_mmap() |
分配内核缓冲区,映射到用户空间,用于数据传输 |
.poll |
binder_poll() |
支持 epoll 机制,用于线程等待事务到达 |
关键理解:
binder_mmap() 是 Binder 驱动性能优化的核心。它在内核空间预分配一块物理连续的内存区域(通常为 4 个页面大小,约 16KB),并将该区域同时映射到内核空间和用户空间。这样,Binder 驱动在传输数据时无需进行额外的数据拷贝(一次拷贝),大大提升了 IPC 效率。
2.4 驱动初始化后的内核数据结构
初始化完成后,Binder 驱动在内核中维护以下核心数据结构:
- binder_proc: 每个打开
/dev/binder的进程对应一个binder_proc结构,包含进程 PID、线程列表、待处理事务队列、已映射缓冲区等信息。 - binder_thread: 每个参与 Binder 通信的线程对应一个
binder_thread结构,包含线程 ID、事务栈、等待队列等。 - binder_node: 每个注册到 Binder 驱动的服务(Binder 实体)对应一个
binder_node结构,包含引用计数、调试 ID 等。 - binder_ref: 每个进程持有的远程 Binder 引用对应一个
binder_ref结构,包含指向binder_node的指针和本地引用描述符。 - binder_transaction: 每个正在处理或等待处理的事务对应一个
binder_transaction结构,包含发送方、接收方、数据缓冲区、工作队列等。
2.5 调试与验证
在开发和调试 Binder 驱动时,可以通过以下方式验证驱动是否正确加载和初始化:
- 检查设备节点:
ls -l /dev/binder或ls -l /dev/binderfs/,确认设备节点存在且权限正确。 - 查看内核模块:
lsmod | grep binder,确认 binder 模块已加载。 - 查看内核日志:
dmesg | grep binder,查看驱动初始化过程中的打印信息。 - 查看 procfs 信息:
cat /proc/binder/proc(如果内核启用了CONFIG_ANDROID_BINDER_IPC_DEBUG),查看当前 Binder 进程列表。 - 使用 binderfs 调试: 挂载 binderfs 后,可以通过
cat /dev/binderfs/binder-control查看设备信息。
调试技巧: 如果
/dev/binder 不存在,请检查内核是否启用了 CONFIG_ANDROID_BINDER_IPC。如果使用 binderfs 但设备节点未出现,请确认 CONFIG_ANDROID_BINDERFS 已启用,并手动执行 mount -t binder binder /dev/binderfs 挂载文件系统。