原文地址:https://www.cnblogs.com/hoys/archive/2012/02/17/2355914.html
// 内核模块加载函数
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内核模块中的内存映射技术,包括kmalloc_map_init函数的设备号申请和buffer分配,kmalloc_map_mmap函数的用户空间内存映射,以及nocache标志设置防止缓存。同时,详细解释了nopage()函数如何为设备提供更灵活的内存映射,以及remap_pfn_range()与nopage()的区别。

1211

被折叠的 条评论
为什么被折叠?



