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

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

接着我们去了遇龙河,那里的水清澈见底,我把脚伸进水里,

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

相关文章:

你感兴趣的文章:

标签云: