Binder 深入理解 第四篇

29 阅读10分钟

Binder 驱动篇2

一、概述

Binder驱动是Android专用的,但底层的驱动架构与Linux驱动一样。binder驱动在以misc设备进行注册,作为虚拟字符设备,没有直接操作硬件,只是对设备内存的处理,主要是驱动设备的初始化(binder_init),打开(binder_open),映射(binder_mmap),数据操作(binder_ioctl)。

  1. 通过init(),创建/dev/binder设备节点
  2. 通过open(),获取Binder Driver的文件描述符
  3. 通过mmap(),在内核分配一块内存,用于存放数据
  4. 通过ioctl(),将IPC数据作为参数传递给Binder Driver

用户态的程序调用Kernel层驱动是需要陷入内核态,进行系统调用(syscall),比如打开Binder驱动方法的调用链是:open()->__open()->binder_open()。open()为用户空间的方法, _open()是系统调用中相应的处理方法,通过查找,对应调用到内核binder驱动的binder_open方法。

image.png

Client进程通过RPC(Remote Procedure Call Protocol)与Server通信,可以简单的分为三层,驱动层、IPC层、业务层。demo()是client和server共同协商好的统一方法,RPC数据、code、handle、协议这四项组成了IPC的层的数据,通过IPC层进行数据传输,而真正在Client和Server两端建立通信的基础设施是Binder Driver。

image.png

例如:当AMS的client向ServiceManger注册服务的过程中,IPC层的数据组成为:handle=0,RPC数据为AMS,code为ADD_SERVICE_TRANSACTION,binder协议为BC_TRANSACTION。

二、Binder核心方法

2.2 binder_open

[->android/binder.c]

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);
    // 将当前线程的task保存到binder进程的tsk
	proc->tsk = current->group_leader;
	proc->cred = get_cred(filp->f_cred);
    // 初始化todo队列
	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);
	}
    // binder_dev初始化
	refcount_inc(&binder_dev->ref);
    // 初始化context
	proc->context = &binder_dev->context;
    // binder_alloc初始化
	binder_alloc_init(&proc->alloc);
    // BINDER_PROC对象创建数加1
	binder_stats_created(BINDER_STAT_PROC);
	proc->pid = current->group_leader->pid;
	INIT_LIST_HEAD(&proc->delivered_death);
	INIT_LIST_HEAD(&proc->waiting_threads);
    // file文件指针的private_data变量指向binder_proc数据
	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;
}

我们有必要先了解一下在binder中非常重要的结构体binder_proc,它是用来描述进程上下文信息以及管理IPC的一个结构体

struct binder_proc {
    // hash链表中的一个节点
	struct hlist_node proc_node;
    // 处理用户请求的线程组成的红黑树
	struct rb_root threads;
    // binder实体组成的红黑树
	struct rb_root nodes;
    // binder引用组成的红黑树,以handle来排序
	struct rb_root refs_by_desc;
    // binder引用组成的红黑树,以它对应的binder实体的地址来排序
	struct rb_root refs_by_node;
	struct list_head waiting_threads;
    // 进程id
	int pid;
    // 进程描述符
	struct task_struct *tsk;
	const struct cred *cred;
	struct hlist_node deferred_work_node;
	int deferred_work;
	int outstanding_txns;
	bool is_dead;
	bool is_frozen;
	bool sync_recv;
	bool async_recv;
	wait_queue_head_t freeze_wait;
    // 待处理事件队列
	struct list_head todo;
	struct binder_stats stats;
	struct list_head delivered_death;
	int max_threads;
	int requested_threads;
	int requested_threads_started;
	int tmp_ref;
	struct binder_priority default_priority;
	struct dentry *debugfs_entry;
    // 用来记录mmap分配的用户虚拟地址空间和内核虚拟地址空间等信息
	struct binder_alloc alloc;
	struct binder_context *context;
	spinlock_t inner_lock;
	spinlock_t outer_lock;
	struct dentry *binderfs_entry;
	bool oneway_spam_detection_enabled;
};

创建binder_proc对象,并把当前进程等信息保存到binder_proc对象,该对象管理IPC所需要的各种信息并拥有其他结构体的根结构体;再把binder_proc对象保存到文件指针filp中,并且把binder_proc加入到全局链表binder_procs。

Binder驱动中 HLIST_HEAD创建全局的哈希链表binder_procs,用于保存所有的binder_proc队列,每次新创建的binder_proc都会加入到binder_procs链表中。

这里面有一些关于Linux的知识需要解释一下

spinlock

spinlock是内核中提供的一种自旋锁机制。在Linux内核实现中,常常会碰到共享数据被中断上下文和进程上下文访问的场景,如果只有进程上下文的话,我们可以使用互斥锁或者信号量解决,将未获得锁的进程置为睡眠状态等待,但由于中断上下文不是一个进程,它不存在task_struct,所以不可被调度,当然也就不可睡眠,这时候就可以通过spinlock自旋锁的忙等待机制来达成睡眠同样的效果

current

Linux内核中,定义了一个叫current的宏,它被定义在asm/current.h

static inline struct task_struct *get_current(void)
{
	return(current_thread_info()->task);
}

#define	current	get_current()

它返回一个task_struct指针,指向执行当前这段内核代码的进程

container_of

container_of也是Linux中定义的一个宏,它的作用是根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针

#define offsetof(TYPE, MEMBER)	((size_t)&((TYPE *)0)->MEMBER)

#define container_of(ptr, type, member) ({              \         
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \         
    (type *)( (char *)__mptr - offsetof(type,member) );})

fd&filp

filp->private_data保存了binder_proc结构体,当进程调用open系统函数时,内核会返回一个文件描述符fd,这个fd指向文件指针filp,在后续调用mmapioctl等函数与binder驱动交互时,会传入这个fd,内核就会以这个fd指向文件指针filp作为参数调用binder_mmapbinder_ioctl等函数,这样这些函数就可以通过filp->private_data取出binder_proc结构体

2.3 binder_mmap

vm_area_struct

在分析mmap前,我们需要先了解一下vm_area_struct这个结构体,它被定义在include/linux/mm_types.h

struct vm_area_struct {
    //当前vma的首地址
    unsigned long vm_start;
    //当前vma的末地址后第一个字节的地址
    unsigned long vm_end;
    
    //链表
    struct vm_area_struct *vm_next, *vm_prev;
    //红黑树中对应节点
    struct rb_node vm_rb;

    //当前vma前面还有多少空闲空间
    unsigned long rb_subtree_gap;

    //当前vma所属的内存地址空间
    struct mm_struct *vm_mm;
    //访问权限
    pgprot_t vm_page_prot;
    //vma标识集,定义在 include/linux/mm.h 中
    unsigned long vm_flags;

    union {
        struct {
            struct rb_node rb;
            unsigned long rb_subtree_last;
        } shared;
        const char __user *anon_name;
    };

    struct list_head anon_vma_chain;
    struct anon_vma *anon_vma;

    //当前vma操作函数集指针
    const struct vm_operations_struct *vm_ops;

    //当前vma起始地址在vm_file中的文件偏移,单位为物理页面PAGE_SIZE
    unsigned long vm_pgoff;
    //被映射的文件(如果使用文件映射)
    struct file * vm_file;
    void * vm_private_data;

#ifndef CONFIG_MMU
    struct vm_region *vm_region;	/* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
    struct mempolicy *vm_policy;	/* NUMA policy for the VMA */
#endif
    struct vm_userfaultfd_ctx vm_userfaultfd_ctx;
};

vm_area_struct结构体描述了一段虚拟内存空间,通常,进程所使用到的虚拟内存空间不连续,且各部分虚存空间的访问属性也可能不同,所以一个进程的虚拟内存空间需要多个vm_area_struct结构来描述(后面简称vma

每个进程都有一个对应的task_struct结构描述,这个task_struct结构中有一个mm_struct结构用于描述进程的内存空间,mm_struct结构中有两个域成员变量分别指向了vma链表头和红黑树根

vma所描述的虚拟内存空间范围由vm_startvm_end表示,vm_start代表当前vma的首地址,vm_end代表当前vma的末地址后第一个字节的地址,即虚拟内存空间范围为[vm_start, vm_end)

vm_operations_struct和上文中的file_operations类似,用来定义虚拟内存的操作函数

[->android/binder.c]

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));
    // 检查用户空间是否可写(FORBIDDEN_MMAP_FLAGS == 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;
	}
    // VM_DONTCOPY表示此vma不可被fork所复制
	vm_flags_mod(vma, VM_DONTCOPY | VM_MIXEDMAP, VM_MAYWRITE);
    // 设置此vma操作函数集
	vma->vm_ops = &binder_vm_ops;
    // 指向binder_proc
	vma->vm_private_data = proc;

    // 处理进程虚拟内存空间与内核虚拟地址空间的映射关系
	return binder_alloc_mmap_handler(&proc->alloc, vma);
}
  • 首先从filp中获取对应的binder_proc信息

  • 将它的进程task_struct和执行当前这段内核代码的进程task_struct对比校验

  • 检查用户空间是否可写(binder驱动为进程分配的缓冲区在用户空间中只可以读,不可以写)

  • 设置vm_flags,令vma不可写,不可复制

  • 设置vma的操作函数集

  • vm_area_struct中的成员变量vm_private_data指向binder_proc,使得vma设置的操作函数中可以拿到binder_proc

  • 处理进程虚拟内存空间与内核虚拟地址空间的映射关系

2.3.1 binder_alloc_mmap_handler

binder_alloc_mmap_handler将进程虚拟内存空间与内核虚拟地址空间做映射,它被实现在drivers/android/binder_alloc.c

这里先介绍一下vm_struct,之前我们已经了解了vm_area_struct表示用户进程中的虚拟地址空间,而相对应的,vm_struct则表示内核中的虚拟地址空间

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;
    // 分配物理页的指针数组,数组大小为vma的等效page个数
	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分配物理内存
	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;
}