linux内存学习笔记(二)——设备地址到用户空间

时间:2023-01-16 21:33:17
 

系统调用mmap(用户空间使用)

caddr_t mmap(caddr_t addr,size_t len,int prot,int flags,int fd,off_t offset);

prot,指定访问权限,

PROT_READ(可读),

PROT_WRITE(可写)

PROT_EXEC(可执行)

PROT_NONE(不可访问)

caddr_t,实际上是 void*;

 

驱动中的mmap(内核空间)

int(*mmap)(struct file *,struct vm_area_struct *);

 

VMA,即vm_area_struct,用于描述一个虚拟内存区域

struct vm_area_struct{

       struct mm_struct *vm_mm;

       unsigned long vm_start;

       unsigned long vm_end;

 

       pgprot_t vm_page_prot;

       unsigned long vm_flags;

 

       struct vm_operations_struct *vm_ops;

      

       unsigned long vm_pgoff;

       struct file *vm_file;

       void *vm_private_data;

       ……

       }

当用户进行mmap()系统调用后,尽管VMA已经产生,但是内核却不会调用VMA操作集中的open()函数。通常需要在驱动的 mmap()函数中显示调用 vma->vm_ops->open()。

使用模板

static int xxx_mmap(struct file *filp,struct vm_area_struct *vma)

{

       if(remap_pfn_range(vma,vma->vma_start,vm->vm_pgoff,vma->vm_end-

vma->vm_start,vma->vm_page_prot))

return –EAGAIN;

              vma->vm_ops=&xxx_rempa_vm_ops;

              xxx_vma_open(vma);//显式调用

              return 0;

       }

 

       void xxx_vma_open(struct vm_area_struct *vma)

       {

              ……

              printk(KERN_NOTICE “xxx_VMA open”);

       }

 

       void xxx_vma_close(struct vm_area_struct *vma)

       {

              ……

              printk(KERN_NOTICE “xxx VMA close.\n”);

       }

 

       static struct vm_operations_struct xxx_remap_vm_ops={

              .open=xxx_vma_open,

              .close=xxx_vma_close,

              ……

       };

 

 模板说明:

(1)       VMA的数据成员是内核根据用户的请求自动填充的。

(2)       vm_pgoff,这个成员是要映射到物理地址上的页桢号。页桢号实际上是实际的物理地址右移PAGA_SIZE得到的

(3)       整个映射的过程是有方向性的,同用户空间(进程地址)到物理空间,而且用户空间需要多少,物理空间才提供多少。提供的页桢号从vm_pgoff开始。

(4)       映射必须以PAGE_SIZE为单位进行。即vma->vm_end - vma->vm_start=N*PAGE_SIZE

(5)       remap_pfn_range()函数原型

int remap_pfn_range(struct vm_area_struct *vma,unsigned long addr,

unsigned long pfn,unsigned long size,pgprot_t prot);

建立页表。即映射。

还有一个作用相似的函数

int remap_page_range(unsigned long start,unsigned long phy_addr,

unsigned long size, pgprot_t prot);

它和 remap_pfn_range的区别就是前者用实际的物理地址(第二个参数)建立页表,后者用页桢号建立页表。

      

        remap_pfn_range函数的一个限制是:它只能访问保留页和超出物理内存的物理地址。(Linux中,在内存映射时,物理地址页被标记为reserved,表示内存管理对其不起作用。如在PC中,640KB-1MB之间的内存被标记为保留的,因为这个范围位于内核自身代码的内部。)所以remap_pfn_range不允许重新映射常规地址。包括调用get_free_page函数所获得的地址。相反,他能映射零内存页。(引用来自网络)

      

       kmalloc()申请的内存若要被映射到用户空间可以通过mem_map_reserve()设置为保留后进行。

       使用模板

————————————————————————————————————

       模块加载函数

              buffer = kmalloc(BUF_SIZE,GFP_KERNEL);

       for(page=virt_to_page(buffer);page<virt_to_page(buffer+BUF_SIZE);page++)

       mem_map_reserve(page);

 

       mmap()函数中

       while(size>0) {

              phyaddr=virt_to_phys((void *)buffer)

              if(remap_page_range(start,phyaddr,PAGE_SIZE,PAGE_SHARED))

                     return –EAGAIN;  

              start +=PAGE_SIZE;

              buffer +=PAGE_SIZE;

              size -= PAGE_SIZE;

              }return 0;

_______________________________________________________________________________

 

通常情况,I/O内存被映射时需要是noche的,这时,只需要在mmap()函数的使用模板中,对vma->vm_page_prot的标志设为nocache即可。

即vma->vm_page_prot=pgprot_noncached(vma->vm_page_prot);

 

nopage()函数

当发生缺页异常时,VMA操作集中的nopage()方法被调用。实现nopage()后,用户通过mremap()系统调用重新映射区域所绑定的地址。

模板

struct page *xxx_vma_nopage(struct vm_area_struct *vma,unsigned long address,int *type)

{

       struct page *pageptr;

       unsigned long offset = vma->vm_pgoff<<PAGE_SHIFT;

       unsigned long physaddr = address – vma->vm_start+offset;

       unsigned long pageframe = physaddr >> PAGE_SHIFT;

       if(!pfn_valid(pageframe))

              return NOPAGE_SIGBUS;

       pageptr = pfn_to_page(pageframe);

       get_page(pageptr);

       if(type)

              *type = VM_FAULT_MINOR;

       return pageptr;

}

 

说明,pfn_valid()确保由用户给定的虚拟地址转化成物理地址页桢号时是有效的。这样,才能实现缺页后,找到正确的物理页面地址填充。

 nopage()中的address,可以是低端内存地址。对它的映射即是对RAM映射。而remap_pfn_range()一般映射设备内存。