设备驱动的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); }
经过测试,成功得到了驱动。