一 內(nèi)存映射函數(shù)的實現(xiàn) binder_mmap(kernel/drivers/android/binder.c)
static int binder_mmap(struct file *filp, struct vm_area_struct *vma/*用戶態(tài)虛擬地址空間描述,地址空間在0~3G*/)
{
int ret;
/* 一塊連續(xù)的內(nèi)核虛擬地址空間描述,32位體系架構(gòu)中地址空間在 3G+896M + 8M ~ 4G之間*/
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,如果大于4M就改為4M大小。
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));
//檢查vma是否被forbidden,vma是一塊連續(xù)的用戶態(tài)虛擬內(nèi)存地址空間的描述
if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {
ret = -EPERM;
failure_string = "bad vm_flags";
goto err_bad_arg;
}
//打開VM_DONTCOPY,關(guān)閉VM_MAYWRITE
vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;
//加上binder_mmap_lock互斥鎖,因為接下來要操作proc結(jié)構(gòu)體,可能發(fā)生多線程競爭
mutex_lock(&binder_mmap_lock);
//一個進程已經(jīng)有一次mmap,如要執(zhí)行新的map,需先將之前的unmap。
if (proc->buffer) {
ret = -EBUSY;
failure_string = "already mapped";
goto err_already_mapped;
}
/* 獲取一塊與用戶態(tài)空間大小一致的內(nèi)核的連續(xù)虛擬地址空間,
* 注意虛擬地址空間是在此一次性分配的,物理頁面卻是需要時才去申請和映射
*/
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;
}
//將內(nèi)核虛擬地址記錄在proc的buffer中
proc->buffer = area->addr;
/* 記錄用戶態(tài)虛擬地址空間與內(nèi)核態(tài)虛擬地址空間的偏移量,
* 這樣通過buffer和user_buffer_offset就可以計算出用戶態(tài)的虛擬地址。
*/
proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
/*釋放互斥鎖*/
mutex_unlock(&binder_mmap_lock);
#ifdef CONFIG_CPU_CACHE_VIPT
/* CPU的緩存方式是否為: VIPT(Virtual Index Physical Tag):使用虛擬地址的索引域和物理地址的標(biāo)記域。
* 這里先不管,有興趣的可參照:[https://blog.csdn.net/Q_AN1314/article/details/78980191](https://blog.csdn.net/Q_AN1314/article/details/78980191)
*/
if (cache_is_vipt_aliasing()) {
while (CACHE_COLOUR((vma->vm_start ^ (uint32_t)proc->buffer))) {
pr_info("binder_mmap: %d %lx-%lx maps %p bad alignment\n", proc->pid, vma->vm_start, vma->vm_end, proc->buffer);
vma->vm_start += PAGE_SIZE;
}
}
#endif
/*分配存放物理頁地址的數(shù)組*/
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中*/
proc->buffer_size = vma->vm_end - vma->vm_start;
/* 安裝vma線性空間操作函數(shù):open,close,fault
* open-> binder_vma_open: 簡單的輸出日志,pid,虛擬地址的起止、大小、標(biāo)志位(vm_flags和vm_page_prot)
* close -> binder_vma_close: 將proc的vma,vma_vm_mm設(shè)為NULL,并將proc加入到binder_deferred_workqueue隊列,
* binder驅(qū)動有一個單獨的線程處理這個隊列。
* fault -> binder_vam_fault: 直接返回VM_FAULT_SIGBUS,
*/
vma->vm_ops = &binder_vm_ops;
/*在vma的vm_private_data字段里存入proc的引指針*/
vma->vm_private_data = proc;
/* 先分配1個物理頁,并將其分別映射到內(nèi)核線性地址和用戶態(tài)虛擬地址上,具體詳見2
*/
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;
}
/*成功分配了物理頁并建立好的映射關(guān)系后,內(nèi)核起始虛地址做為第一個binder_buffer的地址*/
buffer = proc->buffer;
/*接著將內(nèi)核虛擬內(nèi)存鏈入proc的buffers和free_buffers鏈表中,free標(biāo)志位設(shè)為1
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;/*vma->vm_mm: vma對應(yīng)的mm_struct,描述一個進程的虛擬地址空間,一個進程只有一個*/
/*pr_info("binder_mmap: %d %lx-%lx maps %p\n",
proc->pid, vma->vm_start, vma->vm_end, proc->buffer);*/
return 0;
/*出錯處理*/
err_alloc_small_buf_failed:
kfree(proc->pages);
proc->pages = NULL;
err_alloc_pages_failed:
mutex_lock(&binder_mmap_lock);
vfree(proc->buffer);
proc->buffer = NULL;
err_get_vm_area_failed:
err_already_mapped:
mutex_unlock(&binder_mmap_lock);
err_bad_arg:
pr_err("binder_mmap: %d %lx-%lx %s failed %d\n",
proc->pid, vma->vm_start, vma->vm_end, failure_string, ret);
return ret;
}
打開binder后,需要調(diào)用mmap進行內(nèi)存映射,該函數(shù)經(jīng)過系統(tǒng)調(diào)用,會調(diào)用到binder驅(qū)動的binder_mmap函數(shù)?;具^程在上面的代碼基本已經(jīng)都注釋了。幾個需要注意的地方這里再說明一下:
調(diào)用
get_vm_area獲取內(nèi)核態(tài)虛擬地址,地址是在32體系架構(gòu)地址空間在: 3G+896M + 8M ~ 4G之間。proc中沒有直接記錄用戶態(tài)的虛地址,而是存放一個用戶態(tài)地址與內(nèi)核態(tài)地址偏移量:proc->user_buffer_offset。-
vma操作函數(shù) ——vma->vm_ops, Binder實現(xiàn)了open,close和fault三個接口。-
open(binder_vma_open)的實現(xiàn)只是簡單的輸出一條進程id及vma地址及標(biāo)志位相關(guān)信息的debug日志。 -
close(binder_vma_close)除了輸出一條類似open的日志信息外,還會將proc->vma和proc->vma_vm_mm置空,接著調(diào)用binder_deferred_work在binder_deferred_workqueue隊列中放入一個BINDER_DEFERRED_PUT_FILES狀態(tài)的work,在之后binder線程執(zhí)行到該work時會將proc->files置空, 接著調(diào)用put_files_struct來釋放進程所屬的文件資源。 -
fault(binder_vm_fault):簡單的返回VM_FAULT_SIGBUS, 這個鉤子是在訪問的虛地址沒有映射的物理頁時(缺頁)時,該函數(shù)被缺頁處理程序調(diào)用,該函數(shù)負責(zé)返回物理頁描述符,但因在binder中物理頁框與虛擬地址的映射,在調(diào)用binder_alloc_buf分配binder_buffer時就已經(jīng)建立好了,所以一般來說是不會發(fā)生缺頁中斷的。
-
需要調(diào)用
binder_update_page_range分配一個頁框的原因是:用于存放第一個binder_buffer,此時整個地址空間都在這個binder_buffer中管理, 但是隨著地址空間被不斷的分配和回收,會分裂成一系列的binder_buffer節(jié)點。
二 分配物理頁框,建立地址映射 —— binder_update_page_range
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 page **page;
struct mm_struct *mm;
binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
"%d: %s pages %p-%p\n", proc->pid,
allocate ? "allocate" : "free", start, end);
if (end <= start)
return 0;
trace_binder_update_page_range(proc, allocate, start, end);
if (vma)
mm = NULL; /*binder_mmap的調(diào)用走這里*/
else
/* 讀取進程的內(nèi)存描述符(mm_struct),
* 并增加內(nèi)存描述符(mm_struct)中的mm_users用戶計數(shù),防止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;
}
}
/*本次調(diào)用是釋放物理頁,直接進入釋放物理頁框流程*/
if (allocate == 0)
goto free_range;
if (vma == NULL) {
pr_err("%d: binder_alloc_buf failed to map pages in userspace, no vma\n",
proc->pid);
goto err_no_vma;
}
/* 開始循環(huán)分配物理頁,并建立映射,每次循環(huán)分配1個頁框*/
for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
int ret;
/* 確定頁框所存放的數(shù)組的位置,按內(nèi)核虛擬地址由小到大排列*/
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;
}
/*將內(nèi)核虛擬地址與該頁框建立映射關(guān)系*/
ret = **map_kernel_range_noflush**((unsigned long)page_addr,
PAGE_SIZE, PAGE_KERNEL, page);
/* 將剛剛修改的內(nèi)核頁表項刷新到CPU高速緩存*/
**flush_cache_vmap**((unsigned long)page_addr,
(unsigned long)page_addr + PAGE_SIZE);
if (ret != 1) {
pr_err("%d: binder_alloc_buf failed to map page at %p in kernel\n",
proc->pid, page_addr);
goto err_map_kernel_failed;
}
/*計算用戶態(tài)虛地址*/
user_page_addr =
(uintptr_t)page_addr + proc->user_buffer_offset;
/*將用戶虛擬地址與該頁框建立映射關(guān)系*/
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);/*減少內(nèi)存描述符(mm_struct)中的mm_users用戶計數(shù)*/
}
/*分配物理頁框流程到這里結(jié)束,接下去是釋放物理頁流程*/
return 0;
/*釋放物理頁,解除地址映射*/
free_range:/*由后往前解除用戶態(tài)及內(nèi)核態(tài)的物理頁框映射,并釋放物理頁框*/
for (page_addr = end - PAGE_SIZE; page_addr >= start;
page_addr -= PAGE_SIZE) {
page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
if (vma)
/*解除用戶態(tài)虛擬地址和物理頁框的映射*/
zap_page_range(vma, (uintptr_t)page_addr +
proc->user_buffer_offset, PAGE_SIZE, NULL);
err_vm_insert_page_failed:
/*解除內(nèi)核態(tài)虛擬地址和物理頁框的映射*/
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);/*減少內(nèi)存描述符中的mm_users用戶計數(shù)*/
}
return -ENOMEM;
}
binder_update_page_range同時負責(zé)分配和釋放物理頁框,具體是分配還是釋放通過參數(shù)allocate控制,如果該參數(shù)為0,則表示要解除內(nèi)核態(tài)和用戶態(tài)對物理頁框的地址映射,釋放物理頁框;否則就是申請物理頁框,并建立內(nèi)核態(tài)和用戶態(tài)的地址映射。整個流程相對直觀,關(guān)鍵代碼都已經(jīng)注釋,理解起來應(yīng)該不難。參數(shù)
vma的類型是struct vm_area_struct是用戶態(tài)虛擬地址空間描述。該參數(shù)為NULL表示binder_update_page_range在內(nèi)核調(diào)用路徑中,此時需嘗試獲取mm_struct并增加其引用計數(shù),以防止進程的內(nèi)存描述被釋放,然后再在操作完成后減少它的引用計數(shù)(mmput)。