Binder框架探究之binder驱动

  • 通过Binder框架图示,对于Binder的整体流程有了一个基本的认知后,接下来就可以深入每一个部分进行逐一的探究,首先先来探究:Binder的所谓一次拷贝究竟是什么?这个一次拷贝又是发生在何处?

1 一次拷贝的原理解析

1.1 内核空间和用户空间的数据交互

  • 如上篇 binder-框架认知3.1章节 所陈述的,在Linux中,用户进程和内核空间是隔离的,而内核空间和用户空间之间的相互访问只能通过系统调用(System Call)进行,对于用户空间和内核空间之间的数据交互,常用的是以下的几个函数:
    1. 用户空间向内核空间传递数据:
      • copy_from_user()
      • get_user()
    2. 用户空间向内核空间获取数据:
      • copy_to_user()
      • put_user()

1.2 Linux内存映射

  • 概念:内存映射可以调用函数mmap()实现,本质就是将一段物理内存空间(或者虚拟内存空间)映射到特定进程的虚拟内存空间上

  • 目的:实现映射关系后,进程就可以直接通过指针来读写这一段虚拟内存空间,进而实现对另一段映射的内存空间(可以是物理内存也可以是虚拟内存)的操作,而不必需要调用 read/write等系统函数

  • 优点:通过mmap内存映射,可以实现用户空间和内核空间的高效互动,两个空间各自的修改可以直接反映在被映射的内存空间区域,这样就可以减少拷贝次数

    在这里插入图片描述

1.3 传统的IPC通信原理

  • Linux传统进程IPC通信由于Linux进程隔离的原因,通常需要如下的步骤进行跨进程IPC通信(共享内存除外):

    • 对于消息的发送端进程:
      • 通常传统的IPC通信(Socket,管道,消息队列)首先将消息发送方将要发送的数据存放在内存缓存区中
      • 然后通过前面所说系统调用进入内核态。然后操作系统为Linux发送方进程在内核空间分配内存,开辟一块内核缓存区,调用 copy_from_user()函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中
    • 对于消息的接收端进程:
      • 首先在进行接收数据时在自己的用户空间开辟一块内存缓存区
      • 然后操作系统为Linux接收方进程在内核空间中调用 copy_to_user() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区。这样数据发送方进程和数据接收方进程就完成了一次数据传输,而这样就完成了一次进程间通信
  • 整个过程可以用以下模型展示:

    在这里插入图片描述

  • 这种方式有两个明显的缺点:

    1. 传统IPC通信模型传输效率比较低,拷贝次数过多需要两次(这个仅是对于Binder和匿名共享内存而言),第一次是从发送方用户空间拷贝到内核缓存区,第二次是从内核缓存区拷贝到接收方用户空间
    2. 接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用API接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间

1.4 Binder的通信原理

  • 如上面5.1.1.2所叙述的内存映射方法,它不仅可以将文件映射到用户虚拟空间上,从而减少数据的拷贝次数,用内存读写取代I/O读写,提高文件的读取效率;而且还可以将内核空间和用户空间映射到同一块物理空间上,这样从发送方用户空间拷贝到内核空间缓存区的数据,就相当于直接拷贝到接收方的用户空间中了,而Binder正是运用了这种方式,实现了一次拷贝:

    • 对于Binder服务端进程而言:

      • Binder服务端(Service)在启动之后,通过系统调用Binde驱动在内核空间创建一个数据接收缓存区(调用binder_oepn方法执行相关的操作)
      • 接着Binder服务端进程空间的内核接收到系统调用的指令,进而调用binder_mmap函数进行对应的处理。首先申请一块物理内存,然后建立Binder服务端(Service端)的用户空间和内核空间一块区域的映射关系(这样上述两块区域就映射在一起了)
    • 对于请求端(Client)进程而言:

      • Client向服务端发送通信发送请求,这个请求数据打包完毕之后通过系统调用先到驱动中,然后在驱动中通过copy_from_user()将数据从用户空间拷贝到内核空间的缓存区中(注意这块内核空间和Binder服务端的用户空间存在映射关系)
      • 由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间(只需要进行一定的偏移操作即可),这样便完成了一次进程间的通信,从而达到了省去一次拷贝的操作
    • 整个过程可以用以下模型展示:

      在这里插入图片描述

    • Binder相对于传统的IPC,有两个明显的优势:

      1. 减少了数据的拷贝次数,用内存读写取代 I/O 读写,提高了文件读取效率
      2. 作为Binder服务端开辟的数据接收区大小是固定的

2 Binder驱动源码解析

2.1 Binder数据封装

  • 在分析源码之前,需要先知晓Binder数据封装的基本模型

    在这里插入图片描述

    关于这个数据封装模型的得来,会在后续分析Binder框架中的Framework以及Native层时进行详细介绍,此处只需知晓有这样一个数据封装结构就可以了

2.2 Binder驱动基本函数

  • Binder驱动中,主要涉及的函数有:
    1. binder_init()
    2. binder_open()
    3. binder_mmap()
    4. binder_ioctl()
      接下来我们逐一进行函数讲解,需要说明的是,以下函数分析,是基于Android R进行分析的。
2.2.1 binder_init()
  • 文件路径:android/kernel/driver/android/binder.c

    device_initcall(binder_init);
    
    • 首先可以看到Binder设备的驱动入口是:device_initcall()方法,这就意味着当前binder驱动是不支持动态编译的,如果需要,可以将入口修改为:module_init()module_exit()
    static char *binder_devices_param = CONFIG_ANDROID_BINDER_DEVICES;	//在android-base.config中CONFIG_ANDROID_BINDER_DEVICES="binder,hwbinder,vndbinder"
    
    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;
    
    	ret = binder_alloc_shrinker_init();
    	if (ret)
    		return ret;
    
    	atomic_set(&binder_transaction_log.cur, ~0U);
    	atomic_set(&binder_transaction_log_failed.cur, ~0U);
    
    	binder_debugfs_dir_entry_root = debugfs_create_dir("binder", NULL);
    	if (binder_debugfs_dir_entry_root)
    		binder_debugfs_dir_entry_proc = debugfs_create_dir("proc",
    						 binder_debugfs_dir_entry_root);
    
    	if (binder_debugfs_dir_entry_root) {
    		debugfs_create_file("state",
    				    0444,
    				    binder_debugfs_dir_entry_root,
    				    NULL,
    				    &binder_state_fops);
    		debugfs_create_file("stats",
    				    0444,
    				    binder_debugfs_dir_entry_root,
    				    NULL,
    				    &binder_stats_fops);
    		debugfs_create_file("transactions",
    				    0444,
    				    binder_debugfs_dir_entry_root,
    				    NULL,
    				    &binder_transactions_fops);
    		debugfs_create_file("transaction_log",
    				    0444,
    				    binder_debugfs_dir_entry_root,
    				    &binder_transaction_log,
    				    &binder_transaction_log_fops);
    		debugfs_create_file("failed_transaction_log",
    				    0444,
    				    binder_debugfs_dir_entry_root,
    				    &binder_transaction_log_failed,
    				    &binder_transaction_log_fops);
    	}
    
    	if (!IS_ENABLED(CONFIG_ANDROID_BINDERFS) &&
    	    strcmp(binder_devices_param, "") != 0) {	//判断当前的CONFIG_ANDROID_BINDERFS 和 binder_devices_param 合法性
    		/*
    		* Copy the module_parameter string, because we don't want to
    		* tokenize it in-place.
    		 */
    		//拿到binder_devices_param中的内容,并记录在device_names中
    		device_names = kstrdup(binder_devices_param, GFP_KERNEL);
    		if (!device_names) {
    			ret = -ENOMEM;
    			goto err_alloc_device_names_failed;
    		}
    
    		device_tmp = device_names;
    		while ((device_name = strsep(&device_tmp, ","))) {
    			//开始初始化Binder设备
    			ret = init_binder_device(device_name);
    			if (ret)
    				goto err_init_binder_device_failed;
    		}
    	}
    
    	ret = init_binderfs();
    	if (ret)
    		goto err_init_binder_device_failed;
    
    	return ret;
    
    err_init_binder_device_failed:
    	hlist_for_each_entry_safe(device, tmp, &binder_devices, hlist) {
    		misc_deregister(&device->miscdev);
    		hlist_del(&device->hlist);
    		kfree(device);
    	}
    
    	kfree(device_names);
    
    err_alloc_device_names_failed:
    	debugfs_remove_recursive(binder_debugfs_dir_entry_root);
    
    	return ret;
    }
    
    • 可以看到真正进行binder设备的初始化是通过init_binder_device()进行的,接下来再看一下init_binder_device()函数
    //binder_fops结构体中声明了native层的函数和kernel层函数名的对应关系
    const struct file_operations binder_fops = {
    	.owner = THIS_MODULE,
    	.poll = binder_poll,
    	.unlocked_ioctl = binder_ioctl,
    	.compat_ioctl = binder_ioctl,
    	.mmap = binder_mmap,
    	.open = binder_open,
    	.flush = binder_flush,
    	.release = binder_release,
    };
    
    static int __init init_binder_device(const char *name)
    {
    	int ret;
    	struct binder_device *binder_device;
    
    	//给binder_device结构体分配内存
    	binder_device = kzalloc(sizeof(*binder_device), GFP_KERNEL);
    	if (!binder_device)
    		return -ENOMEM;
    
    	//给binder_device结构体进行赋值
    	//binder_fops结构体中就是对应native层函数和kernel层函数的对应关系
    	binder_device->miscdev.fops = &binder_fops;
    	binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
    	//name就是从binder_devices_param中拿到的字符串进行截取之后的字串
    	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);
    
    	//进行misc驱动的注册,之所以将其注册为misc设备,是因为misc设备没有硬件设备,注册上比较简单
    	ret = misc_register(&binder_device->miscdev);
    	if (ret < 0) {
    		kfree(binder_device);
    		return ret;
    	}
    
    	//最后将初始化完成的misc驱动的binder_device->hlist节点,以头插的方式插入binder_devices链表
    	//binder_devices链表是一个全局的静态变量,而这就意味着之后,可以通过binder_devices这个全局变量拿到所有注册的Binder驱动
    	hlist_add_head(&binder_device->hlist, &binder_devices);
    
    	return ret;
    }
    
  • 总结binder_init()函数的工作:

    1. 创建空间用来存储字符串binder_devices_param(“binder,hwbinder,vndbinder”)
    2. init_binder_device()方法中进行设备的初始化,包括建立Native层和Kernel层函数的对应关系
    3. init_binder_device()方法中将注册好的Binder misc设备放入 binder_devices 链表存储
2.2.2 binder_open()
  • 文件路径:android/kernel/driver/android/binder.c

    static int binder_open(struct inode *nodp, struct file *filp)
    {
    	struct binder_proc *proc;
    	struct binder_device *binder_dev;
    	struct binderfs_info *info;
    	struct dentry *binder_binderfs_dir_entry_proc = NULL;
    
    	binder_debug(BINDER_DEBUG_OPEN_CLOSE, "%s: %d:%d\n", __func__,
    		     current->group_leader->pid, current->pid);
    
    	//给proc结构体分配空间,这个proc结构体是用来存放当前的进程信息的
    	//可以说proc代表的就是一个完整的进程
    	proc = kzalloc(sizeof(*proc), GFP_KERNEL);
    	if (proc == NULL)
    		return -ENOMEM;
    	spin_lock_init(&proc->inner_lock);
    	spin_lock_init(&proc->outer_lock);
    	get_task_struct(current->group_leader);
    	//proc->tsk就是current本身
    	proc->tsk = current->group_leader;
    	mutex_init(&proc->files_lock);
    	INIT_LIST_HEAD(&proc->todo);
    	//如果当前进程支持优先级,则记录下当前进程的优先级,如果当前进程不支持优先级,则存NORMAL
    	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);
    	//将初始化完成的proc结构体存放在filp->private_data中,这样后续就可以通过filp->private_data拿到proc对象了
    	filp->private_data = proc;
    
    	mutex_lock(&binder_procs_lock);
    	//将结构体proc->proc_node节点,以头插的形式存放到binder_procs链表中
    	//和binder_devices一样,binder_procs也是一个全局的静态变量
    	hlist_add_head(&proc->proc_node, &binder_procs);
    	mutex_unlock(&binder_procs_lock);
    
    	if (binder_debugfs_dir_entry_proc) {
    		char strbuf[11];
    
    		snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
    		/*
    		 * proc debug entries are shared between contexts, so
    		 * this will fail if the process tries to open the driver
    		 * again with a different context. The priting code will
    		 * anyway print all contexts that a given PID has, 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) {
    		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. If the file has already been created for a
    		 * process, the following binderfs_create_file() call will
    		 * fail with error code EEXIST if another context of the same
    		 * process invoked binder_open(). 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);
    			if (error != -EEXIST) {
    				pr_warn("Unable to create file %s in binderfs (error %d)\n",
    					strbuf, error);
    			}
    		}
    	}
    
    	return 0;
    }
    
  • 总结一下binder_open()函数的工作

    1. 创建proc对象,并为其分配好空间
    2. 将当前进程的信息,通过proc存储起来
    3. proc->proc_node作为头节点,插入binder_procs链表中
    4. 将这个构建好的proc,赋值给filp->private_data,这样下次就可以通过对应filp->private_data直接找到对应的proc了(filp是file类型)
2.2.3 binder_mmap()
  • 文件路径:android/kernel/driver/android/binder.c

    static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
    {
    	int ret;
    	//先拿到对应进程的proc结构体信息
    	struct binder_proc *proc = filp->private_data;
    	const char *failure_string;
    
    	//判断proc保存的进程是否就是当前进程
    	if (proc->tsk != current->group_leader)
    		return -EINVAL;
    
    	//vma是用户空间进程的虚拟内存大小
    	//这里可以看到在内核层规定了,binder的最大空间就是4M
    	if ((vma->vm_end - vma->vm_start) > SZ_4M)
    		vma->vm_end = vma->vm_start + SZ_4M;
    
    	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) {
    		ret = -EPERM;
    		failure_string = "bad vm_flags";
    		goto err_bad_arg;
    	}
    	//对vma也就是用户空间的虚拟内存的vm_flags进行赋值
    	vma->vm_flags |= VM_DONTCOPY | VM_MIXEDMAP;
    	vma->vm_flags &= ~VM_MAYWRITE;
    
    	//给vm_ops结构体进行赋值
    	//vm_ops结构体中就是对应native层函数和kernel层函数的对应关系
    	vma->vm_ops = &binder_vm_ops;
    	//存放代表对应进程的proc结构体
    	vma->vm_private_data = proc;
    
    	//开始进行mmap操作
    	ret = binder_alloc_mmap_handler(&proc->alloc, vma);
    	if (ret)
    		return ret;
    	mutex_lock(&proc->files_lock);
    	proc->files = get_files_struct(current);
    	mutex_unlock(&proc->files_lock);
    	return 0;
    
    err_bad_arg:
    	pr_err("%s: %d %lx-%lx %s failed %d\n", __func__,
    	       proc->pid, vma->vm_start, vma->vm_end, failure_string, ret);
    	return ret;
    }
    
    • 通过binder_alloc_mmap_handler()进行内存映射
  • 文件路径:android/kernel/driver/android/binder_alloc.c

    /**
     * binder_alloc_mmap_handler() - map virtual address space for proc
     * @alloc:	alloc structure for this proc
     * @vma:	vma passed to mmap()
     *
     * Called by binder_mmap() to initialize the space specified in
     * vma for allocating binder buffers
     *
     * Return:
     *      0 = success
     *      -EBUSY = address space already mapped
     *      -ENOMEM = failed to map memory to given address space
     */
    int binder_alloc_mmap_handler(struct binder_alloc *alloc,
    			      struct vm_area_struct *vma)
    {
    	int ret;
    	const char *failure_string;
        //声明一个binder_buffer对象,用来保存诸如红黑树、binder_transaction、free标志等数据
    	struct binder_buffer *buffer;
    
    	mutex_lock(&binder_alloc_mmap_lock);
    	//判断对应进程是否已经做过内存映射了
    	if (alloc->buffer) {
    		ret = -EBUSY;
    		failure_string = "already mapped";
    		goto err_already_mapped;
    	}
    
    	//alloc->buffer保存下用户空间的虚拟内存起始地址
    	alloc->buffer = (void __user *)vma->vm_start;
    	mutex_unlock(&binder_alloc_mmap_lock);
    
    	//分配内核虚拟空间大小的物理页指针数组,数组的大小等效于用户虚拟内存空间占据的页数量
    	alloc->pages = kcalloc((vma->vm_end - vma->vm_start) / PAGE_SIZE,
    			       sizeof(alloc->pages[0]),
    			       GFP_KERNEL);
    	if (alloc->pages == NULL) {
    		ret = -ENOMEM;
    		failure_string = "alloc page array";
    		goto err_alloc_pages_failed;
    	}
    	//在内核空间结构体中记录下用户空间虚拟内存地址的大小
    	alloc->buffer_size = vma->vm_end - vma->vm_start;
    
    	//给buffer分配空间
    	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
    	if (!buffer) {
    		ret = -ENOMEM;
    		failure_string = "alloc buffer struct";
    		goto err_alloc_buf_struct_failed;
    	}
    
    	//这个alloc->buffer其实就是用户空间的虚拟内存其实地址
    	buffer->user_data = alloc->buffer;
    	//将buffer->entry放入alloc->buffers链表中
    	list_add(&buffer->entry, &alloc->buffers);
    	buffer->free = 1;
    	//根据该binder_buffer大小找到对应位置插入binder_alloc的free_buffers红黑树,用于后续分配
    	binder_insert_free_buffer(alloc, buffer);
    	//异步空间为总空间的一半
    	alloc->free_async_space = alloc->buffer_size / 2;
    	//把vma(用户空间的虚拟内存)记录到alloc(内核空间的虚拟空间)中
    	binder_alloc_set_vma(alloc, vma);
    	mmgrab(alloc->vma_vm_mm);
    
    	return 0;
    
    err_alloc_buf_struct_failed:
    	kfree(alloc->pages);
    	alloc->pages = NULL;
    err_alloc_pages_failed:
    	mutex_lock(&binder_alloc_mmap_lock);
    	alloc->buffer = NULL;
    err_already_mapped:
    	mutex_unlock(&binder_alloc_mmap_lock);
    	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;
    }
    
  • 总结一下binder_mmap()函数的工作:

    1. proc->alloc 里面存放着对应进程相关的binder数据 ; struct vm_area_struct *vma 代表的是用户空间的虚拟内存空间
    2. binder_insert_free_buffer(alloc, buffer) 根据buffer的大小找到对应位置插入allocfree_buffers红黑树,用于后续分配
    3. binder_alloc_set_vma(alloc, vma)vma记录到alloc中;alloc:对应进程的binder数据存放结构;vma:用户虚拟内存空间;
  • 在这边需要说明一下,如果有看过Android之前版本(Android P之前)的binder_mmap()函数,会发现在之前的版本中,在binder_mmap()中会进行内核虚拟内存空间的创建,并将用户虚拟内存和内核虚拟内存映射到同一块物理内存上的动作,但是在Android R中在binder_mmap()中取消了内核空间虚拟内存的创建,并且在binder_mmap()中也不做用户虚拟内存空间和物理内存的映射了,这个映射的动作被下放到通过binder_ioctl()方法进行数据传递的过程中去完成。关于这个做法的变化,原因和详细的修改可见文章:Binder内存拷贝的本质和变迁_unbroken-CSDN博客_binder 内存拷贝

2.2.4 binder_ioctl()
  • 文件路径:android/kernel/driver/android/binder.c

    static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
    {
    	int ret;
    	//根据传入的filp,获取到对应的进程数据proc
    	struct binder_proc *proc = filp->private_data;
    	//这个是binder线程,真正完成binder通信任务的角色
    	struct binder_thread *thread;
    	unsigned int size = _IOC_SIZE(cmd);
    	void __user *ubuf = (void __user *)arg;
    
    	/*pr_info("binder_ioctl: %d:%d %x %lx\n",
    			proc->pid, current->pid, cmd, arg);*/
    
    	binder_selftest_alloc(&proc->alloc);
    
    	trace_binder_ioctl(cmd, arg);
    
    	ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
    	if (ret)
    		goto err_unlocked;
    
    	//从对应进程中拿到一个binder线程
    	thread = binder_get_thread(proc);
    	if (thread == NULL) {
    		ret = -ENOMEM;
    		goto err;
    	}
    
    	//根据传入binder_ioctl中的cmd,来执行相应的操作
    	switch (cmd) {
    	case BINDER_WRITE_READ:
    		ret = binder_ioctl_write_read(filp, cmd, arg, thread);
    		if (ret)
    			goto err;
    		break;
    	...
    	default:
    		ret = -EINVAL;
    		goto err;
    	}
    	ret = 0;
    err:
    	if (thread)
    		thread->looper_need_return = false;
    	wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
    	if (ret && ret != -ERESTARTSYS)
    		pr_info("%d:%d ioctl %x %lx returned %d\n", proc->pid, current->pid, cmd, arg, ret);
    err_unlocked:
    	trace_binder_ioctl_done(ret);
    	return ret;
    }
    
  • 在这里我们只分析读写数据的场景,即cmd == binder_ioctl_write_read

static int binder_ioctl_write_read(struct file *filp,
   			unsigned int cmd, unsigned long arg,
   			struct binder_thread *thread)
{
   int ret = 0;
   struct binder_proc *proc = filp->private_data;
   unsigned int size = _IOC_SIZE(cmd);
   void __user *ubuf = (void __user *)arg;
   struct binder_write_read bwr;

   if (size != sizeof(struct binder_write_read)) {
   	ret = -EINVAL;
   	goto out;
   }
   //这里就出现了一次拷贝,但是通过binder_write_read数据结构,可以看到,这里并不是在拷贝真正的数据,只是数据的一层封装
   if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {     
   	ret = -EFAULT;
   	goto out;
   }
   binder_debug(BINDER_DEBUG_READ_WRITE,
   	     "%d:%d write %lld at %016llx, read %lld at %016llx\n",
   	     proc->pid, thread->pid,
   	     (u64)bwr.write_size, (u64)bwr.write_buffer,
   	     (u64)bwr.read_size, (u64)bwr.read_buffer);

   //如果是写数据,那么调用binder_thread_write
   if (bwr.write_size > 0) {
   	ret = binder_thread_write(proc, thread,
   				  bwr.write_buffer,
   				  bwr.write_size,
   				  &bwr.write_consumed);
   	trace_binder_write_done(ret);
   	if (ret < 0) {
   		bwr.read_consumed = 0;
   		if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
   			ret = -EFAULT;
   		goto out;
   	}
   }
   //如果是读数据,那么调用binder_thread_read
   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);
   	trace_binder_read_done(ret);
   	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) {
   		if (copy_to_user(ubuf, &bwr, sizeof(bwr)))
   			ret = -EFAULT;
   		goto out;
   	}
   }
   binder_debug(BINDER_DEBUG_READ_WRITE,
   	     "%d:%d wrote %lld of %lld, read return %lld of %lld\n",
   	     proc->pid, thread->pid,
   	     (u64)bwr.write_consumed, (u64)bwr.write_size,
   	     (u64)bwr.read_consumed, (u64)bwr.read_size);
   //这里又出现了一次拷贝,并且是从内核空间拷贝到用户空间,这里拷贝的也是BWR,即数据的封装,并非拷贝真正的数据
   if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {
   	ret = -EFAULT;
   	goto out;
   }
out:
   return ret;
}

struct binder_transaction_data {
   /* The first two are only used for bcTRANSACTION and brTRANSACTION,
    * identifying the target and contents of the transaction.
    */
   union {
   	/* target descriptor of command transaction */
   	__u32	handle;
   	/* target descriptor of return transaction */
   	binder_uintptr_t ptr;
   } target;
   binder_uintptr_t	cookie;	/* target object cookie */
   __u32		code;		/* transaction command */

   /* General information about the transaction. */
   __u32	        flags;
   pid_t		sender_pid;
   uid_t		sender_euid;
   binder_size_t	data_size;	/* number of bytes of data */
   binder_size_t	offsets_size;	/* number of bytes of offsets */

   /* If this transaction is inline, the data immediately
    * follows here; otherwise, it ends with a pointer to
    * the data buffer.
    */
   union {
   	struct {
   		/* transaction data */
   		binder_uintptr_t	buffer;
   		/* offsets from buffer to flat_binder_object structs */
   		binder_uintptr_t	offsets;
   	} ptr;
   	__u8	buf[8];
   } data;
};

//以写数据函数进行举例分析,这里只捡重点进行注释解析
static int binder_thread_write(struct binder_proc *proc,
   		struct binder_thread *thread,
   		binder_uintptr_t binder_buffer, size_t size,
   		binder_size_t *consumed)
{
	....
   while (ptr < end && thread->return_error.cmd == BR_OK) {
    	....
       case BC_TRANSACTION:	//一般cmd为BC_TRANSACTION
       case BC_REPLY: {
       	struct binder_transaction_data tr;
           //这里又出现了一次拷贝,但是查看binder_transaction_data的数据结构,发现此时仍旧是对数据的一层封装
           if (copy_from_user(&tr, ptr, sizeof(tr)))	
               return -EFAULT;
           ptr += sizeof(tr);
           //Binder强调的一次拷贝,拷贝的是具体的数据,其实是在这个函数中实现的
           binder_transaction(proc, thread, &tr,
                              cmd == BC_REPLY, 0);
           break;
       ......
       }
       ......
   }
   ......
}


struct binder_transaction {
   int debug_id;
   struct binder_work work;
   struct binder_thread *from;
   struct binder_transaction *from_parent;
   struct binder_proc *to_proc;
   struct binder_thread *to_thread;
   struct binder_transaction *to_parent;
   unsigned need_reply:1;
   /* unsigned is_dead:1; */	/* not used at the moment */

   struct binder_buffer *buffer;
   unsigned int	code;
   unsigned int	flags;
   struct binder_priority	priority;
   struct binder_priority	saved_priority;
   bool    set_priority_called;
   kuid_t	sender_euid;
   binder_uintptr_t security_ctx;
   /**
    * @lock:  protects @from, @to_proc, and @to_thread
    *
    * @from, @to_proc, and @to_thread can be set to NULL
    * during thread teardown
    */
   spinlock_t lock;
};

static void binder_transaction(struct binder_proc *proc,
   		       struct binder_thread *thread,
   		       struct binder_transaction_data *tr, int reply,
   		       binder_size_t extra_buffers_size)
{
   struct binder_transaction *t;
   ...

   t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size,
   	tr->offsets_size, extra_buffers_size,
   	!reply && (t->flags & TF_ONE_WAY));
   if (IS_ERR(t->buffer)) {
   	/*
   	 * -ESRCH indicates VMA cleared. The target is dying.
   	 */
   	return_error_param = PTR_ERR(t->buffer);
   	return_error = return_error_param == -ESRCH ?
   		BR_DEAD_REPLY : BR_FAILED_REPLY;
   	return_error_line = __LINE__;
   	t->buffer = NULL;
   	goto err_binder_alloc_buf_failed;
   }
   ...
   //此处是真正在进行数据的拷贝,binder所强调的一次拷贝也就是此处
   if (binder_alloc_copy_user_to_buffer(
   			&target_proc->alloc,
   			t->buffer, 0,
   			(const void __user *)
   				(uintptr_t)tr->data.ptr.buffer,
   			tr->data_size)) {
   	binder_user_error("%d:%d got transaction with invalid data ptr\n",
   			proc->pid, thread->pid);
   	return_error = BR_FAILED_REPLY;
   	return_error_param = -EFAULT;
   	return_error_line = __LINE__;
   	goto err_copy_data_failed;
   }
   ...
}
  • 总结一下binder_ioctl()的工作:

    • 首先,我们针对ioctl的数据进行解析示例:

      在这里插入图片描述

      在这层层的数据封装中,只有binder_transcation_data结构中的data才存放着真正传输的数据,所以在binder中,其强调的一次拷贝,指的是拷贝data数据,至于之前的多次拷贝只是拷贝一些封装的数据,并非真正的传输数据的拷贝

    • 数据读写的流程是:

      • binder_ioctl() --> binder_ioctl_write_read() --> binder_thread_write()/binder_thread_read() --> binder_transaction()

      • 使用图示展示:

        在这里插入图片描述

3 优秀文章

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值