Android Binder 驱动 - 内核驱动层源码初探

10 阅读22分钟

前言

前面几篇文章中涉及 Binder 内核方法,这篇文章统一做一下介绍和总结,包括:binder_initbinder_openbinder_mmapbinder_ioctl

Binder 驱动的核心目标是高效地实现进程间通信(IPC),它通过一种"一次拷贝"和基于线程池的模型来 达成这一目标,这些函数共同协作,完成了驱动的初始化、设备打开,内存映射和具体 IPC 命令处理的全流程。

一、binder_init(模块初始化)

这是 Binder 驱动模块被加载到内核时调用的初始化函数。它的主要作用是创建设备节点,为 Binder 驱动运行准备基础的内核基础设施,(在 Linux 中万物皆文件嘛) 让用户 空间进程能够通过文件操作来访问 Binder 驱动。 Linux Kernel 源码位置(非 AOSP 中):

drivers/android/binder.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;
}
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

/**
 * 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

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

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

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 和 TGID
  • proc->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 实现“一次拷贝”的关键。它的任务是在内核空间和用户空间之间共享同一块物理内存,作为数据传输的缓冲区。

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

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_startalloc->vm_end) , 在内核里分配好用来装物理页指针的空数组(alloc->pages),以及把整块空间标记为一个超大的空闲内存块(binder_buffer) 真正的物理页分配、物理页与用户/内核双重映射的代码,被延后到了具体的 ioctl 通信阶段——当发送端真正要发数据时,内核才会临时申请物理页并刷页表(调用 binder_alloc_new_buf_locked -> binder_alloc_page)。这极大地节约了系统的物理内存。

实际拷贝完整逻辑:

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

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 发送数据时

  1. Client 将数据拷贝一次,写入到这块共享内存中(用户空间->共享缓冲区)
  2. 驱动通过 ioctl 命令通知 Server 有数据到来
  3. 内核无需再次拷贝数据到 Server 进程。因为内核和 Server 进程共享同一块物理内存,内核可以直接将缓冲区地址传递给 Server 进程,Server 进程直接读取即可。

mmap 为用户进程建立了一段虚拟地址映射,而这段虚拟地址最终映射到内核分配的物理页,因此 Binder 驱动和用户进程都能访问同一块物理内存。

binder_mmap 建立了内核与用户进程共享的通信缓冲区,是 Binder 性能优于传统 IPC(如管道 、Socket 需要两次拷贝)的核心原因。

四、binder_ioctl(输入输出控制)

这是 Binder 驱动的大脑和调度中心。用户进程的所有 Binder IPC 操作,最终都通过 ioctl 系统调用下发到驱动,并由 binder_ioctl 函数处理。它是最复杂的一个函数。

drivers/android/binder.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)来记录自己的状态。

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 指针
}
                一个 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(档案)


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 的进程上下文切换开销降到了最低。

五、最后

  1. binder_init/dev/binder 设备在系统启动时诞生。
  2. 应用层进程一启动,binder_open 帮它在内核创建了存放服务引用的几棵红黑树,binder_mmap 帮它抢占了一块内核只读的虚拟地址空间。
  3. 当 Client 端真正要发送事务时,调用 binder_ioctl 进入内核:
  • 驱动通过 binder_alloc_new_buf 在接收端的虚拟地址上圈地。
  • 紧接着,binder_install_buffer_pages 临门一脚,动态申请物理页并执行 vm_insert_page 修改页表,把物理内存同时挂载到内核与接收端的用户态。
  • 最后,copy_from_user 把数据往这块物理内存里一丢(唯一一次拷贝),接收端线程被唤醒,睁眼直接在自己的用户态读取数据。