系统调用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()一般映射设备内存。