MMU(内存管理单元),它的作用
1.它负责虚拟地址到物理地址的转换
2.提供硬件机制的内存访问权限检查
没有启动或者没有MMU时,外设(包括物理内存)等所有部件使用的都是物理地址,cpu通过物理地址来访问外设(包括物理内存)。启动MMU后,CPU核心对外发出虚拟地址给MMU,MMU把虚拟地址转换为物理地址,最后使用物理地址读取实际设备
虚拟地址转换为物理地址的方法:
1.确定的数学公式进行转换
2.用表格存储虚拟地址对应的物理地址
tiny210使用第二种方法,并根据每次转换时查表的次数分为一级页表方式(以段的方式转换)和二级页表方式两种(以页的方式转换)
linux二级页表映射机制
如上图所示, 对于32位CPU,所拥有的地址线为32条,对应的虚拟内存地址为0~2^32-1也就是4G,对应于线性地址。与此对应,所有的物理内存被分为4k大小的块,对所有的块进行编号(在页表中存放)。
至此即可通过线性地址(虚拟内存地址)的高10位找到页目录、中10位找到页表(即块编号)、低12位找到对应的块的偏移值,进而与中10位找到的块起始地址一起即可确定这一内存地址对应的物理地址。
MMU内存映射机制,实现不同的进程均可以访问所有的用户空间,同时,不同的进程(由于页目录、页表不一样所以映射到的是不同的物理内存地址)又可以保存自己的私有数据。
Linux进程虚拟地址空间管理:
如图,一个task_struct结构体表示一个进程。每个进程都有自己唯一的mm_struct结构体来管理自己这一进程的虚拟地址空间。mm_struct结构体存储着自己的页目录,以及一个结构体链表mmap(链表中的所有结构体vm_area_struct瓜分了这一进程的虚拟内存地址,每一个结构体表示一个虚拟内存空间域)。系统中所有的mm_struct结构体都通过自身的mmlist域连接在一个双向链表中,该链表的首元素是init_mm内存描述符,它代表init进程的地址空间。
read/write和mmap函数:
用户空间的mmap()函数实现文件映射到进程地址空间,实现直接访问文件内容的功能。
用户层调用read/write函数,实际上在底层是调用copy_to_user/copy_from_user来实现而这两个函数的实现要通过数据复制来实现,而mmap函数因为建立了映射关系,可直接访问数据,省去了数据复制的开销。所以为提高效率可以用mmap替代read/write函数。
用户空间mmap的内核实现:
用户空间的mmap()会通过系统调用调用到内核的do_mmap()函数。
do_mmap()函数实现:
1.首先创建一个新的VMA并初始化,然后加入进程的虚拟地址空间里。
2.然后调用驱动的mmap函数建立上述用户空间的VMA和驱动空间的内存地址之间的联系(建立页表)
驱动的mmap建立虚拟地址和物理地址的映射:
建立vma和物理地址的映射的工作由remap_pfn_range来完成,原型如下:
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr,
unsigned long pfn, unsigned long size, pgprot_t prot);
/* vma
需要建立映射的VMA
virt_addr
需要建立映射的VMA的起始地址
pfn
页帧号, 对应虚拟地址应当被映射的物理地址. 这个页帧号简单地是物理地址右移 PAGE_SHIFT 位
size
需要建立映射的VMA的大小, 以字节.
prot
使用在 vma->vm_page_prot 中找到的值.*/
注:上述用户空间通过mmap实现文件映射的功能的实现得益于Linux内核中实现的底层操作函数。但是对于设备的映射,需要自己编写设备驱动来实现。
设备的映射机制实现的一种原理:
首先,在设备驱动中开辟一段空间(即内核空间),这一段虚拟内核空间会映射到一段设备内存空间中。
其次,获取上面的虚拟内核空间的物理空间,在进程空间建立一段虚拟内存空间,运用remap_pfn_range建立进程中虚拟内存空间和物理空间的映射。至此建立了两个映射,最终实现进程空间间接访问内核空间的设备。