Binder進程間通訊(四)----映射設備文件

    進程打開了/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進程間通訊機制的理解有很是大的做用,絕對大於全文的代碼分析,畢竟咱們並不會親自去寫這部分代碼,理解代碼內容纔是關鍵。

相關文章
相關標籤/搜索