感谢支持
我们一直在努力

Linux Kernel 及 binder mmap实现

1. 简介

    对于mmap在用户态通过函数以下函数进行调用:


  1. void*   mmap( void*  addr,  size_t  size, int  prot, int  flags, int  fd,  long  offset )  

    然后进入系统调用。 


2. Kernel mmap实现

1)然后进入系统调用,其系统调用号为: 


kernel/arch/arm/include/asm/unistd.h


#define __NR_mmap2   (__NR_SYSCALL_BASE+192)


2)触发软中断


     其ISR 代码位于kernel/arch/arm/kernel/entry-common.S的ENTRY(vector_swi), __NR_mmap2对应的函数为:sys_mmap2(位于linux/arch/arm/kernel/calls.S)


3)sys_mmap2的实现


位于kernel/arch/arm/kernel/entry-common.S,实现代码如下:


  1. /* 

  2.  * Note: off_4k (r5) is always units of 4K.  If we can’t do the requested 

  3.  * offset, we return EINVAL. 

  4.  */  

  5. sys_mmap2:  

  6. #if PAGE_SHIFT > 12   

  7.         tst r5, #PGOFF_MASK  

  8.         moveq   r5, r5, lsr #PAGE_SHIFT – 12  

  9.         streq   r5, [sp, #4]  

  10.         beq sys_mmap_pgoff  

  11.         mov r0, #-EINVAL  

  12.         mov pc, lr  

  13. #else   

  14.         str r5, [sp, #4]  

  15.         b   sys_mmap_pgoff  

  16. #endif  

4) 调用sys_mmap_pgoff


在kernel/include/linux/syscalls.h中定义如下:


  1. asmlinkage long sys_mmap_pgoff(unsigned long addr, unsigned long len,  

  2.             unsigned long prot, unsigned long flags,  

  3.             unsigned long fd, unsigned long pgoff);  

6)sys_mmap_pgoff实现
在kernel/mm/mmap.c中实现如下:


  1. SYSCALL_DEFINE6(mmap_pgoff, unsigned long, addr, unsigned long, len,  

  2.         unsigned long, prot, unsigned long, flags,  

  3.         unsigned long, fd, unsigned long, pgoff)  

  4. {  

  5.     struct file *file = NULL;  

  6.     unsigned long retval = -EBADF;  

  7.   

  8.     if (!(flags & MAP_ANONYMOUS)) {  

  9.         audit_mmap_fd(fd, flags);  

  10.         if (unlikely(flags & MAP_HUGETLB))  

  11.             return -EINVAL;  

  12.         file = fget(fd);  

  13.         if (!file)  

  14.             goto out;  

  15.     } else if (flags & MAP_HUGETLB) {  

  16.         struct user_struct *user = NULL;  

  17.         /* 

  18.          * VM_NORESERVE is used because the reservations will be 

  19.          * taken when vm_ops->mmap() is called 

  20.          * A dummy user value is used because we are not locking 

  21.          * memory so no accounting is necessary 

  22.          */  

  23.         len = ALIGN(len, huge_page_size(&default_hstate));  

  24.         file = hugetlb_file_setup(HUGETLB_ANON_FILE, len, VM_NORESERVE,  

  25.                         &user, HUGETLB_ANONHUGE_INODE);  

  26.         if (IS_ERR(file))  

  27.             return PTR_ERR(file);  

  28.     }  

  29.   

  30.     flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);  

  31.   

  32.     down_write(¤t->mm->mmap_sem);  

  33.     retval = do_mmap_pgoff(file, addr, len, prot, flags, pgoff);  

  34.     up_write(¤t->mm->mmap_sem);  

  35.   

  36.     if (file)  

  37.         fput(file);  

  38. out:  

  39.     return retval;  

  40. }  

     其功能为:从当前进程中获取用户态可用的虚拟地址空间(vm_area_struct *vma),在mmap_region中真正获取vma,然后调用file->f_op->mmap(file, vma),调用具体的支持mmap的驱动来处理。


     下面以binder驱动为例。

3. binder mmap实现

    binder驱动的mmap函数为:binder_mmap,其实现代码如下:


  1. static int binder_mmap(struct file *filp, struct vm_area_struct *vma)  

  2. {  

  3.     int ret;  

  4.     struct vm_struct *area;  

  5.     struct binder_proc *proc = filp->private_data;  

  6.     const char *failure_string;  

  7.     struct binder_buffer *buffer;  

  8.   

  9.     if ((vma->vm_end – vma->vm_start) > SZ_4M)  

  10.         vma->vm_end = vma->vm_start + SZ_4M;  

  11.   

  12.     binder_debug(BINDER_DEBUG_OPEN_CLOSE,  

  13.              “binder_mmap: %d %lx-%lx (%ld K) vma %lx pagep %lx\n”,  

  14.              proc->pid, vma->vm_start, vma->vm_end,  

  15.              (vma->vm_end – vma->vm_start) / SZ_1K, vma->vm_flags,  

  16.              (unsigned long)pgprot_val(vma->vm_page_prot));  

  17.   

  18.     if (vma->vm_flags & FORBIDDEN_MMAP_FLAGS) {  

  19.         ret = -EPERM;  

  20.         failure_string = “bad vm_flags”;  

  21.         goto err_bad_arg;  

  22.     }  

  23.     vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;  

  24.   

  25.     if (proc->buffer) {  

  26.         ret = -EBUSY;  

  27.         failure_string = “already mapped”;  

  28.         goto err_already_mapped;  

  29.     }  

  30.   

  31.     area = get_vm_area(vma->vm_end – vma->vm_start, VM_IOREMAP);  

  32.     if (area == NULL) {  

  33.         ret = -ENOMEM;  

  34.         failure_string = “get_vm_area”;  

  35.         goto err_get_vm_area_failed;  

  36.     }  

  37.     proc->buffer = area->addr;  

  38.     proc->user_buffer_offset = vma->vm_start – (uintptr_t)proc->buffer;  

  39.   

  40. #ifdef CONFIG_CPU_CACHE_VIPT   

  41.     if (cache_is_vipt_aliasing()) {  

  42.         while (CACHE_COLOUR((vma->vm_start ^ (uint32_t)proc->buffer))) {  

  43.             printk(KERN_INFO “binder_mmap: %d %lx-%lx maps %p bad alignment\n”, proc->pid, vma->vm_start, vma->vm_end, proc->buffer);  

  44.             vma->vm_start += PAGE_SIZE;  

  45.         }  

  46.     }  

  47. #endif   

  48.     proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end – vma->vm_start) / PAGE_SIZE), GFP_KERNEL);  

  49.     if (proc->pages == NULL) {  

  50.         ret = -ENOMEM;  

  51.         failure_string = “alloc page array”;  

  52.         goto err_alloc_pages_failed;  

  53.     }  

  54.     proc->buffer_size = vma->vm_end – vma->vm_start;  

  55.   

  56.     vma->vm_ops = &binder_vm_ops;  

  57.     vma->vm_private_data = proc;  

  58.   

  59.     if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {  

  60.         ret = -ENOMEM;  

  61.         failure_string = “alloc small buf”;  

  62.         goto err_alloc_small_buf_failed;  

  63.     }  

  64.     buffer = proc->buffer;  

  65.     INIT_LIST_HEAD(&proc->buffers);  

  66.     list_add(&buffer->entry, &proc->buffers);  

  67.     buffer->free = 1;  

  68.     binder_insert_free_buffer(proc, buffer);  

  69.     proc->free_async_space = proc->buffer_size / 2;  

  70.     barrier();  

  71.     proc->files = get_files_struct(current);  

  72.     proc->vma = vma;  

  73.   

  74.     /*printk(KERN_INFO “binder_mmap: %d %lx-%lx maps %p\n”, 

  75.          proc->pid, vma->vm_start, vma->vm_end, proc->buffer);*/  

  76.     return 0;  

  77.   

  78. err_alloc_small_buf_failed:  

  79.     kfree(proc->pages);  

  80.     proc->pages = NULL;  

  81. err_alloc_pages_failed:  

  82.     vfree(proc->buffer);  

  83.     proc->buffer = NULL;  

  84. err_get_vm_area_failed:  

  85. err_already_mapped:  

  86. err_bad_arg:  

  87.     printk(KERN_ERR “binder_mmap: %d %lx-%lx %s failed %d\n”,  

  88.            proc->pid, vma->vm_start, vma->vm_end, failure_string, ret);  

  89.     return ret;  

  90. }  

1)获取kernel态虚拟地址空间:
struct vm_struct *area;
area = get_vm_area(vma->vm_end – vma->vm_start, VM_IOREMAP);


     根据传过来的vma(数据结构为vm_area_struct,属于进程的一段空间,用于与内核空间映射用的),调用get_vm_area在内核的vmalloc区域获得一个相同大小的连续空间,数据结构为vm_struct,同时将该结构加入到vm_list统一管理

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实现代码如下:


  1. static int binder_update_page_range(struct binder_proc *proc, int allocate,  

  2.                     void *start, void *end,  

  3.                     struct vm_area_struct *vma)  

  4. {  

  5.     void *page_addr;  

  6.     unsigned long user_page_addr;  

  7.     struct vm_struct tmp_area;  

  8.     struct page **page;  

  9.     struct mm_struct *mm;  

  10.   

  11.     binder_debug(BINDER_DEBUG_BUFFER_ALLOC,  

  12.              “binder: %d: %s pages %p-%p\n”, proc->pid,  

  13.              allocate ? “allocate” : “free”, start, end);  

  14.   

  15.     if (end <= start)  

  16.         return 0;  

  17.   

  18.     if (vma)  

  19.         mm = NULL;  

  20.     else  

  21.         mm = get_task_mm(proc->tsk);  

  22.   

  23.     if (mm) {  

  24.         down_write(&mm->mmap_sem);  

  25.         vma = proc->vma;  

  26.     }  

  27.   

  28.     if (allocate == 0)  

  29.         goto free_range;  

  30.   

  31.     if (vma == NULL) {  

  32.         printk(KERN_ERR “binder: %d: binder_alloc_buf failed to “  

  33.                “map pages in userspace, no vma\n”, proc->pid);  

  34.         goto err_no_vma;  

  35.     }  

  36.   

  37.     for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {  

  38.         int ret;  

  39.         struct page **page_array_ptr;  

  40.         page = &proc->pages[(page_addr – proc->buffer) / PAGE_SIZE];  

  41.   

  42.         BUG_ON(*page);  

  43.                   //分配一个物理页   

  44.         *page = alloc_page(GFP_KERNEL | __GFP_ZERO);  

  45.         if (*page == NULL) {  

  46.             printk(KERN_ERR “binder: %d: binder_alloc_buf failed “  

  47.                    “for page at %p\n”, proc->pid, page_addr);  

  48.             goto err_alloc_page_failed;  

  49.         }  

  50.         tmp_area.addr = page_addr;  

  51.         tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;  

  52.         page_array_ptr = page;  

  53.                   //根据kernel态的虚拟地址,分配对应的pud, pmd和pte并填充对应的值   

  54.                      //以使根据虚拟地址,可以通过pgd, pud, pmd和pte寻址到对应的物理存储单元   

  55.         ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);  

  56.         if (ret) {  

  57.             printk(KERN_ERR “binder: %d: binder_alloc_buf failed “  

  58.                    “to map page at %p in kernel\n”,  

  59.                    proc->pid, page_addr);  

  60.             goto err_map_kernel_failed;  

  61.         }  

  62.         user_page_addr =  

  63.             (uintptr_t)page_addr + proc->user_buffer_offset;  

  64.                   //根据用户态的虚拟地址,插入一页到用户空间的vma,   

  65.                      //从而用户空间访问从user_page_addr开始的一页内存时,   

  66.                      //从而可以访问到与page对应的物理页中对应的存储单元   

  67.         ret = vm_insert_page(vma, user_page_addr, page[0]);  

  68.         if (ret) {  

  69.             printk(KERN_ERR “binder: %d: binder_alloc_buf failed “  

  70.                    “to map page at %lx in userspace\n”,  

  71.                    proc->pid, user_page_addr);  

  72.             goto err_vm_insert_page_failed;  

  73.         }  

  74.         /* vm_insert_page does not seem to increment the refcount */  

  75.     }  

  76.     if (mm) {  

  77.         up_write(&mm->mmap_sem);  

  78.         mmput(mm);  

  79.     }  

  80.     return 0;  

  81.   

  82. free_range:  

  83.     for (page_addr = end – PAGE_SIZE; page_addr >= start;  

  84.          page_addr -= PAGE_SIZE) {  

  85.         page = &proc->pages[(page_addr – proc->buffer) / PAGE_SIZE];  

  86.         if (vma)  

  87.             zap_page_range(vma, (uintptr_t)page_addr +  

  88.                 proc->user_buffer_offset, PAGE_SIZE, NULL);  

  89. err_vm_insert_page_failed:  

  90.         unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);  

  91. err_map_kernel_failed:  

  92.         __free_page(*page);  

  93.         *page = NULL;  

  94. err_alloc_page_failed:  

  95.         ;  

  96.     }  

  97. err_no_vma:  

  98.     if (mm) {  

  99.         up_write(&mm->mmap_sem);  

  100.         mmput(mm);  

  101.     }  

  102.     return -ENOMEM;  

  103. }  

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)-sizeofstruct binder_buffer);


  proc->pages为分配的物理页page的指针数组,开始只有一项,即1页,但是长度还是预留好了;


  proc->buffer为内核连续映射区首地址 


     proc->user_buffer_offset 为用户空间映射区首地址-内核空间连续映射的首地址。

赞(0) 打赏
转载请注明出处:服务器评测 » Linux Kernel 及 binder mmap实现
分享到: 更多 (0)

听说打赏我的人,都进福布斯排行榜啦!

支付宝扫一扫打赏

微信扫一扫打赏