“小王,不瞒你说,我现在是悲喜交加啊,悲的是:这最后一章,我讲的是胆颤心惊(以前自己都没学好,现在也算还账了),喜的是每讲一张,我知道离结束就近了一点,赶快把这个东西过掉,进入下一环节,那又是我牛皮吹破天的时代了”看着小王期盼和怀疑的眼神,我,昔日的风采也不见了。
“没事的,小涛哥,其实说真的,不是我安慰你哈,从开始我什么都不懂,到现在我也算个入门级的高手了,都是你一手带过来的,我已经对你推崇备至了,你就放心吧,会的你尽情教,不会的,你慢慢说,我都听你的”小王善解人意的说。
“嗯,你真好,看来我没看错你,那好,继续课程”。。。
一般情况下,用户空间是不可能也不应该直接访问设备的,但是设备驱动程序可实现mmap()函数,这个函数可使得用户空间能直接访问设备的物理地址。实际上,mmap()S实现了这样的一个映射过程,它将用户空间的一段内存与设备内存关联,当用户访问用户空间的这段地址范围时,实际上会转化为对设备的访问。
mmp()必须以PAGE_SIZE为单位进行映射,实际上,内存只能以页为单位进行映射,若要映射非PAGE_SIZE整数倍的地址范围,要先进行页对齐,强行以PAGE
_SIZE的倍数大小进行映射。驱动中mmp()函数原型如下:int (*mmp)(struct file *, struct vm_area_struct *);它实现的机制是建立页表,并填充VMA结构体中
vm_operations_struct指针,vm_area_struct用于描述一个虚拟内存区域。结构体如下:
struct vm_area_struct { struct mm_struct * vm_mm; //所处的地址空间 unsigned long vm_start;//开始虚拟地址 unsigned long vm_end;//结束虚拟地址 struct vm_area_struct *vm_next; pgprot_t vm_page_prot; //访问权限 unsigned long vm_flags; //标志 ... struct vm_operations_struct * vm_ops; //操作VMA的函数集指针 unsigned long vm_pgoff; //偏移(页帧号) struct file * vm_file; void * vm_private_data; ... };
其中vm_ops成员指向这个VMA的操作集,结构体定义如下:
struct vm_operations_struct { void (*open)(struct vm_area_struct * area); void (*close)(struct vm_area_struct * area); struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int *type); int (*populate)(struct vm_area_struct * area, unsigned long address, unsigned long len, pgprot_t prot, unsigned long pgoff, int nonblock); ... }; 在内核生成一个VMA后,它就会调用该VMA的open()函数。
而驱动中的mmap()函数将在用户进行mmap()系统调用时最终被调用,当用户调用mmap()时候内核会进行如下处理:
1)在进程的虚拟空间查找一块VMA.
2)将这块VMA进行映射。
3)如果设备驱动程序或文件系统的file_operations定义了mmap()操作,则调用它。
4)将这个VMA插入到进程的VMA链表中。
file_operations中mmap()函数的第一个参数就是步骤1中找的VMA.由mmap()系统调用映射的内存可由munmap()解除映射。这个函数原型如下:
int munmap(caddr_t addr, size_t len);
但是,需要注意的是:当用户进行mmap()系统调用后,尽管VMA在设备驱动文件操作结构体的mmap()被调用前就已经产生,内核却不会调用VMA的open
函数,通常需要在驱动的mmap()函数中先上调用vma->vm_ops->open().为了说明问题,给出一个vm_operations_struct操作范例:
static int xxx_mmp(struct file *filp, struct vm_area_struct *vma) { if(remap_pfn_range(vma, vma->vm_start, vm->vm_pgoff, vma->vm_end - vma->start, vma->vm_page_prot)) //建立页表 return - EAGAIN; vma->vm_ops = &xxx_remap_vm_ops; xxx_vma_open(vma); return 0; } void xxx_vma_open(struct vm_area_struct *vma) //VMA打开函数 { ... printk(KERN_NOTICE "xxx VMA open, virt %lx, phys %1x\n",vma->vm_start, vma->vm_pgoff 《PAGE_SHIFT); } void xxx_vma_close(struct vm_area_struct *vma) //VMA关闭函数 { ... printk(KERN_NOTICE "xxx VMA close. \n"); } static struct vm_operation_struct xxx_remap_vm_ops = //VM操作结构体 { .open=xxx_vma_open, .close=xxx_vma_close, ... }
在这段代码中调用的remap_pfn_range()创建页表。我们前边说过在内核空间用kmalloc申请内存,这部分内存如果要映射到用户空间可以通过mem_map
_reserve()调用设置为保留后进行,具体怎么操作,咱们下集继续。