Linux内核开发之内存与I/O访问(四)

时间:晚上7点

地点:寝室中..

“小王,今天就不多话了,接着昨天没讲完的,不然连不起来了,都..”我催促着。

  上节讲到kmalloc()申请的内存若要被映射到用户空间可以通过mem_map_reserve()设置为保留后进行。具体怎么操作呢,给你一个模版吧:

// 内核模块加载函数
int __init kmalloc_map_init(void)
{
    ../申请设备号,添加cedv结构体
  buffer = kmalloc(BUF_SIZE, GFP_KERNEL); //申请buffer
  for(page = virt_to_page(buffer); page< virt_to_page(buffer+BUF_SIZE); page++)
  {
     mem_map_reserve(page);  //置业为保留
  }
}
//mmap()函数
static int kmalloc_map_mmap(struct file *filp, struct vm_area_struct *vma)
{
    unsigned long page, pos;
    unsigned long start = (unsigned long)vma->start;
    unsigned long size = (unsigned long)(vma->end - vma->start);
    printk(KERN_INFO, "mmaptest_mmap called\n");
    if(size > BUF_SIZE)  //用户要映射的区域太大
        return - EINVAL;
    pos = (unsigned long)buffer;
    while(size > 0)   //映射buffer中的所有页
    {
        page = virt_to_phys((void *)pos);
        if(remap_page_range(start, page, PAGE_SIZE, PAGE_SHARRED))
            return -EAGAIN;
        start += PAGE_SIZE;
        pos +=PAGE_SIZE;
        size -= PAGE_SIZE;
    }
    return 0;
}

另外通常,IO内存被映射时需要是nocache的,这个时候应该对vma->vm_page_prot设置nocache标志。如下:

static int xxx_nocache_mmap(struct file *filp, struct vm_area_struct *vma)
{
  vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);   //赋nocache标志
  vma->vm_pgoff = ((u32)map_start >> PAGE_SHIFT);
  if(rempa_pfn_range(vma, vma->vm_start, vma->vm_pgoff, vma->vm_end - vm_start, vma->vm_page_prot));
     return - EAGGIN;
  return 0;
}

这段代码中的pgprot_noncached()是一个宏,它实际上禁止了相关页的cache和写缓冲(write buffer),另外一个稍微少的一些限制的宏是:

#define pgprot_writecombine(prot)  __pgprot(pgprot_val (prot) & –L_PTE_CACHEABLE);    它则没有禁止写缓冲

而除了rempa_pfn_range()外,在驱动程序中实现VMA的nopage()函数通常可以为设备提供更加灵活的内存映射途径。当发生缺页时,nopage()会被内核自动调用,。这是因为,当发生缺页异常时,系统会经过如下处理过程:

1)找到缺页的虚拟地址所在的VMA             2)如果必要,分配中间页目录表和页表              

3)如果页表项对应的物理页表不存在,则调用这个VMA的nopage()方法,它返回物理页面的页描述符。

4)将物理页面的地址填充到页表中。

实现nopage后,用户空间可以通过mremap()系统调用重新绑定映射区所绑定的地址,下面给出一个在设备驱动中使用nopage()的典型范例:

static int xxx_mmap(struct file *filp, struct vm_area_struct *vma);
{
     unsigned long offset = vma->vm_pgoff << PAGE_OFFSET;
     if(offset >= _ _pa(high_memory) || (filp->flags &O_SYNC))
             vma->vm_flags |=VM_IO;
     vma->vm_ops = &xxx_nopage_vm_ops;
     xxx_vma_open(vma);
     return 0;
}
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;    //返回页描述符
}

上述函数对常规内存进行映射,返回一个页描述符,可用于扩大或缩小映射的内存区域,由此可见,nopage()和remap_pfn_range()一个较大的区别在于remap_pfn

_range()一般用于设备内存映射,而nopage()还可以用于RAM映射。

 

小王,这节和前边一节是在一起看的,我也可以喘口气歇歇了,你慢慢看吧,就不烦你了,晚上吃饭叫上我哈..

你可能感兴趣的:(linux)