Binder 深入理解 第三篇

39 阅读12分钟

Binder 驱动篇

image.png

向内核中注册Binder设备驱动

static int __init init_binder_device(const char *name)
{
	int ret;
	struct binder_device *binder_device;

	binder_device = kzalloc(sizeof(*binder_device), GFP_KERNEL);
	if (!binder_device)
		return -ENOMEM;

	binder_device->miscdev.fops = &binder_fops;
	binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
	binder_device->miscdev.name = name;

	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);
       // 该行代码真正向内核中注册了Binder设备
	ret = misc_register(&binder_device->miscdev);
	if (ret < 0) {
		kfree(binder_device);
		return ret;
	}

	hlist_add_head(&binder_device->hlist, &binder_devices);

	return ret;
}

binder_miscdev的定义如下:

static struct miscdevice binder_miscdev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "binder",
    .fops = &binder_fops
};

这里指定了Binder设备的名称是“binder”。这样,在用户空间便可以通过对/dev/binder文件进行操作来使用Binder。

binder_miscdev同时也指定了该设备的fops。fops是另外一个结构体,这个结构中包含了一系列的函数指针,其定义如下:

const struct file_operations binder_fops = {  
.owner = THIS_MODULE,  
.poll = binder_poll,  
.unlocked_ioctl = binder_ioctl,  
.compat_ioctl = compat_ptr_ioctl,  
.mmap = binder_mmap,  
.open = binder_open,  
.flush = binder_flush,  
.release = binder_release,  
};

这其中,有三个函数尤为重要,它们是:binder_open,binder_mmap和binder_ioctl。 这是因为,需要使用Binder的进程,几乎总是先通过binder_open打开Binder设备,然后通过binder_mmap进行内存映射。

在这之后,通过binder_ioctl来进行实际的操作。Client对于Server端的请求,以及Server对于Client请求结果的返回,都是通过ioctl完成的。

这里提到的流程如下图所示:

image.png

5. 主要结构

Binder驱动中包含了很多的结构体。为了便于下文讲解,这里我们先对这些结构体做一些介绍。

驱动中的结构体可以分为两类:

(1) 一类是与用户空间共用的,这些结构体在Binder通信协议过程中会用到。因此,这些结构体定义在binder.h中,包括:

结构体名称                说明
--------------------------------------------------------------
binder_write_read         存储一次读写操作的数据
binder_transaction_data   存储一次事务的数据
flat_binder_object        描述在Binder IPC中传递的对象,见下文
transaction_flags         描述事务的flag,例如是否是异步请求,是否支持fd
binder_version            存储Binder的版本号
binder_ptr_cookie         包含了一个指针和一个cookie
binder_handle_cookie      包含了一个句柄和一个cookie
binder_pri_desc           暂未用到
binder_pri_ptr_cookie     暂未用到

这其中,binder_write_read和binder_transaction_data这两个结构体最为重要,它们存储了IPC调用过程中的数据。关于这一点,我们在下文中会讲解。

(2) Binder驱动中,还有一类结构体是仅仅Binder驱动内部实现过程中需要的,它们定义在binder.c中,包括:

结构体名称                        说明
--------------------------------------------------------------
binder_proc                     描述使用Binder的进程
binder_thread                   描述使用Binder的线程
binder_node                     描述Binder实体节点,即:对应了一个Server
binder_ref                      描述对于Binder实体的引用
binder_buffer                   描述Binder通信过程中存储数据的Buffer
binder_work                     描述通信过程中的一项任务
binder_transaction              描述一次事务的相关信息
binder_deferred_state           描述延迟任务
binder_ref_death                描述Binder实体死亡的信息
binder_transaction_log          debugfs日志
binder_transaction_log_entry    debugfs日志条目

6. Binder协议
Binder协议可以分为控制协议和驱动协议两类。

控制协议是进程通过ioctl(“/dev/binder”) 与Binder设备进行通讯的协议,是 ioctl(cmd) -> binder_ioctl(cmd) 的 cmd 参数,该协议包含以下几种命令:

命令                          说明
--------------------------------------------------------------
BINDER_WRITE_READ             读写操作,最常用的命令。IPC过程就是通过这个命令进行数据传递
BINDER_VERSION                获取Binder驱动的版本号
BINDER_SET_MAX_THREADS        设置进程支持的最大线程数量
BINDER_SET_CONTEXT_MGR        设置自身为ServiceManager
BINDER_THREAD_EXIT            通知驱动Binder线程退出
BINDER_SET_IDLE_PRIORITY      暂未用到
BINDER_SET_IDLE_TIMEOUT       暂未用到

7. Binder的驱动协议描述了对于Binder驱动的具体使用过程。驱动协议又可以分为两类:

(1) 一类是 binder_driver_command_protocol,描述了进程发送给Binder驱动的命令。应用发给驱动的命令集合, 这些是从 binder_buffer 中解析出来的命令!在 binder_thread_write 中使用。共包含17个命令,分别是:

命令                            说明                                    参数类型
-----------------------------------------------------------------------------------------------
BC_TRANSACTION                  Binder事务,即:Client对于Server的请求    binder_transaction_data
BC_REPLY                        事务的应答,即:Server对于Client的回复     binder_transaction_data
BC_FREE_BUFFER                  通知驱动释放Buffer                       binder_uintptr_t
BC_ACQUIRE                      强引用计数+1                             __u32
BC_RELEASE                      强引用计数-1                             __u32
BC_INCREFS                      弱引用计数+1                             __u32
BC_DECREFS                      弱引用计数-1                             __u32
BC_ACQUIRE_DONE                 BR_ACQUIRE的回复                        binder_ptr_cookie
BC_INCREFS_DONE                 BR_INCREFS的回复                        binder_ptr_cookie
BC_ENTER_LOOPER                 通知驱动主线程ready                      void
BC_REGISTER_LOOPER              通知驱动子线程ready                      void
BC_EXIT_LOOPER                  通知驱动线程已经退出                      void
BC_REQUEST_DEATH_NOTIFICATION   请求接收死亡通知                         binder_handle_cookie
BC_CLEAR_DEATH_NOTIFICATION     去除接收死亡通知                         binder_handle_cookie
BC_DEAD_BINDER_DONE             已经处理完死亡通知                        binder_uintptr_t
BC_ATTEMPT_ACQUIRE              暂未实现                                -
BC_ACQUIRE_RESULT               暂未实现                                -

(2) 一类是 binder_driver_return_protocol,描述了Binder驱动发送给进程的命令,是要放到 binder_buffer 中的命令,在 binder_thread_read 函数中使用,binder_driver_return_protocol共包含18个命令,分别是:

返回类型                          说明                                    参数类型
-----------------------------------------------------------------------------------------------
BR_OK                            操作完成                                void
BR_NOOP                          操作完成                                void
BR_ERROR                         发生错误                                __s32
BR_TRANSACTION                   通知进程收到一次Binder请求(Server端)     binder_transaction_data
BR_REPLY                         通知进程收到Binder请求的回复(Client)     binder_transaction_data
BR_TRANSACTION_COMPLETE          驱动对于接受请求的确认回复                 void
BR_FAILED_REPLY                  告知发送方通信目标不存在                  void
BR_SPAWN_LOOPER                  通知Binder进程创建一个新的线程            void
BR_ACQUIRE                       强引用计数+1请求                         binder_ptr_cookie
BR_RELEASE                       强引用计数-1请求                         binder_ptr_cookie
BR_INCREFS                       弱引用计数+1请求                         binder_ptr_cookie
BR_DECREFS                       若引用计数-1请求                         binder_ptr_cookie
BR_DEAD_BINDER                   发送死亡通知                             binder_uintptr_t
BR_CLEAR_DEATH_NOTIFICATION_DONE 清理死亡通知完成                         binder_uintptr_t
BR_DEAD_REPLY                    告知发送方对方已经死亡                    void
BR_ACQUIRE_RESULT                暂未实现                                -
BR_ATTEMPT_ACQUIRE               暂未实现                                -
BR_FINISHED                      暂未实现                                -

单独看上面的协议可能很难理解,这里我们以一次Binder请求过程来详细看一下Binder协议是如何通信的,就比较好理解了。

这幅图的说明如下:

a. Binder是C/S架构的,通信过程牵涉到:Client,Server以及Binder驱动三个角色
b. Client对于Server的请求以及Server对于Client回复都需要通过Binder驱动来中转数据
c. BC_XXX命令是进程发送给驱动的命令
d. BR_XXX命令是驱动发送给进程的命令
e. 整个通信过程由Binder驱动控制

image.png

这里再补充说明一下,通过上面的Binder协议的说明中我们看到,Binder协议的通信过程中,不仅仅是发送请求和接受数据这些命令。同时包括了对于引用计数的管理和对于死亡通知的管理(告知一方,通讯的另外一方已经死亡)等功能。

这些功能的通信过程和上面这幅图是类似的:一方发送BC_XXX,然后由驱动控制通信过程,接着发送对应的BR_XXX命令给通信的另外一方。因为这种相似性,对于这些内容就不再赘述了。

在有了上面这些背景知识介绍之后,我们就可以进入到Binder驱动的内部实现中来一探究竟了。

8. 打开Binder设备
任何进程在使用Binder之前,都需要先通过open("/dev/binder")打开Binder设备。上文已经提到,用户空间的open系统调用对应了驱动中的binder_open函数。在这个函数,Binder驱动会为调用的进程做一些初始化工作。binder_open函数代码如下所示:

static int binder_open(struct inode *nodp, struct file *filp)
{
	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;

	binder_debug(BINDER_DEBUG_OPEN_CLOSE, "%s: %d:%d\n", __func__,
		     current->group_leader->pid, current->pid);

    // 创建进程对应的binder_proc对象
	proc = kzalloc(sizeof(*proc), GFP_KERNEL);
	if (proc == NULL)
		return -ENOMEM;
    // 初始化binder_proc
	spin_lock_init(&proc->inner_lock);
	spin_lock_init(&proc->outer_lock);
	get_task_struct(current->group_leader);
	proc->tsk = current->group_leader;
	proc->cred = get_cred(filp->f_cred);
	INIT_LIST_HEAD(&proc->todo);
	init_waitqueue_head(&proc->freeze_wait);
	if (binder_supported_policy(current->policy)) {
		proc->default_priority.sched_policy = current->policy;
		proc->default_priority.prio = current->normal_prio;
	} else {
		proc->default_priority.sched_policy = SCHED_NORMAL;
		proc->default_priority.prio = NICE_TO_PRIO(0);
	}

	/* binderfs stashes devices in i_private */
	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_alloc_init(&proc->alloc);

	binder_stats_created(BINDER_STAT_PROC);
	proc->pid = current->group_leader->pid;
	INIT_LIST_HEAD(&proc->delivered_death);
	INIT_LIST_HEAD(&proc->waiting_threads);
	filp->private_data = proc;

	mutex_lock(&binder_procs_lock);
	hlist_for_each_entry(itr, &binder_procs, proc_node) {
		if (itr->pid == proc->pid) {
			existing_pid = true;
			break;
		}
	}
    // 添加到全局列表binder_procs中
	hlist_add_head(&proc->proc_node, &binder_procs);
	mutex_unlock(&binder_procs_lock);

	if (binder_debugfs_dir_entry_proc && !existing_pid) {
		char strbuf[11];

		snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
		/*
		 * proc debug entries are shared between contexts.
		 * Only create for the first PID to avoid debugfs log spamming
		 * The printing code will anyway print all contexts for a given
		 * PID so this is not a problem.
		 */
		proc->debugfs_entry = debugfs_create_file(strbuf, 0444,
			binder_debugfs_dir_entry_proc,
			(void *)(unsigned long)proc->pid,
			&proc_fops);
	}

	if (binder_binderfs_dir_entry_proc && !existing_pid) {
		char strbuf[11];
		struct dentry *binderfs_entry;

		snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
		/*
		 * Similar to debugfs, the process specific log file is shared
		 * between contexts. Only create for the first PID.
		 * This is ok since same as debugfs, the log file will contain
		 * information on all contexts of a given PID.
		 */
		binderfs_entry = binderfs_create_file(binder_binderfs_dir_entry_proc,
			strbuf, &proc_fops, (void *)(unsigned long)proc->pid);
		if (!IS_ERR(binderfs_entry)) {
			proc->binderfs_entry = binderfs_entry;
		} else {
			int error;

			error = PTR_ERR(binderfs_entry);
			pr_warn("Unable to create file %s in binderfs (error %d)\n",
				strbuf, error);
		}
	}

	return 0;
}

9. 内存映射(mmap)
在打开Binder设备之后,进程还会通过mmap进行内存映射。mmap的作用有如下两个:

(1) 申请一块内存空间,用来接收Binder通信过程中的数据
(2) 对这块内存进行地址映射,以便将来访问
binder_mmap函数对应了mmap系统调用的处理,这个函数也是Binder驱动的精华所在(这里说的binder_mmap函数也包括其内部调用的binder_update_page_range函数,见下文)。

前文我们说到,使用Binder机制,数据只需要经历一次拷贝就可以了,其原理就在这个函数中。

binder_mmap这个函数中,会申请一块物理内存,然后在用户空间和内核空间同时对应到这块内存上。在这之后,当有Client要发送数据给Server的时候,只需一次,将Client发送过来的数据拷贝到Server端的内核空间指定的内存地址即可,由于这个内存地址在服务端已经同时映射到用户空间,因此无需再做一次复制,Server即可直接访问,整个过程如下图所示:

image.png

a. Server在启动之后,调用对/dev/binder设备调用mmap
b. 内核中的binder_mmap函数进行对应的处理:申请一块物理内存,然后在用户空间和内核空间同时进行映射
c. Client通过BINDER_WRITE_READ命令发送请求,这个请求将先到驱动中,同时需要将数据从Client进程的用户空间拷贝到内核空间
d. 驱动通过BR_TRANSACTION通知Server有人发出请求,Server进行处理。由于这块内存也在用户空间进行了映射,因此Server进程的代码可以直接访问

了解原理之后,我们再来看一下Binder驱动的相关源码。这段代码有两个函数:
a. binder_mmap 函数对应了mmap的系统调用的处理
b. binder_update_page_range函数真正实现了内存分配和地址映射

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
	struct binder_proc *proc = filp->private_data;

	if (proc->tsk != current->group_leader)
		return -EINVAL;

	binder_debug(BINDER_DEBUG_OPEN_CLOSE,
		     "%s: %d %lx-%lx (%ld K) vma %lx pagep %lx\n",
		     __func__, proc->pid, vma->vm_start, vma->vm_end,
		     (vma->vm_end - vma->vm_start) / SZ_1K, vma->vm_flags,
		     (unsigned long)pgprot_val(vma->vm_page_prot));

	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;
	}
	vm_flags_mod(vma, VM_DONTCOPY | VM_MIXEDMAP, VM_MAYWRITE);

	vma->vm_ops = &binder_vm_ops;
	vma->vm_private_data = proc;

	return binder_alloc_mmap_handler(&proc->alloc, vma);
}

=============================================================

int binder_alloc_mmap_handler(struct binder_alloc *alloc,
			      struct vm_area_struct *vma)
{
	struct binder_buffer *buffer;
	const char *failure_string;
	int ret, i;

	if (unlikely(vma->vm_mm != alloc->mm)) {
		ret = -EINVAL;
		failure_string = "invalid vma->vm_mm";
		goto err_invalid_mm;
	}

	mutex_lock(&binder_alloc_mmap_lock);
	if (alloc->buffer_size) {
		ret = -EBUSY;
		failure_string = "already mapped";
		goto err_already_mapped;
	}
	alloc->buffer_size = min_t(unsigned long, vma->vm_end - vma->vm_start,
				   SZ_4M);
	mutex_unlock(&binder_alloc_mmap_lock);

	alloc->buffer = vma->vm_start;

	alloc->pages = kcalloc(alloc->buffer_size / PAGE_SIZE,
			       sizeof(alloc->pages[0]),
			       GFP_KERNEL);
	if (alloc->pages == NULL) {
		ret = -ENOMEM;
		failure_string = "alloc page array";
		goto err_alloc_pages_failed;
	}

	for (i = 0; i < alloc->buffer_size / PAGE_SIZE; i++) {
		alloc->pages[i].alloc = alloc;
		INIT_LIST_HEAD(&alloc->pages[i].lru);
	}

	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
	if (!buffer) {
		ret = -ENOMEM;
		failure_string = "alloc buffer struct";
		goto err_alloc_buf_struct_failed;
	}

	buffer->user_data = alloc->buffer;
	list_add(&buffer->entry, &alloc->buffers);
	buffer->free = 1;
	binder_insert_free_buffer(alloc, buffer);
	alloc->free_async_space = alloc->buffer_size / 2;

	/* Signal binder_alloc is fully initialized */
	binder_alloc_set_vma(alloc, vma);

	return 0;

err_alloc_buf_struct_failed:
	kfree(alloc->pages);
	alloc->pages = NULL;
err_alloc_pages_failed:
	alloc->buffer = 0;
	mutex_lock(&binder_alloc_mmap_lock);
	alloc->buffer_size = 0;
err_already_mapped:
	mutex_unlock(&binder_alloc_mmap_lock);
err_invalid_mm:
	binder_alloc_debug(BINDER_DEBUG_USER_ERROR,
			   "%s: %d %lx-%lx %s failed %d\n", __func__,
			   alloc->pid, vma->vm_start, vma->vm_end,
			   failure_string, ret);
	return ret;
}

---参考 ## 理解Android Binder机制(1/3):驱动篇