看到之前在csdn 上写的摄像头驱动总结,首先得感谢摄像头驱动这个东西 让我在读书时挣到了一笔生活费!!------------
现在把文章简要拷贝过来,以及去掉之前的代码然后随便扯一下文件的map吧
驱动核心: 将摄像头驱动中的yuv数据map到用户空间,便于访问。read 性能不够!!
原理是:通过mmap将内核太buffer关联到用户空间,DMA拷贝yuv数据到内核buffer,此时应用层直接访问yuv数据!
文件物理地址和进程虚拟地址的一一映射关系过程
- 进程在用户空间调用库函数mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
- 在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址;
- 为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化;
- 将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表或树中
- 为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。
- 通过该文件的文件结构体,链接到file_operations模块,调用内核函数mmap,其原型为:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用户空间库函数。
- 内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。
由于此时需要映射的fd 不是文件而是open的设备驱动!所以需要实现驱动的map系统调用!!总不能用文件的mmap吧
目前mmap的系统调用的入口函数都是:do_mmap
最后do_mmap都会调用do_mmap_pgoff
其核心代码为:
1、检测各种参数,具体不看了
2、创建新的vma区域之前先要寻找一块足够大小的空闲区域,所以调用get_unmapped_area函数查找没有映射过的空洞内存区,返回值addr就是这段空洞的起始地址
3、判断是文件映射还是匿名映射,如果是文件映射则赋值inode ;以及各种私有 共享属性设置/检测
4、创建新vma线性区,通过函数mmap_region实现;
- 检测当前映射的addr ---addr+end地址是否已经被映射;遍历该进程已有的vma红黑树,如果找到vma覆盖[addr, end]区域,然后去munmap已有的vma区域
- 是否可以复用以前老旧的VMA线性区;属性不同的区段不能共存于同一逻辑区间VMA,所以如果找不到可用的一样属性的vma 就需要单独建立一个逻辑区间(就相当于 堆栈 线性区不一样)------>也就是将多个属性的地址空间【addr----addr+end】 交给一个VMA管理;减少因为vma导致的slab消耗和虚拟内存的空洞
- vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);创建一个单独的线性区
5、如果是新创建的vma;也就是不能复用之前的vma;就需要设置vma相关参数
/*初始化vma线性区成员*/ vma->vm_mm = mm; vma->vm_start = addr; vma->vm_end = addr + len; vma->vm_flags = vm_flags; vma->vm_page_prot = vm_get_page_prot(vm_flags); vma->vm_pgoff = pgoff; INIT_LIST_HEAD(&vma->anon_vma_chain);
6、如果是文件映射; error = file->f_op->mmap(file, vma)---------调用文件操作函数集的mmap成员
如果是匿名的话 就调用 对应的shmem_zero_setup 进行映射物理内存等
7、vma_link(mm, vma, prev, rb_link, rb_parent);----------------------------将新建的vma插入到进程地址空间的vma红黑树中,已经做一些计数更新等。
对于文件来说:比如ext2文件,其f_op->mmap 指向了generic_file_mmap
const struct vm_operations_struct generic_file_vm_ops = { .fault = filemap_fault, }; /* This is used for a general mmap of a disk file */ int generic_file_mmap(struct file * file, struct vm_area_struct * vma) { struct address_space *mapping = file->f_mapping; if (!mapping->a_ops->readpage) return -ENOEXEC; file_accessed(file); vma->vm_ops = &generic_file_vm_ops; vma->vm_flags |= VM_CAN_NONLINEAR; return 0; }
实际上没有映射物理内存, 设置了一个缺页中断函数;当这个区间的一个页面首次受到访问时,会由于见面无映射而发生缺页异常,相应的处理函数是do_no_page()
但是由于摄像头mmap肯定是用来缓存读取yuv数据的;何必不在一开始分配虚拟地址的时候就映射好呢?
使用remap_pfn_range一次建立所有页表;
所以 自己实现int (*mmap) (struct file *, struct vm_area_struct *) 函数时,直接调用
ret = remap_pfn_range(vma, vma->vm_start, paddr >> PAGE_SHIFT, len, vma->vm_page_prot);
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,unsigned long pfn, unsigned long size, pgprot_t prot) vma: 虚拟内存区域指针 virt_addr: 虚拟地址的起始值 pfn: 要映射的物理地址所在的物理页帧号,可将物理地址>>PAGE_SHIFT得到。 size: 要映射的区域的大小。 prot: VMA的保护属性。
常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。
mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程。因此mmap效率更高。
设备数据就是:摄像头芯片缓存数据DMA传输到内核空间----map到用户态-