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

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读写操作。可以基于此模式增加自己的硬件设备节点。通过此节点的实现...

    move android binder to linux

    2. **用户空间库**:在Linux上实现与Android Binder兼容的用户空间库,包括Binder接口、Parcel对象的序列化和反序列化以及线程管理等。 3. **Service Manager**:移植Android的Service Manager,使得服务能在系统中...

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

    1. **Binder驱动**:Binder机制的底层依赖于Binder驱动,它在内核空间实现了Binder对象的管理和消息传递。每个Binder对象在内核中都有对应的Binder实体,用于处理消息和状态。 2. **Binder实体**:每个Binder对象在...

    Binder设计与实现

    Binder是Android系统中独特的进程间通信(IPC)机制,相较于Linux已有的管道、System V IPC和socket等,Binder具有显著的优势。理解Binder的工作原理和设计细节对于提升Android应用的性能和安全性至关重要。 1. ...

    Android and Linux Kernel

    标题中的“Android and Linux Kernel”指向了两个核心概念:Android操作系统和Linux内核。Android是一种基于Linux内核的开源操作系统,主要用于移动设备如智能手机和平板电脑。它由Google主导开发,并在开源社区中...

    Android Binder设计与实现

    尽管Linux内核已提供了多种IPC机制如管道、System V IPC(消息队列/共享内存/信号量)、Socket等,但Binder以其独特的优势成为Android系统中首选的IPC机制。 #### 二、Binder概述 **2.1 定义与优势** Binder是一...

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

    Binder正是在这种需求下应运而生,它在Linux内核驱动的基础上被设计实现,并且有着不可比拟的优势。 Binder机制基于Client-Server通信模型,这种模型已经在多种平台和应用领域得到广泛使用。在Android系统中,许多...

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

    需要注意的是,Binder通信的实现细节涉及到底层的消息传递机制,包括使用`Parcel`类来打包和解包数据,使用`Binder`类来管理通信过程。开发者必须理解这些类的作用和如何在客户端和服务端之间正确传递数据,才能成功...

    Android Binder 实现原理

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

    binder 核心思想分析

    1. **文件位置**:Binder驱动程序的主要实现位于`kernel/include/linux/binder.h`和`kernel/drivers/android/binder.c`文件中。 2. **设备信息**:Binder驱动程序是一个misc device,主设备号为10,采用动态设备号...

    android 的binder机制在java、c++层的实现

    在C++层,Binder是基于Linux内核驱动的,实现了跨进程的数据传输和方法调用。 **Java层实现**: 1. **服务端**:创建一个实现了`IBinder`接口的类,该类中的方法就是可供客户端调用的接口。例如,我们可以创建一个...

    binder例子

    3. Binder Driver:位于Linux内核层,负责Binder的调度和数据传输。 4. System Server:Android系统服务,负责管理所有的Binder连接和服务注册。 三、C++实现Binder 1. 创建Binder类:在C++中,我们需要继承自...

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

    本示例"Android Binder C/C++层实现示例"着重展示了如何在C/C++层面进行Binder通信的实现,这对于深入理解Android系统的底层工作原理至关重要。 一、Binder架构 Binder架构包括Client、Server、Service Manager和...

    通过binder实现进程间通讯 ,可以使用service的binder或者 AIDL生成的Stub返回binder 实现demo

    在Android开发中,Service的binder是实现IPC的一种方式。开发者可以在Service中定义一个binder对象,该对象包含可以被远程调用的方法。当Client进程绑定到该Service时,它会获取这个binder对象的一个引用,然后就...

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

    深入理解Binder的设计原理及其与传统IPC的对比,对于把握进程间通信的实现细节及性能优化至关重要。 #### Binder通信模型与协议 Binder的设计围绕Client-Server模型展开,其中服务器端负责提供服务,客户端则请求...

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

    本教程将详细阐述如何使用C++实现一个基于Binder的服务(Service)和客户端(Client),并利用共享内存进行高效的数据传输。 一、Binder基础 Binder是Android系统中的一个核心组件,它提供了跨进程调用方法,让不同...

    Android Binder Aidl原理.pdf

    Binder的mmap机制是在ProcessState构造函数中实现的。在ProcessState构造函数中,Binder驱动程序的内存被映射到进程的虚拟地址空间中,大小约为1M。这块内存映射只用于接收transactions,也就是说,它不参与数据的...

    binder原理

    1. **标准 Linux Kernel IPC 接口**:如信号量、共享内存等,这些机制是基于 Linux 内核提供的。 2. **标准 D-BUS 接口**:D-BUS 是一种用于进程间通信的通用消息总线。 3. **Binder 接口**:Binder 是 Android ...

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

    在Android系统中,Binder是实现跨进程通信(IPC,Inter-Process Communication)的主要机制,它允许不同进程间的组件互相调用方法,实现数据交换。在这个"使用Binder实现进程间传递对象案例"中,我们将深入探讨如何...

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

    在Android系统中,Binder是实现进程间通信(IPC,Inter-Process Communication)的主要机制,它允许不同进程的应用组件之间共享数据和服务。在这个简单的Binder案例中,我们将深入理解如何在不依赖AIDL(Android ...

Global site tag (gtag.js) - Google Analytics