Linux Kernel 及 binder mmap实现

1. 简介

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

  1. void*mmap(void*addr,size_tsize,intprot,intflags,intfd,longoffset)

然后进入系统调用。

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)isalwaysunitsof4K.Ifwecan'tdotherequested
  3. *offset,wereturnEINVAL.
  4. */
  5. sys_mmap2:
  6. #ifPAGE_SHIFT>12
  7. tstr5,#PGOFF_MASK
  8. moveqr5,r5,lsr#PAGE_SHIFT-12
  9. streqr5,[sp,#4]
  10. beqsys_mmap_pgoff
  11. movr0,#-EINVAL
  12. movpc,lr
  13. #else
  14. strr5,[sp,#4]
  15. bsys_mmap_pgoff
  16. #endif

4) 调用sys_mmap_pgoff

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

  1. asmlinkagelongsys_mmap_pgoff(unsignedlongaddr,unsignedlonglen,
  2. unsignedlongprot,unsignedlongflags,
  3. unsignedlongfd,unsignedlongpgoff);

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

  1. SYSCALL_DEFINE6(mmap_pgoff,unsignedlong,addr,unsignedlong,len,
  2. unsignedlong,prot,unsignedlong,flags,
  3. unsignedlong,fd,unsignedlong,pgoff)
  4. {
  5. structfile*file=NULL;
  6. unsignedlongretval=-EBADF;
  7. if(!(flags&MAP_ANONYMOUS)){
  8. audit_mmap_fd(fd,flags);
  9. if(unlikely(flags&MAP_HUGETLB))
  10. return-EINVAL;
  11. file=fget(fd);
  12. if(!file)
  13. gotoout;
  14. }elseif(flags&MAP_HUGETLB){
  15. structuser_struct*user=NULL;
  16. /*
  17. *VM_NORESERVEisusedbecausethereservationswillbe
  18. *takenwhenvm_ops->mmap()iscalled
  19. *Adummyuservalueisusedbecausewearenotlocking
  20. *memorysonoaccountingisnecessary
  21. */
  22. len=ALIGN(len,huge_page_size(&default_hstate));
  23. file=hugetlb_file_setup(HUGETLB_ANON_FILE,len,VM_NORESERVE,
  24. &user,HUGETLB_ANONHUGE_INODE);
  25. if(IS_ERR(file))
  26. returnPTR_ERR(file);
  27. }
  28. flags&=~(MAP_EXECUTABLE|MAP_DENYWRITE);
  29. down_write(¤t->mm->mmap_sem);
  30. retval=do_mmap_pgoff(file,addr,len,prot,flags,pgoff);
  31. up_write(¤t->mm->mmap_sem);
  32. if(file)
  33. fput(file);
  34. out:
  35. returnretval;
  36. }

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

下面以binder驱动为例。

3. binder mmap实现

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

  1. staticintbinder_mmap(structfile*filp,structvm_area_struct*vma)
  2. {
  3. intret;
  4. structvm_struct*area;
  5. structbinder_proc*proc=filp->private_data;
  6. constchar*failure_string;
  7. structbinder_buffer*buffer;
  8. if((vma->vm_end-vma->vm_start)>SZ_4M)
  9. vma->vm_end=vma->vm_start+SZ_4M;
  10. binder_debug(BINDER_DEBUG_OPEN_CLOSE,
  11. "binder_mmap:%d%lx-%lx(%ldK)vma%lxpagep%lx\n",
  12. proc->pid,vma->vm_start,vma->vm_end,
  13. (vma->vm_end-vma->vm_start)/SZ_1K,vma->vm_flags,
  14. (unsignedlong)pgprot_val(vma->vm_page_prot));
  15. if(vma->vm_flags&FORBIDDEN_MMAP_FLAGS){
  16. ret=-EPERM;
  17. failure_string="badvm_flags";
  18. gotoerr_bad_arg;
  19. }
  20. vma->vm_flags=(vma->vm_flags|VM_DONTCOPY)&~VM_MAYWRITE;
  21. if(proc->buffer){
  22. ret=-EBUSY;
  23. failure_string="alreadymapped";
  24. gotoerr_already_mapped;
  25. }
  26. area=get_vm_area(vma->vm_end-vma->vm_start,VM_IOREMAP);
  27. if(area==NULL){
  28. ret=-ENOMEM;
  29. failure_string="get_vm_area";
  30. gotoerr_get_vm_area_failed;
  31. }
  32. proc->buffer=area->addr;
  33. proc->user_buffer_offset=vma->vm_start-(uintptr_t)proc->buffer;
  34. #ifdefCONFIG_CPU_CACHE_VIPT
  35. if(cache_is_vipt_aliasing()){
  36. while(CACHE_COLOUR((vma->vm_start^(uint32_t)proc->buffer))){
  37. printk(KERN_INFO"binder_mmap:%d%lx-%lxmaps%pbadalignment\n",proc->pid,vma->vm_start,vma->vm_end,proc->buffer);
  38. vma->vm_start+=PAGE_SIZE;
  39. }
  40. }
  41. #endif
  42. proc->pages=kzalloc(sizeof(proc->pages[0])*((vma->vm_end-vma->vm_start)/PAGE_SIZE),GFP_KERNEL);
  43. if(proc->pages==NULL){
  44. ret=-ENOMEM;
  45. failure_string="allocpagearray";
  46. gotoerr_alloc_pages_failed;
  47. }
  48. proc->buffer_size=vma->vm_end-vma->vm_start;
  49. vma->vm_ops=&binder_vm_ops;
  50. vma->vm_private_data=proc;
  51. if(binder_update_page_range(proc,1,proc->buffer,proc->buffer+PAGE_SIZE,vma)){
  52. ret=-ENOMEM;
  53. failure_string="allocsmallbuf";
  54. gotoerr_alloc_small_buf_failed;
  55. }
  56. buffer=proc->buffer;
  57. INIT_LIST_HEAD(&proc->buffers);
  58. list_add(&buffer->entry,&proc->buffers);
  59. buffer->free=1;
  60. binder_insert_free_buffer(proc,buffer);
  61. proc->free_async_space=proc->buffer_size/2;
  62. barrier();
  63. proc->files=get_files_struct(current);
  64. proc->vma=vma;
  65. /*printk(KERN_INFO"binder_mmap:%d%lx-%lxmaps%p\n",
  66. proc->pid,vma->vm_start,vma->vm_end,proc->buffer);*/
  67. return0;
  68. err_alloc_small_buf_failed:
  69. kfree(proc->pages);
  70. proc->pages=NULL;
  71. err_alloc_pages_failed:
  72. vfree(proc->buffer);
  73. proc->buffer=NULL;
  74. err_get_vm_area_failed:
  75. err_already_mapped:
  76. err_bad_arg:
  77. printk(KERN_ERR"binder_mmap:%d%lx-%lx%sfailed%d\n",
  78. proc->pid,vma->vm_start,vma->vm_end,failure_string,ret);
  79. returnret;
  80. }

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. staticintbinder_update_page_range(structbinder_proc*proc,intallocate,
  2. void*start,void*end,
  3. structvm_area_struct*vma)
  4. {
  5. void*page_addr;
  6. unsignedlonguser_page_addr;
  7. structvm_structtmp_area;
  8. structpage**page;
  9. structmm_struct*mm;
  10. binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
  11. "binder:%d:%spages%p-%p\n",proc->pid,
  12. allocate?"allocate":"free",start,end);
  13. if(end<=start)
  14. return0;
  15. if(vma)
  16. mm=NULL;
  17. else
  18. mm=get_task_mm(proc->tsk);
  19. if(mm){
  20. down_write(&mm->mmap_sem);
  21. vma=proc->vma;
  22. }
  23. if(allocate==0)
  24. gotofree_range;
  25. if(vma==NULL){
  26. printk(KERN_ERR"binder:%d:binder_alloc_buffailedto"
  27. "mappagesinuserspace,novma\n",proc->pid);
  28. gotoerr_no_vma;
  29. }
  30. for(page_addr=start;page_addr<end;page_addr+=PAGE_SIZE){
  31. intret;
  32. structpage**page_array_ptr;
  33. page=&proc->pages[(page_addr-proc->buffer)/PAGE_SIZE];
  34. BUG_ON(*page);
  35. //分配一个物理页
  36. *page=alloc_page(GFP_KERNEL|__GFP_ZERO);
  37. if(*page==NULL){
  38. printk(KERN_ERR"binder:%d:binder_alloc_buffailed"
  39. "forpageat%p\n",proc->pid,page_addr);
  40. gotoerr_alloc_page_failed;
  41. }
  42. tmp_area.addr=page_addr;
  43. tmp_area.size=PAGE_SIZE+PAGE_SIZE/*guardpage?*/;
  44. page_array_ptr=page;
  45. //根据kernel态的虚拟地址,分配对应的pud,pmd和pte并填充对应的值
  46. //以使根据虚拟地址,可以通过pgd,pud,pmd和pte寻址到对应的物理存储单元
  47. ret=map_vm_area(&tmp_area,PAGE_KERNEL,&page_array_ptr);
  48. if(ret){
  49. printk(KERN_ERR"binder:%d:binder_alloc_buffailed"
  50. "tomappageat%pinkernel\n",
  51. proc->pid,page_addr);
  52. gotoerr_map_kernel_failed;
  53. }
  54. user_page_addr=
  55. (uintptr_t)page_addr+proc->user_buffer_offset;
  56. //根据用户态的虚拟地址,插入一页到用户空间的vma,
  57. //从而用户空间访问从user_page_addr开始的一页内存时,
  58. //从而可以访问到与page对应的物理页中对应的存储单元
  59. ret=vm_insert_page(vma,user_page_addr,page[0]);
  60. if(ret){
  61. printk(KERN_ERR"binder:%d:binder_alloc_buffailed"
  62. "tomappageat%lxinuserspace\n",
  63. proc->pid,user_page_addr);
  64. gotoerr_vm_insert_page_failed;
  65. }
  66. /*vm_insert_pagedoesnotseemtoincrementtherefcount*/
  67. }
  68. if(mm){
  69. up_write(&mm->mmap_sem);
  70. mmput(mm);
  71. }
  72. return0;
  73. free_range:
  74. for(page_addr=end-PAGE_SIZE;page_addr>=start;
  75. page_addr-=PAGE_SIZE){
  76. page=&proc->pages[(page_addr-proc->buffer)/PAGE_SIZE];
  77. if(vma)
  78. zap_page_range(vma,(uintptr_t)page_addr+
  79. proc->user_buffer_offset,PAGE_SIZE,NULL);
  80. err_vm_insert_page_failed:
  81. unmap_kernel_range((unsignedlong)page_addr,PAGE_SIZE);
  82. err_map_kernel_failed:
  83. __free_page(*page);
  84. *page=NULL;
  85. err_alloc_page_failed:
  86. ;
  87. }
  88. err_no_vma:
  89. if(mm){
  90. up_write(&mm->mmap_sem);
  91. mmput(mm);
  92. }
  93. return-ENOMEM;
  94. }

a)map_vm_area:映射Kernel虚拟地址到物理内存,为vmalloc区域的连续地址空间进行页表映射,当然需要vm_struct(提供虚拟地址)参数和page参数(用来makepte的),这就完成了内核区的映射

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)-sizeofstructbinder_buffer);

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

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

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

http://www.linuxidc.com/Linux/2012-03/55897p3.htm
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值