时间:晚上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映射。
小王,这节和前边一节是在一起看的,我也可以喘口气歇歇了,你慢慢看吧,就不烦你了,晚上吃饭叫上我哈..