利用ioremap访问硬件,需要经过两次拷贝
1.明确:不管是在用户空间还是在内核空间,软件一律不能去直接访问设备的物理地址;
2.在内核驱动中如果要访问设备的物理地址,需要利用ioremap将设备的物理地址映射到内核虚拟地址上(动态内存映射区),以后驱动程序访问这个内核虚拟地址就是在间
接得访问设备的物理地址(MMU,TLB,TTW)3.如果用户要访问硬件设备,不能直接访问,也不能在用户空间访问,只能通过系统调用(open,close,read,write,ioctl)来访问映射好的内核虚拟
地址,通过这种间接的访问来访问硬件设备,但是如果设计到数据的拷贝,还需要借助4个内存拷贝函数!
1.通过以上的分析,发现应用程序通过read,write,ioctl来访问硬件设备,它们都要经过两次的数据拷贝,一次是用户空间和内核空间的数据拷贝,另外一次是内核空间和硬件之
间的数据拷贝,如果设备拷贝的数据量比较小,那么read,write,ioctl的两次数据拷贝的过程对系统的影响几乎可以忽略不计,如果设备的数据量非常大,例如显卡(独立),
LCD屏幕(显存共享主存),摄像头,声卡这类设备涉及的数据量比较庞大,如果还是用read,write,ioctl进行访问设备数据,无形对系统的性能影响非常大。
2.用户访问设备,最终其实涉及的用户和硬件,而read,write,ioctl本身会牵扯到内核,所以这些函数涉及2次的数据拷贝,用户要直接去访问硬件设备,只需要将硬件
设备的物理地址信息映射到用户的虚拟地址空间即可,一旦完毕,不会在牵扯到内核空间,以后用户直接访问用户的虚拟地址就是在访问设备硬件,由2次的数据拷贝的
转换为一次的数据拷贝。
目的:将硬件物理地址映射到用户虚拟地址空间,由2次数据拷贝变成1次数据拷贝!
用户空间3G虚拟内存区域的划分:
高地址开始:
栈区 ↓
MMAP内存映射区(相当于内核中的动态内存映射区) ↓
堆区 ↑
BSS段区
DATA段区
TEXT段区
在内核中如何描述一个进程:
内核中,无论描述进程还是线程都用task_struct 描述。在Linux系统里面,是没有线程这个概念的, Linux的线程是模仿windows的,windows的进程和线程
有本质上的区别,Linux下没有区别,仅限的区别在于访问的地址空间不一样。mm_struct描述每个进程的3G用户空间
MMAP内存映射区作用:
让用户程序直接访问设备内存,在要求高性能的应用当中比较常用。应用程序使用的动态库映射到这个区域;
应用程序调用mmap,将设备物理地址和这个区域的虚拟内存进行映射;
mmap映射内存必须是页面大小的整数倍(也就是字节/4K)
结论:linux系统通过mmap来实现将物理地址映射到用户3G的MMAP内存映射区上的虚拟内存上!
mmap系统调用的过程:
void *addr;
addr = mmap(0,0x1000, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
参数1:如果是0,内核帮找一块内存区域,起始地址是返回值addr
参数2:虚拟内存的大小,0x1000一页(如果申请的内存空间没有对齐,内核会帮我们对齐,会经过一次if/else判断,浪费开销)
参数3:权限
参数4:权限
参数5:fd,将该文件映射到该区域
参数6:映射的偏移量
1.应用程序调用mmap首先调用C库的mmap
2.C库的mmap保存mmap的系统调用号到R7中,然后调用svc触发软中断异常(陷入内核空间)
3.内核启动时,已经初始化好了异常向量表,触发软中断,跳转到软中断的异常向量表的入口地址vector_swi.
4.根据R7保存的系统调用号,以它索引,在内核的系统调用表找到对应的函数sys_mmap,然后调用内核实现的sys_mmap
5.sys_mmap内核会做两件事:
1.首先在当前进程的MMAP内存映射区中找一块空闲的虚拟内存区域;
2.一旦找到以后,利用struct vm_area_strcut结构创建一个对象来描述这块空闲的虚拟内存区域;
6.sys_mmap最终调用底层驱动的mmap,然后将描述空闲虚拟内存区域的对象指针传递给底层驱动的mmap函数使用;
7.底层驱动的mmap根据传递过来的虚拟内存区域的信息获取用户要映射的虚拟地址,再根据某些函数建立用户虚拟地址和物理地址的映射关系
8.一旦建立映射,mmap函数返回,返回值保存着这块空闲内存区域的起始地址,以后用户在用户空间就可以为所欲为了!
内核利用struct vm_area_strcut描述找的虚拟内存区域
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; //虚拟内存区域标志
unsigned long vm_pgoff;//偏移量
};
struct file_operations :
int (*mmap)(struct file *filp,
struct vm_area_struct *vma) ;
vma:内核帮忙找的空闲的内存区域,描述内存区域的信息
9.驱动mmap利用一下函数建立映射(用户虚拟地址和物理地
址)
int remap_pfn_range(struct vm_area_struct*vma,
unsigned long addr,
unsigned long pfn,
unsigned size,pgprot_t prot);
vma: 用户虚拟内存区域指针
addr: 用户虚拟内存起始地址->vma->vm_start
pfn: 要映射的物理地址所在页帧号,可以通过物理地址>>12得到
size: 待映射的内存区域的大小
prot: vma的保护属性vma->vm_page_prot
功能:建立已知的用户虚拟内存和已知的物理地址之间的映射关系;
注意:利用这个函数进行地址映射的时候,不管是物理地址还是用户虚拟地址都要求是页的整数倍!
1页=4K=0x1000
0xe0200080这个GPIO寄存器地址不是页的整数倍!
通过芯片手册可知GPIO使用的地址空间范围:
0xE0200000 ~ 0xE02FFFFF
映射时指定的物理地址应该是:0xE0200000(页的整数倍)
访问0xe0200080:用户虚拟地址 + 0x80
访问0xe0200084:用户虚拟地址+ 0x84
注意:一个物理地址同时可以映射到内核的虚拟地址上,还可以映射到用户的虚拟地址上!
访问0xe0200080:用户虚拟地址 + 0x80
访问0xe0200084:用户虚拟地址+ 0x84
一个物理地址同时可以映射到内核的虚拟地址上,还可以映射到用户的虚拟地址上!
当访问的文件比较小时,可以用read、write,
文件比较大时,用mmap