虚拟内存
虚拟内存提供了三个功能:
- 将主存看作是磁盘的高速缓存,根据需要在主存和磁盘之间传送数据。
- 为每一个进程提供了一个一致的地址空间。
- 保护每一个进程的地址空间不被其他进程破坏。
物理内存和虚拟内存
计算机的主存可以看作是M个连续的字节数组,每一个字节用一个唯一的物理地址来标志。上图左边的物理寻址方式下,cpu生成一条物理地址,通过内存总线发送到主存,将该物理地址上的数据发送给cpu。虚拟寻址方式下cpu生成一条虚拟地址,通过MMU进行地址翻译得到对应的物理地址,将翻译后的物理地址上的数据发送给cpu。
物理地址空间 描述的是物理地址的大小,也就是计算机主存的大小,如果主存有M个字节,那么相应的物理地址空间就是:{0, 1, 2, ..., M-2, M-1}。
n位的虚拟地址空间 \(N=2^n\) :{0, 1, 2, ..., N-2, N-1},就是进程可用的地址空间,也是程序员可操作的内存地址,现代系统通常支持32位和64位的虚拟地址空间。
物理内存(主存)可以看作是磁盘的高速缓存。物理内存和虚拟内存都被划分为大小为P字节的页,称为物理页(PP)和虚拟页(VP),磁盘也被划分为固定大小的块(等同于页),因此只要页面匹配成功后虚拟地址的页内偏移量可以直接用在物理地址和磁盘地址中。
程序启动前虚拟内存没有和任何物理内存或磁盘关联,当程序启动后,有些虚拟内存页会和磁盘块相互关联,有些虚拟内存页会和物理内存页相互关联。当cpu生成一条虚拟地址时,如果该虚拟地址所属的虚拟页恰好和某个物理内存页相关联(即页命中),则直接将虚拟地址翻译为物理地址然后从物理地址取数据,否则就需要将该虚拟地址所属的虚拟页相关联的磁盘块换入到主存中,然后再从主存中取数据。
页命中
为了快速方便的进行地址翻译,在物理内存中有一张页表(每个进程各有一张),页表存储了和虚拟页同等数量的页表条目(PTE),每一个页表条目中要么是空指针,要么是一个指向物理页的地址,要么是一个指向磁盘中的地址。当需要翻译某一个虚拟地址时,先根据此虚拟地址所属的虚拟页从页表中找到对应的页表条目,如果该页表条目指向了一个物理页的地址,就意味着页命中,再从该物理页中定位到相应的物理地址,取出数据送到cpu。例如上图中从虚拟地址找到PTE2,再找到PP1,PP1中就缓存了VP2的数据。
缺页
上图中从虚拟地址中找到PTE3,但是PTE3中不是指向物理页,此即缺页。此时需要从磁盘中将对应的页数据读到物理内存中,并更新页表,然后重新翻译虚拟地址。因为物理内存中没有多余的空间存放新加进来的页,所以需要换出一个页,此处将VP4换出了,如果这一页中有数据发生改变则还需要写入到磁盘。
分配页
通常C语言中的malloc函数就是通过操作虚拟内存在磁盘上分配一块空间,然后更新页表的某个PTE让其指向磁盘的地址。如图中更新了PTE5。当操作这块新申请的内存时将会发生上面的缺页和页命中的过程。
PTE上还可以很方便的设置相应的标志位来控制进程对它指向的那页物理地址的访问权限。
PTE缓存
假设一个32位地址空间、页面大小为4KB,一个页表条目PTE占用4字节,那么一个页表就有 \(2^{32} \div 4K=1M\) 个PTE,所以页表的大小就是4MB。内存中需要缓存这4MB的页表并且每次翻译一个虚拟地址都需要从4MB的页表中查找,这不仅浪费内存也浪费时间。
最朴素的想法就是在mmu和内存之间增加一个高速缓存,将每次查找到的PTE缓存起来。
现在的mmu中通常包含了一个快表(TLB),TLB中的条目其实就是一个PTE的缓存,快表条目使用多路组相联来组织。mmu收到一个虚拟地址会先在TLB中查找,找到后直接翻译为物理地址,没有找到再去查找页表并将找到的一个PTE缓存到TLB中。
多级页表
除了TLB,还可以使用一种层次结构的多级页表来降低内存占用和加速地址翻译。还是假设一个32位地址空间、页面大小为4KB,一个页表条目PTE占用4字节,那么一个页表就有 \(2^{32} \div 4K=1M\) 个PTE,所以页表的大小就是4MB。
上图是一个二级页表的结构,其中有一张一级页表,一级页表有1024个PTE,每一个一级页表PTE要么为空要么为指向一个二级页表的地址,有1024个二级页表,一个二级页表中有1024个PTE,每一个二级页表PTE要么为空要么与一个虚拟内存页相关联。只有一级页表和经常使用的二级页表才需要常驻内存。此时的虚拟地址中包含了一个一级页表的索引号和二级页表的索引号,先在一级页表中找到对应的PTE再去二级页表中找到对应的PTE。

浙公网安备 33010602011771号