Linux驱动修炼之道-内存映射


  1. void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);  

内存映射函数mmap负责把文件内容映射到进程的虚拟内存空间,通过对这段内存的读取和修改,来实现对文件的读取和修改,而不需要再调用read,write等操作。
addr:指定映射的起始地址,通常设为NULL,由系统指定。
length:映射到内存的文件长度。
prot:映射的保护方式,可以是:
PROT_EXEC:映射区可被执行
PROT_READ:映射区可被读取
PROT_WRITE:映射区可被写入
PROT_NONE:映射区不能存取
Flags:映射区的特性,可以是:
MAP_SHARED:
写入映射区的数据会复制回文件,且允许其他映射该文件的进程共享。
MAP_PRIVATE:
对映射区的写入操作会产生一个映射区的复制(copy_on_write),对此区域所做的修改不会写回原文件。
fd:由open返回的文件描述符,代表要映射的文件。
offset:以文件开始处的偏移量,必须是分页大小的整数倍,通常为0,表示从文件头开始映射。
解除映射:

  1. int munmap(void *start, size_t length);  

功能:取消参数start所指向的映射内存,参数length表示欲取消的内存大小。
返回值:解除成功返回0,否则返回-1,错误原因存在于errno中。
虚拟地址区域:vm_area_struct
Linux内核使用结构vm_area_struct(<linux/mm_types.h>)描述虚拟内存区域,其中几个主要成员如下:
unsigned long vm_start 虚拟内存区域起始地址
unsigned long vm_end   虚拟内存区域结束地址
unsigned long vm_flags 该区域的标志
如:VM_IO和VM_RESERVED。VM_IO将该VMA标记为内存映射的IO区域,VM_IO会阻止系统将该区域包含在进程的存放转存(core dump)中,VM_RESERVED标志内存区域不能被换出。
mmap设备操作
映射一个设备是指把用户空间的一段地址关联到设备内存上,当程序读写这段用户空间的地址时,它实际上是在访问设备。这里需要做的两个操作:
1.找到可以用来关联的虚拟地址区间
2.关联
其中找到可以用来关联的虚拟地址区间是由内核完成的,mmap只要关联这个操作。
mmap方法是file_operations结构的成员,在mmap系统调用发出时被调用。在此之前,内核已经完成了很多工作。mmap设备方法所需要做的就是建立虚拟地址到物理地址的页表。

  1. void (*mmap)(struct file*, struct vm_area_struct *);  

其中第二个参数struct vm_area_struct *相当于内核找到的,可以拿来用的虚拟内存区间。
mmap完成页表的建立:
方法有二:
1.使用remap_pfn_range一次建立所有页表;
2.使用nopage VMA方法每次建立一个页表;
构造页表的工作可由remap_pgn_range函数完成,原型如下:

  1. int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot);  

vma是内核为我们找到的虚拟地址空间,addr要关联的是虚拟地址,pfn是要关联的物理地址,size是关联的长度是多少。
mmap设备操作实例:

  1. int memdev_map(struct file *filp, struct vm_area_struct *vma){   
  2.     vma->vm_flags |= VM_IO;   
  3.     vma->vm_flags |= VM_RESERVED;   
  4.     if(remap_pgn_range(vma,vma->start, virt_to_phys(dev->data>>PAGE_SHIFT),size,vma->vm_page_prot))   
  5.         return -EAGAIN;   
  6.     return 0;   
  7. }  

先说一下对于ARM而言虚拟地址与物理地址的关系:
在arch/arm/include/asm/memory.h中:

  1. #define __virt_to_phys(x)   ((x) - PAGE_OFFSET + PHYS_OFFSET)  
  2. #define __phys_to_virt(x)   ((x) - PHYS_OFFSET + PAGE_OFFSET)   
  3.   
  4. static inline unsigned long virt_to_phys(void *x)   
  5. {   
  6.     return __virt_to_phys((unsigned long)(x));   
  7. }   
  8.   
  9. static inline void *phys_to_virt(unsigned long x)   
  10. {   
  11.     return (void *)(__phys_to_virt((unsigned long)(x)));   
  12. }  
  • 1
  • 2
  • 下一页

相关内容