前言
前面几篇文章中涉及 Binder 内核方法,这篇文章统一做一下介绍和总结,包括:binder_init 、binder_open 、binder_mmap 和 binder_ioctl。
Binder 驱动的核心目标是高效地实现进程间通信(IPC),它通过一种"一次拷贝"和基于线程池的模型来 达成这一目标,这些函数共同协作,完成了驱动的初始化、设备打开,内存映射和具体 IPC 命令处理的全流程。
一、binder_init(模块初始化)
这是 Binder 驱动模块被加载到内核时调用的初始化函数。它的主要作用是创建设备节点,为 Binder 驱动运行准备基础的内核基础设施,(在 Linux 中万物皆文件嘛) 让用户 空间进程能够通过文件操作来访问 Binder 驱动。 Linux Kernel 源码位置(非 AOSP 中):
drivers/android/binder.c
c
static int __init init_binder_device(const char *name)
{
int ret;
struct binder_device *binder_device;
/* -------------------------------------------------------------
* 1. 分配设备结构体内存
* 在内核空间为当前要创建的 Binder 设备(如 binder、hwbinder 或 vndbinder)
* 分配一个管理结构体 `binder_device` 的内存空间。
* ------------------------------------------------------------- */
binder_device = kzalloc_obj(*binder_device);
if (!binder_device)
return -ENOMEM;
/* -------------------------------------------------------------
* 2. 绑定 Linux 字符设备标准操作接口 (fops)
* 这是最关键的一步:将标准的杂项设备结构体 `miscdev` 与 `binder_fops` 绑定。
* `binder_fops` 内部映射了用户空间熟悉的系统调用接口:
* .open = binder_open, .mmap = binder_mmap, .unlocked_ioctl = binder_ioctl
* 从而让用户空间对 `/dev/binder` 的操作能准确路由到内核的对应函数。
* ------------------------------------------------------------- */
binder_device->miscdev.fops = &binder_fops; // 绑定操作接口(open/mmap/ioctl等)
binder_device->miscdev.minor = MISC_DYNAMIC_MINOR; // 由 Linux 内核动态分配次设备号
binder_device->miscdev.name = name; // 设置设备在 /dev/ 目录下的名称
/* -------------------------------------------------------------
* 3. 初始化 Binder 上下文 (Context)
* 每一个独立的 Binder 设备都拥有自己独立的上下文。
* 此处初始化引用计数为 1,将当前设备 ServiceManager (Context Manager) 的
* 拥有者 UID 初始化为无效值(等待后续 servicemanager 进程来注册掌管)。
* ------------------------------------------------------------- */
refcount_set(&binder_device->ref, 1);
binder_device->context.binder_context_mgr_uid = INVALID_UID;
binder_device->context.name = name;
mutex_init(&binder_device->context.context_mgr_node_lock);
/* -------------------------------------------------------------
* 4. 向 Linux 内核正式注册杂项设备 (Misc Device)
* 调用标准内核接口 `misc_register`。这一步执行成功后,
* Android 的 `/dev/` 目录下就会真正暴露出对应的字符设备节点(如 `/dev/binder`)。
* ------------------------------------------------------------- */
ret = misc_register(&binder_device->miscdev);
if (ret < 0) {
kfree(binder_device); // 如果注册失败,释放刚才分配的设备内存
return ret;
}
/* -------------------------------------------------------------
* 5. 挂载到驱动全局设备链表
* 注册成功后,将该设备结构体添加到驱动全局的 `binder_devices` 哈希链表中,
* 方便驱动后续进行统一管理以及在卸载/失败时倒序销毁。
* ------------------------------------------------------------- */
binder_add_device(binder_device);
return ret;
}
c
static int __init binder_init(void)
{
int ret;
char *device_name, *device_tmp;
struct binder_device *device;
struct hlist_node *tmp;
char *device_names = NULL;
const struct binder_debugfs_entry *db_entry;
...
/* -------------------------------------------------------------
* 传统字符设备模式:解析并创建多域 Binder 设备 (适配 Project Treble)
* 如果系统编译时没有开启新的 binderfs 文件系统,则走此传统流程。
* 内核启动参数 binder_devices_param 通常被配置为 "binder,hwbinder,vndbinder"。
* ------------------------------------------------------------- */
if (!IS_ENABLED(CONFIG_ANDROID_BINDERFS) && strcmp(binder_devices_param, "") != 0) {
// 拷贝一份字符串,防止在原字符串上操作破坏数据
device_names = kstrdup(binder_devices_param, GFP_KERNEL);
if (!device_names) {
ret = -ENOMEM;
goto err_alloc_device_names_failed;
}
// 使用 strsep 函数以逗号 "," 为分隔符,循环切分字符串
device_tmp = device_names;
while ((device_name = strsep(&device_tmp, ","))) {
/* 核心子函数:内部会调用标准内核的 misc_register(),
* 正式在 /dev/ 目录下注册对应的杂项字符设备实体:
* - /dev/binder (常规 App 与系统服务通信)
* - /dev/hwbinder (系统与 HIDL/HAL 硬件层通信)
* - /dev/vndbinder (供应商/底层厂商进程间通信) */
ret = init_binder_device(device_name);
if (ret)
goto err_init_binder_device_failed;
}
}
/* -------------------------------------------------------------
* 现代模式:初始化 binderfs 文件系统 (Android 10+ 重大演进)
* 初始化一种新的、专门为 Binder 打造的虚拟文件系统。
* 它允许 Android 在容器化、沙箱或者模拟器环境中,通过 mount 动态、
* 隔离地创建和销毁独立的 Binder 设备节点,互不干扰,极大提升了安全性和灵活性。
* ------------------------------------------------------------- */
ret = init_binderfs();
if (ret)
goto err_init_binderfs_failed;
return ret;
/* -------------------------------------------------------------
* 严密的错误回滚机制 (倒序释放)
* 如果上述任何一步初始化失败,内核绝不允许驱动"半死不活"地留在内存里,
* 必须顺着相反的顺序,逐个注销设备、释放内存、删除调试目录,确保安全退出。
* ------------------------------------------------------------- */
err_init_binderfs_failed:
// ...
err_init_binder_device_failed:
hlist_for_each_entry_safe(device, tmp, &binder_devices, hlist) {
misc_deregister(&device->miscdev); // 注销已经注册成功的 /dev 设备
binder_remove_device(device);
kfree(device);
}
kfree(device_names);
err_alloc_device_names_failed:
debugfs_remove_recursive(binder_debugfs_dir_entry_root); // 递归删除整个调试文件夹
binder_alloc_shrinker_exit(); // 释放内存回收器
return ret;
}
drivers/android/binder_internal.h
c
/**
* struct binder_device - 表示一个 Binder 设备节点(如 /dev/binder、/dev/hwbinder、/dev/vndbinder)
*/
struct binder_device {
// 哈希链表节点,用于将当前 binder_device 挂入全局 binder_devices 链表,方便统一管理所有 Binder 设备。
struct hlist_node hlist;
// Linux 杂项字符设备对象,负责注册 /dev/binder 等设备节点,
// 并关联 Binder 驱动的文件操作(binder_fops)。
struct miscdevice miscdev;
// 当前 Binder 设备对应的上下文(Binder Context),
// 保存该 Binder 域(Context)的全局信息,例如 Context Manager(ServiceManager)。
struct binder_context context;
...
};
include/linux/miscdevice.h
c
struct miscdevice {
int minor; // 次设备号。
const char *name; // 设备名称 , name="binder" ==> /dev/binder。
// 文件操作集(最重要)。
// 指向 Binder 驱动实现的 binder_fops,
// 用户空间调用 open、ioctl、mmap、poll、release 等系统调用时,
// 最终都会回调到这里对应的函数。
const struct file_operations *fops;
...
};
binder_init 最主要的功能就是 misc_register :创建设备节点,将自己注册一个 misc(杂项)设备 dev/binder 或 dev/hwbinder,定义了文件操作结构体 binder_fops,该结构体将文件操作(open、mmap、unlocked_ioctl 等)映射到具体的实现函数(binder_open、binder_mmap、binder_ioctl),用户空间通过 open("dev/binder",O_RDWR)来打开设备与驱动交互。 binder_init 是准备操作,让用户空间有了一个可以敲门的入口(设备文件)
二、binder_open(打开设备)
当用户空间(App 进程或系统服务进程)调用 open("dev/binder",O_RDWR) 时,内核会调用驱动中的 binder_open 函数。它的主要任务是为该进程创建一个专属的、用于管理其 Binder IPC 状态的数据结构。 注意:几乎每个进程每个应用都有自己专属的 Binder 数据结构,这一点也为 Binder 的一次拷贝打下基础!
drivers/android/binder.c
c
static int binder_open(struct inode *nodp, struct file *filp)
{
// 当前 binder 进程结构体
struct binder_proc *proc, *itr;
struct binder_device *binder_dev;
struct binderfs_info *info;
struct dentry *binder_binderfs_dir_entry_proc = NULL;
bool existing_pid = false;
/* -------------------------------------------------------------
* 1. 内存分配
* 为代表当前进程的 `binder_proc` 结构体在内核中分配内存。
* ------------------------------------------------------------- */
proc = kzalloc_obj(*proc);
if (proc == NULL)
return -ENOMEM;
/* -------------------------------------------------------------
* 2. 进程基本信息与锁的初始化
* 初始化双重锁(内部锁与外部锁,用于高并发下保护红黑树等结构)。
* 获取并保存当前进程主线程(group_leader)的 task_struct 和进程 PID(TGID)。
* ------------------------------------------------------------- */
dbitmap_init(&proc->dmap);
spin_lock_init(&proc->inner_lock);
spin_lock_init(&proc->outer_lock);
proc->tsk = get_task_struct(current->group_leader); // 绑定主线程 task
proc->pid = current->tgid; // 保存进程的 PID
proc->cred = get_cred(filp->f_cred); // 保存安全凭证(用于权限校验)
INIT_LIST_HEAD(&proc->todo); // 初始化待处理任务队列
proc->default_priority = task_nice(current); // 记录进程的默认调度优先级
/* -------------------------------------------------------------
* 3. 识别并绑定对应的 Binder 域上下文
* 区分系统是通过传统字符设备还是通过现代的 binderfs 打开的。
* 获取对应的设备实例(如 /dev/binder),并将进程的 context 指针指向它,
* 确保进程在该 Binder 域(常规、硬件层或供应商层)内进行隔离通信。
* ------------------------------------------------------------- */
if (is_binderfs_device(nodp)) {
binder_dev = nodp->i_private;
info = nodp->i_sb->s_fs_info;
binder_binderfs_dir_entry_proc = info->proc_log_dir;
} else {
binder_dev = container_of(filp->private_data, struct binder_device, miscdev);
}
refcount_inc(&binder_dev->ref);
proc->context = &binder_dev->context; // 绑定当前 Binder 域的上下文
/* -------------------------------------------------------------
* 4. 关键初始化:绑定 Binder 内存分配器
* 初始化内存分配结构体(binder_alloc)。此时只是初始化结构,
* 真正的物理和虚拟内存映射要等到后续调用 `mmap` 时才真正发生。
* ------------------------------------------------------------- */
binder_alloc_init(&proc->alloc);
INIT_LIST_HEAD(&proc->delivered_death); // 初始化已发送的死亡通知队列
INIT_LIST_HEAD(&proc->waiting_threads); // 初始化正在等待任务的空闲 Binder 线程队列
/* -------------------------------------------------------------
* 5. 绑定到文件句柄 (关键步骤)
* 将新创建的 `proc` 账本指针存入 Linux 核心文件结构体的 `private_data`。
* 这样后续进程调用 `mmap` 或 `ioctl` 时,内核能直接通过 `filp->private_data` 找到它。
* ------------------------------------------------------------- */
filp->private_data = proc;
/* -------------------------------------------------------------
* 6. 将当前进程加入全局进程哈希表
* 加锁,遍历全局 `binder_procs` 链表看该进程是否已经打开过 Binder。
* 随后将当前进程的 `proc_node` 挂入全局总账本,方便驱动统一管理。
* ------------------------------------------------------------- */
mutex_lock(&binder_procs_lock);
hlist_for_each_entry(itr, &binder_procs, proc_node) {
if (itr->pid == proc->pid) {
existing_pid = true;
break;
}
}
hlist_add_head(&proc->proc_node, &binder_procs); // 挂入全局管理链表
mutex_unlock(&binder_procs_lock);
...
return 0;
}
drivers/android/binder_internal.h
c
struct binder_proc {
struct hlist_node proc_node; // 挂载点,用于将当前进程加入驱动的全局进程链表
/* -------------------------------------------------------------
* 核心基础设施:红黑树群
* ------------------------------------------------------------- */
struct rb_root threads; // 线程红黑树:管理当前进程中所有参与 Binder 通信的线程 (binder_thread)
struct rb_root nodes; // 实体红黑树:管理当前进程作为 "Server端" 暴露给外界的所有 Binder 本地对象 (binder_node)
struct rb_root refs_by_desc; // 句柄红黑树:管理当前进程作为 "Client端" 持有的所有远程 Binder 对象的引用,按用户态的 handle 句柄排序
struct rb_root refs_by_node; // 引用红黑树:同上,但是按内核中的 binder_node 内存地址排序,方便内核快速查重
struct list_head waiting_threads; // 空闲线程链表:挂载当前进程中正处于休眠、等待接收 IPC 请求的 Binder 线程
int pid; // 进程的 PID (TGID)
struct task_struct *tsk; // 指向进程主线程的内核 task 结构体
const struct cred *cred; // 进程的安全凭证,包含 UID/GID,IPC 通信时驱动用来做安全权限检查
/* -------------------------------------------------------------
* 任务与同步控制
* ------------------------------------------------------------- */
struct list_head todo; // 进程级待处理任务队列:当一个 IPC 请求是发给整个进程而不是某个特定线程时,会丢进这里
struct binder_stats stats; // 统计数据:记录当前进程创建了多少个 Binder 对象、进行了多少次传输等
struct list_head delivered_death; // 死亡通知队列:当该进程监听的远程服务挂掉时,驱动向该进程派发的通知列表
/* -------------------------------------------------------------
* 线程池管理
* ------------------------------------------------------------- */
u32 max_threads; // 线程池最大线程数:由应用层通过 ioctl(BINDER_SET_MAX_THREADS) 设定(通常为 15)
int requested_threads; // 驱动已向应用层请求创建、但应用层尚未启动完成的线程数
int requested_threads_started; // 当前进程线程池中已经成功启动并运转的 Binder 线程数
/* -------------------------------------------------------------
* 内存与上下文环境
* ------------------------------------------------------------- */
struct binder_alloc alloc; // 内存分配器:账本里最核心的资产,记录了 mmap 映射的用户态/内核态地址及 Buffer 链表
struct binder_context *context; // 指向所属 Binder 设备的上下文管理器(决定了谁是当前域的 ServiceManager)
spinlock_t inner_lock; // 内部自旋锁:保护线程树、实体树以及 todo 队列
spinlock_t outer_lock; // 外部自旋锁:保护引用树、句柄描述符等
};
binder_open 的主要作用: binder_proc 结构体是每个进程独有的最关键的数据结构,它存储了该进程所有与 binder 相关的上下文信息,驱动会分配内核内存来创建一个新的 binder_proc 对象。 重要字段:
proc->task:指向该进程的 task_struct(进程控制块)proc->pid和 proc->tgid:存储进程的 PID 和 TGIDproc->buffer:指向进程映射的 Binder 内存缓冲区(此时为 NULL,在 mmap 时分配)proc->pages:指向该缓冲区的对应物理页结构体数组binder_proc对象存储在 filp->private_data 字段中,这样在该进程后续调用 mmap,ioctl 时,驱动可以通过 filp->private_data 直接拿到属于这个进程的 binder_proc,无需再次查找。
最后将该进程的 binder_proc 插入到一个全局的 binder_procs 链表中,方便驱动进行全局管理和查找。
binder_open 的工作十分单纯:它在内核里圈出了一块地(proc),并在里面整齐地栽好了几棵红黑树(管理线程、服务和引用)和几个队列(管理任务和空闲线程),最后把这个地盘的钥匙丢进了文件描述符的 private_data 里。一切安排妥当,只欠应用层通过 mmap 来给这块地盘"铺上草坪"(分配内存)。
三、binder_mmap(内存映射)
drivers/android/binder.c
当用户空间进程 (在 open 之后) 调用 mmap 来映射 Binder 设备时,会触发此函数。这是 Binder 实现"一次拷贝"的关键。它的任务是在内核空间和用户空间之间共享同一块物理内存,作为数据传输的缓冲区。
c
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
/* 1. 从 file 结构体中直接取出在 binder_open 中塞进去的 binder_proc 账本 */
struct binder_proc *proc = filp->private_data;
/* 2. 跨进程安全性检查:确保发起 mmap 的线程和 open 时的线程属于同一个进程组(TGID) */
if (!same_thread_group(proc->tsk, current))
return -EINVAL;
/* 3. 拦截非法标志:过滤掉不被允许的内存映射属性,如可读写的 VM_WRITE */
if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
pr_err("%s: %d %lx-%lx %s failed %d\n", __func__,
proc->pid, vma->vm_start, vma->vm_end, "bad vm_flags", -EPERM);
return -EPERM;
}
/* 4. 修改 VMA 内存区标志 (防御性操作)
* VM_DONTCOPY: fork 子进程时不复制这块虚拟内存。
* VM_MIXEDMAP: 告诉内核该内存区同时混合了物理页和页帧号。
* VM_MAYWRITE (被清除): **核心保护**,绝不允许用户空间直接通过指针 write 这块内存,
* 用户空间只能以只读形式读取内核帮它拷贝过来的数据。 */
vm_flags_mod(vma, VM_DONTCOPY | VM_MIXEDMAP, VM_MAYWRITE);
/* 5. 绑定虚拟内存块的操作函数和私有数据 */
vma->vm_ops = &binder_vm_ops; // 绑定这块内存在被关闭(close)等事件发生时的内核回调
vma->vm_private_data = proc; // 方便在其它内存管理回调中直接找回 proc 账本
/* 6. 将具体的物理与地址管理逻辑路由到专门的内存分配器中 */
return binder_alloc_mmap_handler(&proc->alloc, vma);
}
drivers/android/binder_alloc.c
c
int binder_alloc_mmap_handler(struct binder_alloc *alloc, struct vm_area_struct *vma)
{
struct binder_buffer *buffer;
const char *failure_string;
int ret;
/* 1. 检查当前申请内存的虚拟内存管理结构(mm)是否与分配器记录的一致 */
if (unlikely(vma->vm_mm != alloc->mm)) {
ret = -EINVAL;
failure_string = "invalid vma->vm_mm";
goto err_invalid_mm;
}
/* 2. 互斥锁控制:检查并计算映射大小,防止重复 mmap */
mutex_lock(&binder_alloc_mmap_lock);
if (alloc->buffer_size) {
ret = -EBUSY;
failure_string = "already mapped";
goto err_already_mapped;
}
/* 限制 Binder 驱动的最大内存为 4MB,即使用户层传入的大于 4MB,也会被 min_t 截断 */
alloc->buffer_size = min_t(unsigned long, vma->vm_end - vma->vm_start, SZ_4M);
mutex_unlock(&binder_alloc_mmap_lock);
/* 3. 记录用户空间虚拟内存的起点 (接收端拿到这个基地址+偏移量就能读数据) */
alloc->vm_start = vma->vm_start;
/* 4. **分配物理页指针数组**
* 此时**并没有**分配真正的物理内存页,而是分配了一个指针数组(pages),
* 数量等于映射总大小除以系统页大小(4KB)。数组用来存放以后动态申请的物理页指针(懒加载)。 */
alloc->pages = kvzalloc_objs(alloc->pages[0], alloc->buffer_size / PAGE_SIZE);
if (!alloc->pages) {
ret = -ENOMEM;
failure_string = "alloc page array";
goto err_alloc_pages_failed;
}
/* 5. 创建第一个全功能的内存块描述符(binder_buffer) */
buffer = kzalloc_obj(*buffer);
if (!buffer) {
ret = -ENOMEM;
failure_string = "alloc buffer struct";
goto err_alloc_buf_struct_failed;
}
/* 6. 初始化这块大内存的属性并挂载到空闲链表
* 将整块内存的起点赋值给 buffer,并将其标记为空闲(free=1)。
* 随后塞入 `alloc->buffers` 总链表以及空闲红黑树中,供后续 IPC 事务动态切分使用。 */
buffer->user_data = alloc->vm_start;
list_add(&buffer->entry, &alloc->buffers);
buffer->free = 1;
binder_insert_free_buffer(alloc, buffer); // 插入空闲 buffer 树
/* 7. 分配异步通信空间上限
* 为防止单向异步广播(Oneway)把进程的 Binder 内存全部撑爆,
* 内核强行限制异步通信最多只能使用整个映射空间的一半(通常为 512KB)。 */
alloc->free_async_space = alloc->buffer_size / 2;
/* 8. 标志着该进程的 Binder 内存分配器已经完全就绪 */
binder_alloc_set_mapped(alloc, true);
return 0;
/* 9. 严密的错误回滚分支 */
...
return ret;
}
binder_mmap 执行完时,实际上内核并没有立刻调用分配并映射具体物理内存页的代码,先在用户空间锁定一段虚拟地址(alloc->vm_start 到 alloc->vm_end) , 在内核里分配好用来装物理页指针的空数组(alloc->pages),以及把整块空间标记为一个超大的空闲内存块(binder_buffer) 真正的物理页分配、物理页与用户/内核双重映射的代码,被延后到了具体的 ioctl 通信阶段 ------当发送端真正要发数据时,内核才会临时申请物理页并刷页表(调用 binder_alloc_new_buf_locked -> binder_alloc_page)。这极大地节约了系统的物理内存。
实际拷贝完整逻辑:
c
binder_ioctl()
└── binder_ioctl_write_read()
└── binder_thread_write()
└── binder_transaction() [发现要把数据发给目标进程]
└── binder_alloc_new_buf() [总调度:在接收端分配缓冲区]
├── binder_alloc_new_buf_locked() [第1步:红黑树账本划分,圈出虚拟地址]
└── binder_install_buffer_pages() [第2步:物理页到位!真正调用底层映射]
└── binder_alloc_page() [真正调用 alloc_page、vm_map_ram 和 vm_insert_page 建立双重映射的地方!]
└── binder_alloc_copy_user_to_buffer() [第3步:开始往接收端倒数据]
└── binder_alloc_get_page() [直接定位刚才备好的物理页,直接返回 alloc->pages[index]]
└── copy_from_user() [【唯一一次拷贝】数据落入物理页,接收端立刻可见]
drivers/android/binder_alloc.c
c
binder_alloc_copy_user_to_buffer(struct binder_alloc *alloc,
struct binder_buffer *buffer,
binder_size_t buffer_offset,
const void __user *from,
size_t bytes)
{
if (!check_buffer(alloc, buffer, buffer_offset, bytes))
return bytes;
while (bytes) {
unsigned long size;
unsigned long ret;
struct page *page;
pgoff_t pgoff;
void *kptr;
// 物理分配与双重映射真正发生地
// 内核申请 4KB 物理内存页
// 挂载内核虚拟地址映射
// 挂载接收端用户虚拟地址映射
page = binder_alloc_get_page(alloc, buffer,
buffer_offset, &pgoff);
size = min_t(size_t, bytes, PAGE_SIZE - pgoff);
// 获取内核临时写指针
kptr = kmap_local_page(page) + pgoff;
// 【唯一一次拷贝】数据落入物理页
ret = copy_from_user(kptr, from, size);
// 解映射,完成写入
kunmap_local(kptr);
if (ret)
return bytes - size + ret;
bytes -= size;
from += size;
buffer_offset += size;
}
return 0;
}
当开始倒数据时,binder_alloc_get_page 已经是一条极为轻量级的绿色通道------它不需要再分配内存,只需要通过简单的位运算计算出 index,直接把前一步刚刚备好的 alloc->pages[index] 物理页指针甩给 copy_from_user 即可,实现极致的传输性能。
一次拷贝的完整流程:当 client 向 Server 发送数据时
- Client 将数据拷贝一次,写入到这块共享内存中(用户空间->共享缓冲区)
- 驱动通过 ioctl 命令通知 Server 有数据到来
- 内核无需再次拷贝数据到 Server 进程。因为内核和 Server 进程共享同一块物理内存,内核可以直接将缓冲区地址传递给 Server 进程,Server 进程直接读取即可。
mmap 为用户进程建立了一段虚拟地址映射,而这段虚拟地址最终映射到内核分配的物理页,因此 Binder 驱动和用户进程都能访问同一块物理内存。
binder_mmap 建立了内核与用户进程共享的通信缓冲区,是 Binder 性能优于传统 IPC(如管道 、Socket 需要两次拷贝)的核心原因。
四、binder_ioctl(输入输出控制)
这是 Binder 驱动的大脑和调度中心。用户进程的所有 Binder IPC 操作,最终都通过 ioctl 系统调用下发到驱动,并由 binder_ioctl 函数处理。它是最复杂的一个函数。
drivers/android/binder.c
c
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
struct binder_proc *proc = filp->private_data; // 取出 open 时建档的进程结构体
struct binder_thread *thread;
void __user *ubuf = (void __user *)arg;
/* -------------------------------------------------------------
* 1. 获取/创建当前调用线程的上下文
* 每一个调用 ioctl 的线程,在内核中都有一个专属的 `binder_thread`。
* ------------------------------------------------------------- */
thread = binder_get_thread(proc);
if (thread == NULL) return -ENOMEM;
/* -------------------------------------------------------------
* 2. 根据 cmd 命令进行核心业务路由 (大 Switch-Case)
* ------------------------------------------------------------- */
switch (cmd) {
case BINDER_WRITE_READ:
/* 【通信核心】:处理真正的数据读写请求 */
ret = binder_ioctl_write_read(filp, arg, thread);
if (ret) goto err;
break;
case BINDER_SET_MAX_THREADS: {
u32 max_threads;
/* 拷贝应用层希望设置的线程池最大线程数(通常是 15) */
if (copy_from_user(&max_threads, ubuf, sizeof(max_threads))) return -EINVAL;
proc->max_threads = max_threads; // 记录到进程账本
break;
}
case BINDER_SET_CONTEXT_MGR:
/* 【服务注册】:将当前进程(即 servicemanager)提升为整个 Binder 域的大管家 */
ret = binder_ioctl_set_ctx_mgr(filp, NULL);
if (ret) goto err;
break;
case BINDER_THREAD_EXIT:
/* 【线程退出】:当应用层的 Binder 线程由于闲置等原因准备退出时,通知内核注销 */
binder_thread_release(proc, thread);
thread = NULL;
break;
case BINDER_VERSION: {
struct binder_version __user *ver = ubuf;
/* 【版本校验】:将内核当前的协议版本号写入应用层传进来的指针中 */
if (put_user(BINDER_CURRENT_PROTOCOL_VERSION, &ver->protocol_version)) return -EINVAL;
break;
}
case BINDER_FREEZE: {
/* 【进程冻结适配】:Android 12 非常依赖的进程应用冻结机制(Cgroup Freeze 改良版)
* 拷贝应用层传入的冻结信息,调用底层将目标进程的 Binder 通信暂时挂起或限制 */
struct binder_freeze_info info;
if (copy_from_user(&info, ubuf, sizeof(info))) return -EFAULT;
// ... 遍历并执行冻结逻辑 ...
break;
}
default:
ret = -EINVAL;
goto err;
}
ret = 0;
err:
// ... 异常与清理 ...
return ret;
}
高并发下,应用层有多个线程在同时调用 ioctl。这个函数确保每个线程在内核里都有且仅有一个"跟班"(binder_thread)来记录自己的状态。
c
static struct binder_thread *binder_get_thread(struct binder_proc *proc)
{
struct binder_thread *thread;
struct binder_thread *new_thread;
/* 1. 加锁,去当前进程的 threads 红黑树里找,看当前线程以前是否来过 */
binder_inner_proc_lock(proc);
thread = binder_get_thread_ilocked(proc, NULL);
binder_inner_proc_unlock(proc);
/* 2. 如果没来过(第一次调用 ioctl),则在内核现场分配一个 thread 结构体 */
if (!thread) {
new_thread = kzalloc_obj(*thread);
if (new_thread == NULL) return NULL;
/* 再度加锁,正式将新 thread 插入到进程的 threads 红黑树中 */
binder_inner_proc_lock(proc);
thread = binder_get_thread_ilocked(proc, new_thread);
binder_inner_proc_unlock(proc);
/* 如果别的线程刚好抢先插入了,就把多余的释放掉 */
if (thread != new_thread) kfree(new_thread);
}
return thread; // 返回属于当前线程的 binder_thread 指针
}
c
一个 Linux 进程
│
binder_proc
│
threads (RB Tree)
│
┌───────────────┼───────────────┐
▼ ▼ ▼
binder_thread1 binder_thread2 binder_thread3
│ │ │
▼ ▼ ▼
task_struct1 task_struct2 task_struct3
│ │ │
Thread A Thread B Thread C
binder_thread并不是内核创建的线程,而是 Binder 驱动为了管理"调用 Binder 的用户线程"而创建的一个内核对象。
也就是说 Binder 会为每一个调用 ioctl 的线程创建一个独立的 binder_thread(档案)
c
static int binder_ioctl_write_read(struct file *filp, unsigned long arg, struct binder_thread *thread)
{
int ret = 0;
struct binder_proc *proc = filp->private_data;
void __user *ubuf = (void __user *)arg;
struct binder_write_read bwr;
/* 1. 从应用层把收发总数据结构(bwr)拷贝到内核空间 */
if (copy_from_user(&bwr, ubuf, sizeof(bwr))) return -EFAULT;
/* 2. 【写流程】:如果 write_size > 0,说明当前线程有数据/指令要发送给别的人 */
if (bwr.write_size > 0) {
/* 进入写分支,内部会调用在前面流程中看到的 binder_transaction 并执行分配与拷贝 */
ret = binder_thread_write(proc, thread, bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
if (ret < 0) {
bwr.read_consumed = 0; // 如果写失败,清空读计数并退出
goto out;
}
}
/* 3. 【读流程】:如果 read_size > 0,说明当前线程是来"收件"的(或者是发起同步调用等待回复) */
if (bwr.read_size > 0) {
/* 进入读分支。如果当前没有接收到任何信件,该线程会在这里休眠阻塞 */
ret = binder_thread_read(proc, thread, bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK);
/* 读完被唤醒后,顺便检查进程的任务队列,如果有工作则唤醒进程中的其他空闲线程来干活 */
binder_inner_proc_lock(proc);
if (!binder_worklist_empty_ilocked(&proc->todo)) binder_wakeup_proc_ilocked(proc);
binder_inner_proc_unlock(proc);
if (ret < 0) goto out;
}
out:
/* 4. **将执行结果同步回应用层**
* 关键:将更新后的 `write_consumed`(消耗了多少写数据)和 `read_consumed`(读取了多少数据)
* 重新写回用户空间,好让应用层知道下一步该怎么处理。 */
if (copy_to_user(ubuf, &bwr, sizeof(bwr))) ret = -EFAULT;
return ret;
}
binder_ioctl 构建了高并发跨进程通信的高速传输大厅 通过 binder_get_thread 确保进来的每一个线程都有内核红黑树节点跟踪,互不干扰。 binder_ioctl_write_read 极其精妙地允许应用层在同一次系统调用中,既把要发的数据塞给内核(binder_thread_write),又顺便把别人发给自己的数据顺带捞回去(binder_thread_read)。这种寄件、收件二合一的设计,把 Linux 的进程上下文切换开销降到了最低。
五、最后
binder_init让/dev/binder设备在系统启动时诞生。- 应用层进程一启动,
binder_open帮它在内核创建了存放服务引用的几棵红黑树,binder_mmap帮它抢占了一块内核只读的虚拟地址空间。 - 当 Client 端真正要发送事务时,调用
binder_ioctl进入内核:
- 驱动通过
binder_alloc_new_buf在接收端的虚拟地址上圈地。 - 紧接着,
binder_install_buffer_pages临门一脚,动态申请物理页并执行vm_insert_page修改页表,把物理内存同时挂载到内核与接收端的用户态。 - 最后,
copy_from_user把数据往这块物理内存里一丢(唯一一次拷贝),接收端线程被唤醒,睁眼直接在自己的用户态读取数据。