用戶空間在使用Binder進行IPC前,須要對Binder驅動進行初始化,這個過程主要執行了Binder驅動的open和mmap操做。mmap映射Binder傳輸使用的內存空間,大小爲(1M - 8K),但僅僅是進行虛擬地址空間映射,實際的物理內存分配會在數據傳輸時進行。mmap的源碼以下,node
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; // 映射空間不能大於4M if ((vma->vm_end - vma->vm_start) > SZ_4M) vma->vm_end = vma->vm_start + SZ_4M; ...... // fork的子進程沒法複製映射空間,而且不容許修改寫屬性 vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE; ...... // 獲取內核虛擬地址空間 area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP); ...... proc->buffer = area->addr; proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer; mutex_unlock(&binder_mmap_lock); ...... // 建立物理頁結構體 proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL); ...... 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)) { ...... // 建立buffers鏈表,插入第一個free buffer buffer = proc->buffer; INIT_LIST_HEAD(&proc->buffers); list_add(&buffer->entry, &proc->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; ...... }
幾個相關細節說明一下,數據結構
物理內存的分配和虛擬地址空間的映射是經過binder_update_page_range()進行的。物理內存的回收一樣也使用這個函數,經過參數allocate來區分。異步
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; ...... // 只有mmap時vma不爲NULL,其餘狀況都會根據proc來獲取內存相關數據 if (vma) mm = NULL; else // 獲取內存描述符,並增長用戶計數,防止mm_struct被釋放 mm = get_task_mm(proc->tsk); if (mm) { down_write(&mm->mmap_sem); vma = proc->vma; if (vma && mm != proc->vma_vm_mm) { pr_err("%d: vma mm and task mm mismatch\n", proc->pid); vma = NULL; } } // 回收內存時allocate爲0 if (allocate == 0) goto free_range; ...... // 循環分配物理頁,每次分配一頁 for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) { int ret; struct page **page_array_ptr; page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE]; BUG_ON(*page); // 分配一個物理頁,保存地址到proc中 *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO); ...... tmp_area.addr = page_addr; tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */; page_array_ptr = page; // 創建頁表與物理頁的映射 ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr); ...... user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset; // 插入物理頁到用戶虛擬地址空間 ret = vm_insert_page(vma, user_page_addr, page[0]); ...... } 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: ; } ........ }
Binder內存分配函數爲binder_alloc_buf(),直接看源碼。async
static struct binder_buffer *binder_alloc_buf(struct binder_proc *proc, size_t data_size, size_t offsets_size, int is_async) { struct rb_node *n = proc->free_buffers.rb_node; struct binder_buffer *buffer; size_t buffer_size; struct rb_node *best_fit = NULL; void *has_page_addr; void *end_page_addr; size_t size; ...... // size按指針字節數對齊 size = ALIGN(data_size, sizeof(void *)) + ALIGN(offsets_size, sizeof(void *)); ...... // 在free_buffers上尋找匹配size大小的節點。循環結束後,若是n==NULL // 表示沒有匹配節點,則best_fit爲最接近的大於size的節點 while (n) { buffer = rb_entry(n, struct binder_buffer, rb_node); BUG_ON(!buffer->free); buffer_size = binder_buffer_size(proc, buffer); if (size < buffer_size) { best_fit = n; n = n->rb_left; } else if (size > buffer_size) n = n->rb_right; else { best_fit = n; break; } } ...... // free_buffers沒有找到匹配的節點,使用best_fit節點來分配 if (n == NULL) { buffer = rb_entry(best_fit, struct binder_buffer, rb_node); buffer_size = binder_buffer_size(proc, buffer); } // best_fit節點最後一頁的起始地址 has_page_addr = (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK); // best_fit節點的內存大小,在分配size後若是不足以建立新的buffer,則不拆分 if (n == NULL) { if (size + sizeof(struct binder_buffer) + 4 >= buffer_size) buffer_size = size; /* no room for other buffers */ else buffer_size = size + sizeof(struct binder_buffer); } // 須要分配空間的最後一頁的結束地址 end_page_addr = (void *)PAGE_ALIGN((uintptr_t)buffer->data + buffer_size); // end_page>has_page代表要分配的空間的結束地址在best_fit節點的最後一頁上 // 這是修正分配的結束地址到has_page,由於最後一頁已經映射過 if (end_page_addr > has_page_addr) end_page_addr = has_page_addr; // 分配物理頁 if (binder_update_page_range(proc, 1, (void *)PAGE_ALIGN((uintptr_t)buffer->data), end_page_addr, NULL)) return NULL; // 將best_fit從free_buffers上擦除,讓後將新建的buffer插入allocated_buffers rb_erase(best_fit, &proc->free_buffers); buffer->free = 0; binder_insert_allocated_buffer(proc, buffer); // 多餘空間進行拆分,插入free_buffers if (buffer_size != size) { struct binder_buffer *new_buffer = (void *)buffer->data + size; list_add(&new_buffer->entry, &buffer->entry); new_buffer->free = 1; binder_insert_free_buffer(proc, new_buffer); } ...... buffer->data_size = data_size; buffer->offsets_size = offsets_size; buffer->async_transaction = is_async; ...... return buffer; }
分配過程的重點在起始地址的計算上。若是在free_buffers樹上能夠找到匹配的buffer則使用。若是找不到匹配的buffer,就使用大於需求大小且最接近的buffer。由於proc->buffers隊列上不會有連續的兩個free buffer(在釋放buffer時會合並),因此計算起始地址時就要考慮已分配的狀況。當需求分配的起始地址在一個allocated buffer上,就不須要再申請這個頁。函數
經過一張圖來描述Binder內存的變化。
ui
一樣看一下binder_free_buf()的源碼。spa
static void binder_free_buf(struct binder_proc *proc, struct binder_buffer *buffer) { size_t size, buffer_size; // 獲取要釋放的buffer的大小 buffer_size = binder_buffer_size(proc, buffer); // size按指針字節數對齊 size = ALIGN(buffer->data_size, sizeof(void *)) + ALIGN(buffer->offsets_size, sizeof(void *)); ...... // 釋放物理頁 binder_update_page_range(proc, 0, (void *)PAGE_ALIGN((uintptr_t)buffer->data), (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK), NULL); // 將釋放的buffer從allocated_buffers樹上擦除 rb_erase(&buffer->rb_node, &proc->allocated_buffers); buffer->free = 1; // 若是proc->buffers中下一個buffer爲free,則合併到正在釋放的buffer上 if (!list_is_last(&buffer->entry, &proc->buffers)) { struct binder_buffer *next = list_entry(buffer->entry.next, struct binder_buffer, entry); if (next->free) { rb_erase(&next->rb_node, &proc->free_buffers); binder_delete_free_buffer(proc, next); } } // 若是proc->buffers中上一個buffer爲free,則合併釋放的buffer到上一個buffer中 if (proc->buffers.next != &buffer->entry) { struct binder_buffer *prev = list_entry(buffer->entry.prev, struct binder_buffer, entry); if (prev->free) { binder_delete_free_buffer(proc, buffer); rb_erase(&prev->rb_node, &proc->free_buffers); buffer = prev; } } // 將處理好的buffer插入到free_buffers樹中 binder_insert_free_buffer(proc, buffer); }
在內存回收的過程當中,須要注意的仍是邊界地址的計算。與分配時類似,須要考慮釋放的buffer與其餘buffer在同一頁上的狀況。指針
內存回收時還要進行相連free buffer的合併,找到合併的buffer後,使用binder_delete_free_buffer()將下一buffer刪除掉。code
static void binder_delete_free_buffer(struct binder_proc *proc, struct binder_buffer *buffer) { struct binder_buffer *prev, *next = NULL; int free_page_end = 1; int free_page_start = 1; // 獲取被刪除buffer的前一個buffer prev = list_entry(buffer->entry.prev, struct binder_buffer, entry); // 若是被刪除buffer與前一個buffer會共用到同一物理頁,則不刪除起始頁,將free_page_start設置爲0 if (buffer_end_page(prev) == buffer_start_page(buffer)) { free_page_start = 0; // 若是被刪除buffer結束頁也在這一頁,則不會刪除物理頁,free_page_end也設爲0 if (buffer_end_page(prev) == buffer_end_page(buffer)) free_page_end = 0; ...... } if (!list_is_last(&buffer->entry, &proc->buffers)) { // 獲取被刪除buffer的下一個buffer next = list_entry(buffer->entry.next, struct binder_buffer, entry); // 若是被刪除buffer與下一個buffer會共用到同一物理頁,則不刪除結束頁 if (buffer_start_page(next) == buffer_end_page(buffer)) { free_page_end = 0; // 若是被刪除buffer起始頁也在這一頁,則不會刪除物理頁 if (buffer_start_page(next) == buffer_start_page(buffer)) free_page_start = 0; ...... } // 將buffer從proc->buffers列表中刪除 list_del(&buffer->entry); if (free_page_start || free_page_end) { ...... // 釋放物理頁,參考了free_page_start和free_page_end binder_update_page_range(proc, 0, free_page_start ? buffer_start_page(buffer) : buffer_end_page(buffer), (free_page_end ? buffer_end_page(buffer) : buffer_start_page(buffer)) + PAGE_SIZE, NULL); } }
最後用圖來展現內存回收時buffer的變化。
blog