2)保存kernel态虚拟地址空间的起始地址,以便后面使用:
proc->buffer = area->addr;
3) 计算并保存进程用户态虚拟地址空间起始地址与kernel态虚拟地址空间的起始地址的差值, 以便后面使用。
proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
4)分配物理页表项(struct page)
proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
5)binder_update_page_range
它的工作为:
a)分配物理页
b)分别对vma用户空间建立页表、对vmalloc区域建立页表映射关系。
前面有了用户态和Kernel态的虚拟地址空间,但是还不能访问,因为还没有对应的物理内存。
补充知识:
a)struct page用于跟踪描述一个物理页面是否正在被使用。所有的page结构将都被存入一个叫做mem_map的全局数组中.
b)在每个进程的task_struct中包含一个指向mm_struct结构的指针.进程的mm_struct中则包含了进程可执行影像的页目录指针pgd.还包含了指向vm_area_struct的几个指针,每个vm_area_struct包含一个进程的虚拟地址区域.
binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)
proc->buffer指向内核的vmalloc 区域的起始地址,前面已经有了vma(vm_area_struct)和 area(vm_struct)。binder_update_page_range实现代码如下:
staticintbinder_update_page_range(structbinder_proc *proc,intallocate,
void*start,void*end,
structvm_area_struct *vma)
{
void*page_addr;
unsignedlonguser_page_addr;
structvm_struct tmp_area;
structpage **page;
structmm_struct *mm;
binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
"binder: %d: %s pages %p-%p\n", proc->pid,
allocate ?"allocate":"free", start, end);
if(end <= start)
return0;
if(vma)
mm = NULL;
else
mm = get_task_mm(proc->tsk);
if(mm) {
down_write(&mm->mmap_sem);
vma = proc->vma;
}
if(allocate == 0)
gotofree_range;
if(vma == NULL) {
printk(KERN_ERR"binder: %d: binder_alloc_buf failed to "
"map pages in userspace, no vma\n", proc->pid);
gotoerr_no_vma;
}
for(page_addr = start; page_addr
intret;
structpage **page_array_ptr;
page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
BUG_ON(*page);
//分配一个物理页
*page = alloc_page(GFP_KERNEL | __GFP_ZERO);
if(*page == NULL) {
printk(KERN_ERR"binder: %d: binder_alloc_buf failed "
"for page at %p\n", proc->pid, page_addr);
gotoerr_alloc_page_failed;
}
tmp_area.addr = page_addr;
tmp_area.size = PAGE_SIZE + PAGE_SIZE/* guard page? */;
page_array_ptr = page;
//根据kernel态的虚拟地址,分配对应的pud, pmd和pte并填充对应的值
//以使根据虚拟地址,可以通过pgd, pud, pmd和pte寻址到对应的物理存储单元
ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);
if(ret) {
printk(KERN_ERR"binder: %d: binder_alloc_buf failed "
"to map page at %p in kernel\n",
proc->pid, page_addr);
gotoerr_map_kernel_failed;
}
user_page_addr =
(uintptr_t)page_addr + proc->user_buffer_offset;
//根据用户态的虚拟地址,插入一页到用户空间的vma,
//从而用户空间访问从user_page_addr开始的一页内存时,
//从而可以访问到与page对应的物理页中对应的存储单元
ret = vm_insert_page(vma, user_page_addr, page[0]);
if(ret) {
printk(KERN_ERR"binder: %d: binder_alloc_buf failed "
"to map page at %lx in userspace\n",
proc->pid, user_page_addr);
gotoerr_vm_insert_page_failed;
}
/* vm_insert_page does not seem to increment the refcount */
}
if(mm) {
up_write(&mm->mmap_sem);
mmput(mm);
}
return0;
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((unsignedlong)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;
}
a)map_vm_area: 映射Kernel虚拟地址到物理内存,为vmalloc 区域的连续地址空间进行页表映射,当然需要vm_struct (提供虚拟地址)参数和 page参数(用来make pte的),这就完成了内核区的映射
b) vm_insert_page: 更新vma对应的页表,这样就是实现了mmap功能
c)binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)调用的时候只分配了1页,这个是为了节约空间,按需分配。而进程虚拟空间和vmalloc内核空间按需要分配,反正它不占用实际物理内存,所以开始就占用了所需的全部空间,而实际的物理页按需获取;
proc->vma为调用进程的一段用户空间;
proc->files为调用进程的files_struct结构;
proc->buffer_size为需要映射的长度(小于4m)-sizeof(structbinder_buffer);
proc->pages为分配的物理页page的指针数组,开始只有一项,即1页,但是长度还是预留好了;
proc->buffer为内核连续映射区首地址;
proc->user_buffer_offset 为用户空间映射区首地址-内核空间连续映射的首地址。