mmap作为struct file_operations的重要一个元素,mmap主要是实现物理内存到虚拟内存的映射关系,这样可以实现直接访问虚拟内存,而不用使用设备相关的read、write操作,mmap的基本过程是将文件映射到虚拟内存中。在之前的一篇博客中谈到了mmap实现文件复制的操作。
关于linux中的mmap调用如下,最好的办法查看命令,man mmap:
必要的头文件
#include
函数声明
void * mmap(void *addr,size_t length,int prot,
int flags,int fd,off_t offset);
关于各个参数的意义如下:
1、返回值是一个通用型指针,这样就保证了各种类型的申请方式。
2、void *addr 是程序员所希望的虚拟地址作为起始映射地址,通常为NULL,内核自动分配。
3、size_t length当然是指需要映射的区域大小。
4、int flags是指对这段区域的保护方式。具体的可以参看内核源码的linux/mm.h。常用的是PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE。
5、int flags主要是指对这段区域的映射方式,主要分为两种方式MAP_SHARE,MAP_PRIVATE.其中的MAP_SHARE是指对映射区域的写操作会更新到文件中,这样就相当于直接操作文件。而MAP_PRIVATE通常采用一种称为"写时保护的机制"实现映射,对映射区的写操作不会更新到文件中,实现方法是将需要被写操作的页复制到重新分配的新页中,然后再对新的页进行写操作。原来的映射并没有改变,但是读操作并不会重新分配物理内存空间。具体的参考深入理解计算机系统。
6、int fd是指将被映射的文件描述符,映射需要保证文件描述符的正确性。
7、off_t offset是指从文件的具体位置开始映射,通常情况下可以设置为0,即从开头映射。
基本的映射关系如下图:
设备驱动的mmap实现主要是将一个物理设备的可操作区域(设备空间)映射到一个进程的虚拟地址空间。这样就可以直接采用指针的方式像访问内存的方式访问设备。在驱动中的mmap实现主要是完成一件事,就是实际物理设备的操作区域到进程虚拟空间地址的映射过程。同时也需要保证这段映射的虚拟存储器区域不会被进程当做一般的空间使用,因此需要添加一系列的保护方式。
具体的实现过程如下:
- /*主要是建立虚拟地址到物理地址的页表关系,其他的过程又内核自己完成*/
- static int mem_mmap(struct file* filp,struct vm_area_struct *vma)
- {
- /*间接的控制设备*/
- struct mem_dev *dev = filp->private_data;
-
- /*标记这段虚拟内存映射为IO区域,并阻止系统将该区域包含在进程的存放转存中*/
- vma->vm_flags |= VM_IO;
- /*标记这段区域不能被换出*/
- vma->vm_flags |= VM_RESERVED;
- /**/
- if(remap_pfn_range(vma,/*虚拟内存区域*/
- vma->vm_start, /*虚拟地址的起始地址*/
- virt_to_phys(dev->data)>>PAGE_SHIFT, /*物理存储区的物理页号*/
- dev->size, /*映射区域大小*/
- vma->vm_page_prot /*虚拟区域保护属性*/
- ))
- return -EAGAIN;
- return 0;
- }
具体的实现分析如下:
- vma->vm_flags |= VM_IO;
- vma->vm_flags |= VM_RESERVED;
上面的两个保护机制就说明了被映射的这段区域具有映射IO的相似性,同时保证这段区域不能随便的换出。就是建立一个物理页与虚拟页之间的关联性。具体原理是虚拟页和物理页之间是以页表的方式关联起来,虚拟内存通常大于物理内存,在使用过程中虚拟页通过页表关联一切对应的物理页,当物理页不够时,会选择性的牺牲一些页,也就是将物理页与虚拟页之间切断,重现关联其他的虚拟页,保证物理内存够用。在设备驱动中应该具体的虚拟页和物理页之间的关系应该是长期的,应该保护起来,不能随便被别的虚拟页所替换。具体也可参看关于虚拟存储器的文章。
接下来就是建立物理页与虚拟页之间的关系,即采用函数remap_pfn_range(),具体的参数如下:
int remap_pfn_range(structvm_area_struct *vma, unsigned long addr,unsigned long pfn, unsigned long size, pgprot_t prot)
1、struct vm_area_struct是一个虚拟内存区域结构体,表示虚拟存储器中的一个内存区域。其中的元素vm_start是指虚拟存储器中的起始地址。
2、addr也就是虚拟存储器中的起始地址,通常可以选择addr = vma->vm_start。
3、pfn是指物理存储器的具体页号,通常通过物理地址得到对应的物理页号,具体采用virt_to_phys(dev->data)>>PAGE_SHIFT.首先将虚拟内存转换到物理内存,然后得到页号。>>PAGE_SHIFT通常为12,这是因为每一页的大小刚好是4K,这样右移12相当于除以4096,得到页号。
4、size区域大小
5、区域保护机制。
返回值,如果成功返回0,否则正数。
测试代码可以直接通过对虚拟内存区域操作,实现不同的操作,如下:
- #include<fcntl.h>
- #include<unistd.h>
- #include<stdio.h>
- #include<stdlib.h>
- #include<sys/types.h>
- #include<sys/stat.h>
- #include<sys/mman.h>
- #include<string.h>
- int main()
- {
- int fd;
- char *start;
-
- char buf[2048];
- strcpy(buf,"This is a test!!!!");
- fd = open("/dev/memdev0",O_RDWR);
-
- if(fd == -1)
- {
- printf("Error!!\n");
- exit(-1);
- }
- /*创建映射*/
- start = mmap(NULL,2048,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
- /*必须检测是否成功*/
- if(start == -1)
- {
- printf("mmap error!!!\n");
- exit(-1);
- }
- strcpy(start,buf);
- printf("start = %s,buf = %s\n",start,buf);
- strcpy(start,"Test is Test!!!\n");
- printf("start = %s,buf = %s\n",start,buf);
- /**/
- strcpy(buf,start);
-
- printf("start = %s,buf=%s\n",start,buf);
- /*取消映射关系*/
- munmap(start,2048);
- /*关闭文件*/
- close(fd);
- exit(0);
- }
经过测试,成功得到了驱动。
阅读(4005) | 评论(0) | 转发(4) |