`
hududumo
  • 浏览: 238525 次
文章分类
社区版块
存档分类
最新评论

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
分享到:
评论

相关推荐

    linux之 kernel添加node节点案例

    本文档需要使用ultra-edit代开,里面实现了linux kernel设备节点的全部代码,可以直接在linux 上编译验证。此节点是file类型,实现对file的buffer读写操作。可以基于此模式增加自己的硬件设备节点。通过此节点的实现...

    Binder设计与实现

    Linux已经拥有管道,system V IPC,socket等IPC手段,却还要倚赖Binder来实现进程间通信,说明Binder具有无可比拟的优势。深入了解Binder并将之与传统 IPC做对比有助于我们深入领会进程间通信的实现和性能优化。本文...

    android系统深入浅出binder机制分析

    android系统深入浅出binder机制分析

    move android binder to linux

    将android的binder框架移植到linux下面,同时写了一个点灯硬件服务进行测试,还包括android logger系统 测试半年未发现问题

    Android Binder 实现原理

    很详细讲解了Binder实现原理,细节,设计思想

    Android Binder设计与实现

    详细分析了android binder的设计与实现。

    Android Binder设计与实现——设计篇

    罗升阳在介绍和学习Binder的Blog中推荐:《Android Binder设计与实现》一文,详细地介绍了内核空间的Binder驱动程序的数据结构和设计原理

    Android binder C++ service/client 实现. 共享内存

    android binder c++ 代码实现,添加内存共享、读写通知通过binder通行。代码再Android 9上测试无问题。

    Android Binder设计与实现.docx

    Android Binder设计与实现.docxAndroid Binder设计与实现.docxAndroid Binder设计与实现.docxAndroid Binder设计与实现.docxAndroid Binder设计与实现.docxAndroid Binder设计与实现.docxAndroid Binder设计与实现....

    Android_Binder设计与实现_-_设计篇

    Android_Binder设计与实现_-_设计篇

    模拟binder通信的demo

    模拟binder通信的demo模拟binder通信的demo模拟binder通信的demo模拟binder通信的demo模拟binder通信的demo模拟binder通信的demo模拟binder通信的demo模拟binder通信的demo模拟binder通信的demo模拟binder通信的demo...

    图解android Binder 实现机制

    通过图解的方式,将Android Binder通信的根本原理呈现出来。

    使用Binder实现进程间传递对象案例

    本资源使用 Binder 实现2个进程间传输用户自定义对象,在不使用 AIDL 的情况下,重写 Stub 类和 Proxy 类,分别配置在服务端和客户端,帮助初学者更好得理解 Binder

    使用Binder实现进程间通讯简单案例

    本资源使用 Binder 实现2个进程间通讯(只传输基本数据类型数据),在不使用 AIDL 的情况下,重写 Stub 类和 Proxy 类,分别配置在服务端和客户端,帮助初学者更好得理解 Binder

    基于Android自带的Binder库,用C++来实现Binder应用程序的Demo.pdf

    学习韦东山老师的4412开发板视频,做了点笔记,发出来跟大家分享一下这个学习的过程,如发现说的有误的地方,还请指出,谢谢!

    Android AIDL Binder 实现与详解 Demo

    Android AIDL Binder 实现与详解。此资源实现了 Android AIDL 通信,自定义 AIDL 数据类型。同时演示了定向 Tag 『inout in out』的区别。并且配有博文详细解释相关知识点以及需要注意的细节。

    Android NDK实现Binder服务和客户端

    需要在系统源码下编译,或者提取出对应的头文件亦可。这里需要注意Android4.x以后系统SeLinux如果打开,系统级需要配置对应的sepolicy才能使用。测试阶段推荐直接setenforce 0关闭鉴权即可

    AndroidBinder设计与实现-设计篇

    Linux已经拥有管道,systemVIPC,socket等IPC手段,却还要倚赖Binder来实现进程间通信,说明Binder具有无可比拟的优势。深入了解Binder并将之与传统IPC做对比有助于我们深入领会进程间通信的实现和性能优化。本文将...

    Android Binder C/C++层实现示例

    此代码是Android Binder一个C/C++层的实现demo,简单的描述了client和server的实现过程。

Global site tag (gtag.js) - Google Analytics