进程打开了/dev/binder设备以后,还须要使用mmap方法将这个设备文件映射到进程的内存之中,而后才能使用Binder进程间通讯。(关于mmap是一种实现ipc的重要机制,http://www.cnblogs.com/huxiao-tee/p/4660352.html )html
/dev/binder是一个虚拟设备,咱们映射这个东西主要是为了为进程分配内核缓冲区,以便传输进程间通讯数据。咱们在初始化驱动程序的时候知道,当调用mmap映射/dev/binder时,实际上会调用的方法是binder_mmap数组
static int binder_mmap(struct file *filp, struct vm_area_struct *vma) { int ret; struct vm_struct *area; struct binder_proc *proc = filp->private_data; const char *failure_string; struct binder_buffer *buffer; if (proc->tsk != current) return -EINVAL; if ((vma->vm_end - vma->vm_start) > SZ_4M) vma->vm_end = vma->vm_start + SZ_4M; binder_debug(BINDER_DEBUG_OPEN_CLOSE, "binder_mmap: %d %lx-%lx (%ld K) vma %lx pagep %lx\n", 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_MAYWRITE; mutex_lock(&binder_mmap_lock); if (proc->buffer) { ret = -EBUSY; failure_string = "already mapped"; goto err_already_mapped; } ..... }
PS : 内存映射信息放在vma参数中,注意,这里的vma的数据类型是struct vm_area_struct,它表示的是一块连续的虚拟地址空间区域,在函数变量声明的地方,咱们还看到有一个相似的结构体struct vm_struct,这个数据结构也是表示一块连续的虚拟地址空间区域,那么,这二者的区别是什么呢?在Linux中,struct vm_area_struct表示的虚拟地址是给用户空间使用的,而struct vm_struct表示的虚拟地址是给内核使用的,它们对应的物理页面均可以是不连续的。struct vm_area_struct表示的地址空间范围是0~3G,而struct vm_struct表示的地址空间范围是(3G + 896M + 8M) ~ 4G。struct vm_struct表示的地址空间范围为何不是3G~4G呢?原来,3G ~ (3G + 896M)范围的地址是用来映射连续的物理页面的,这个范围的虚拟地址和对应的实际物理地址有着简单的对应关系,即对应0~896M的物理地址空间,而(3G + 896M) ~ (3G + 896M + 8M)是安全保护区域(例如,全部指向这8M地址空间的指针都是非法的),所以struct vm_struct使用(3G + 896M + 8M) ~ 4G地址空间来映射非连续的物理页面。安全
这一段代码至关于都是准备工做, proc的赋值咱们在上一篇有过介绍,filp指向文件结构体,其中的private_data是在调用binder_open的时候binder驱动程序赋值的。数据结构
上面说了vma是用户空间的虚拟地址空间,vm_end和vm_start制定了地址区域,而后判断是否大于4m,若是大于则截取。说明Binder驱动程序最多分配4M的虚拟地址空间用于传输进程间通讯数据。app
binder要求用户空间的虚拟地址空间不可写,不可拷贝,经过vm_flags来设置。而后检测一下binder_proc是否已经分配了内核空间,若是已经分配了直接返回。异步
到此准备工做算是结束了,接下去是真正的分配空间操做async
static int binder_mmap(struct file *filp, struct vm_area_struct *vma) { ........ //在内核中分配一段指定大小的虚拟地址空间 area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP); if (area == NULL) { ret = -ENOMEM; failure_string = "get_vm_area"; goto err_get_vm_area_failed; } //内核虚拟地址空间起始地址赋值给buffer proc->buffer = area->addr; //用户空间地址和内核空间地址的offset(用于快速计算地址) proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer; mutex_unlock(&binder_mmap_lock); ............ //为申请物理空间作准备,建立一个数组,用来存储物理页面,保存在proc->pages中。(只建立了数组,还没真正分配物理内存) //每一页虚拟地址空间都对应一个物理页面 proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL); if (proc->pages == NULL) { ret = -ENOMEM; failure_string = "alloc page array"; goto err_alloc_pages_failed; } //初始化内核虚拟地址空间大小 proc->buffer_size = vma->vm_end - vma->vm_start; //设置用户空间虚拟地址打开和关闭的函数 vma->vm_ops = &binder_vm_ops; vma->vm_private_data = proc; //为内核的虚拟地址空间请求物理内存,并进行分配 if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) { ret = -ENOMEM; failure_string = "alloc small buf"; goto err_alloc_small_buf_failed; } //使用binder_buffer 结构体来描述内核的虚拟地址空间 ,而且将其添加到proc->buffers链表中 //步骤a buffer = proc->buffer; INIT_LIST_HEAD(&proc->buffers); list_add(&buffer->entry, &proc->buffers); //虚拟内存空间为空,因此添加到free_buffers中。 buffer->free = 1; binder_insert_free_buffer(proc, buffer); //将最大可用于异步事物的内核虚拟地址空间设置为总的一半 proc->free_async_space = proc->buffer_size / 2; barrier(); proc->files = get_files_struct(current); proc->vma = vma; proc->vma_vm_mm = vma->vm_mm; /*pr_info("binder_mmap: %d %lx-%lx maps %p\n", proc->pid, vma->vm_start, vma->vm_end, proc->buffer);*/ return 0; ............ }
PS: 步骤a 首先咱们知道proc->buffer 的值是 area->addr,也就是内核的虚拟地址空间的起始位置的地址值,将其保存到buffer指针中。&buffer->entry 表明的也是一个在内核虚拟地址空间地址,将其添加到buffers列表中。当前buffers列表中只有一个数据。函数
差很少就是上面这个意思……ui
binder_update_page_range的做用是真正的为内核的虚拟地址空间分配物理内存,代码以下spa
// 参数是 proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma static int binder_update_page_range(struct binder_proc *proc, int allocate, void *start, void *end, struct vm_area_struct *vma) { void *page_addr; unsigned long user_page_addr; struct vm_struct tmp_area; struct page **page; struct mm_struct *mm; .... if (allocate == 0) goto free_range; .... //分配物理内存, for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) { int ret; //依次获取pages数组中的项 page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE]; BUG_ON(*page); //请求物理页 *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO); if (*page == NULL) { pr_err("%d: binder_alloc_buf failed for page at %p\n", proc->pid, page_addr); goto err_alloc_page_failed; } //将物理页映射到内核虚拟地址空间 tmp_area.addr = page_addr; tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */; ret = map_vm_area(&tmp_area, PAGE_KERNEL, page); if (ret) { pr_err("%d: binder_alloc_buf failed to map page at %p in kernel\n", proc->pid, page_addr); goto err_map_kernel_failed; } //获取内核虚拟地址空间对应的用户虚拟地址空间(经过加上user_buffer_offset) user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset; //将物理页映射到用户虚拟地址空间 ret = vm_insert_page(vma, user_page_addr, page[0]); if (ret) { pr_err("%d: binder_alloc_buf failed to map page at %lx in userspace\n", proc->pid, user_page_addr); goto err_vm_insert_page_failed; } /* vm_insert_page does not seem to increment the refcount */ } if (mm) { up_write(&mm->mmap_sem); mmput(mm); } return 0; //回收物理内存 free_range: for (page_addr = end - PAGE_SIZE; page_addr >= start; page_addr -= PAGE_SIZE) { page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE]; if (vma) zap_page_range(vma, (uintptr_t)page_addr + proc->user_buffer_offset, PAGE_SIZE, NULL); err_vm_insert_page_failed: //解除物理映射 unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE); err_map_kernel_failed: //释放物理页 __free_page(*page); *page = NULL; err_alloc_page_failed: ; } err_no_vma: if (mm) { up_write(&mm->mmap_sem); mmput(mm); } return -ENOMEM; }
这个方法在分配物理内存页和回收物理内存页的时候都会用到,使用第二个参数来控制,0表示释放物理内存,1表示分配物理内存。须要注意的是咱们传入的end,咱们看到,这里咱们只分配了一个page的物理内存!
因此binder_mmap的主要做用就是为进程间通讯的进程分配内核虚拟地址空间,以及为内核虚拟地址空间,用户虚拟地址空间申请物理内存,并创建映射关系
上面表示进程的虚拟内存,下面表示物理内存,咱们看到咱们为用户虚拟地址空间和内核虚拟地址空间映射到了同一块物理内存上,这样在binder驱动修改内核地址空间的时候,用户地址空间对应的数据也作了修改,反之亦然!
我的以为上图对于Binder进程间通讯机制的理解有很是大的做用,绝对大于全文的代码分析,毕竟咱们并不会亲自去写这部分代码,理解代码内容才是关键。