目录

一、引言
想象一下,你是一位大厨,正在一个空间非常有限的厨房(这就是电脑的内存)里准备一场盛大的宴席。
你的操作台(内存)很小,只能同时放下切菜的案板、炒菜的锅和几个备用的碗碟。现在,客人的菜单来了,足足有50道菜!
你发现,操作台根本不可能同时放下50道菜的所有食材和厨具。如果把所有东西都堆上来,你连转身的地方都没有,根本没法工作。这就像电脑同时打开太多程序(浏览器、Word、音乐、游戏),所有东西都想挤进内存里,但内存大小是有限的,会直接“爆满”卡死。
但你很聪明!做了这样一件事:
只放当前用的:操作台上只留下正在做的那道菜所需要的食材和工具。比如现在炒宫保鸡丁,那就只放鸡丁、花生、酱料和炒锅。其他菜的食材(比如做红烧鱼要的鱼,做甜点要的面粉)先统统放到旁边巨大的仓库(这就是电脑的硬盘)里存着。等到你需要的时候,再去仓库里拿。
你的整个宴席菜单,就好像电脑运行的所有程序需要的全部数据,它非常大。
小小的操作台就是物理内存,空间有限,但速度极快。
旁边的大仓库就是硬盘,空间巨大,但来回取东西比较慢。
恭喜你!掌握了虚拟内存管理技术的精髓。
二、虚拟内存
2.1 引入虚拟内存的原因
为了有效的管理内存,减少出错。计算机系统中,CPU和内存是所有进程共享的。共享就意味着风险。一般情况下,我们电脑上都不仅仅只打开一个进程,而是很多个。如果一个进程不小心写入了另一个进程的内存,那么被写入的这个进程就有可能产生意想不到的结果。
2.2 虚拟内存的基本概念
虚拟内存是计算机系统对内存管理的一种技术,通过硬件和操作系统协作,将物理内存与磁盘存储空间结合(操作台与仓库的结合),为应用程序提供比实际物理内存更大的地址空间(一个小小的操作台即可搞定50道菜)。一个游戏十几个G,我们电脑的内存只有4个G,但是依旧可以跑得起来,原因就在于系统不是一下子全把这十几个G的数据加载到内存的,它是先加载一部分,后续需要的数据再从磁盘中加载进来,同时那些变得不常用的数据会从内存了拿走,使得内存始终有空间,不至于爆满。
2.3 虚拟内存的作用
1) 内存隔离与保护:每个程序拥有独立的虚拟地址空间,避免程序间直接访问彼此内存。以我自己的情况为例,平时在写代码的时候经常能遇到这样一个报错—— Segmentation fault(核心已转储),就是程序出现野指针了,指向了不属于自己的内存空间。
2) 简化编程模型:虚拟内存让每个进程以为整个内存资源都是自己的,无需关心物理内存的实际分配情况。这样一来,程序员在编程的时候就不用再去考虑不同进程的内存分配问题。
下面是虚拟内存模型。

上图中的0x……就是虚拟地址,CPU在执行进程时,要把虚拟地址转换为物理地址(物理内存地址)才能够找到想要的数据。在虚拟地址转换为物理地址的过程中,我们的主角——页表,起到了关键作用。
三、页表
3.1 页表的基本概念
页表是操作系统内存管理中的核心数据结构,用于实现虚拟地址到物理地址的转换。它由操作系统的内存管理单元MMU来维护。
3.2 虚拟地址到物理地址转换的基本模型

通过虚拟地址定位到页表中的页表条目(填充颜色部分),在32位系统下一个页表条目式4字节,页表条目里的内容就是对应的物理地址,通过这个物理地址就可以找到相应的数据,然后传回给CPU来处理。当然,在虚拟地址转换到物理地址的过程中可没那么简单,还有不少细节,但是可以通过以上的模型来建立对虚拟地址到物理地址转换的基本认知,细节我们后面再慢慢填充。
四、单级页表
4.1 CR3寄存器
CR3寄存器,存储当前进程的页目录基地址(Page Directory Base Address,PDBR),也叫做页表基址寄存器。在单级页表中,没有页目录,所以CR3寄存器指向的是页表的基地址。
4.2 虚拟地址的格式

32位系统下(本文基于32位系统),虚拟地址的高12位(页号)表示的是页表中页表条目的偏移量,因为页表是连续的,所以可以将页表理解为数组,页号理解为数组的下标。低20位 ,页内偏移表示的是数据在物理页内的偏移。
4.3 基于单级页表的地址转换

CR3寄存器保存页表的起始地址,结合上虚拟地址中的页号便可以定位到对应的页表条目,页表条目里保存的是数据所在物理页的起始地址,通过这个起始地址找到对应的物理页后,结合页内偏移即可定位到数据的具体地址。
4.4 单级页表的不足
1张页表4KB,1个页表条目4B,那么1张页表就有1024个页表条目。
1个页表条目可以映射1张物理页,也就是1个页表条目可以映射4KB的物理内存。
综上,1张页表可以映射1024张物理页,即1张页表可以映射 1024 * 4KB(4MB)的物理内存。
32位系统下,内存大小为4GB。
那么,就需要1024张页表,才可以完全映射4GB内存。
总共,页表的开销就需要1024 * 4KB(4MB)的连续内存。
但是,随着系统的运行,很难找到这么一大片的连续空间。这还不是最要命的,更要命的是,每个进程都需要有自己独立的页表,系统上一般又不止跑一个进程,而是很多个,所以就更难找到这么多块连续的4MB内存空间了——这就是单级页表的不足之处了。
单级页表的缺陷,演化出了多级页表!
五、二级页表
5.1 二级页表介绍
常见的多级页表有二级页表和四级页表,除此之外没再见过更多级的页表了,毕竟小编的见识有限,但是更多级的页表应不应该引入我们待会可以简单分析一下。

结合上图分析,二级页表在单级页表的基础上,增加了一个目录结构,每一个目录项存的是页表的起始地址。除此之外,虚拟地址的划分也更细致了。
5.2 基于二级页表的地址转换
以5.1节中的图来说明。第一步,通过CR3寄存器找到页目录的起始地址,然后结合一级页号(也就是页目录项的偏移量)来找到页目录项。第二步,页目录项中保存了相应页表的起始地址,通过该地址可以找到对应的页表,然后结合二级页号(也就是页表条目在页表中的偏移量)找到对应的页表条目。第三步,页表条目中保存了对应物理页的起始地址,通过该地址找到对应的物理页,然后结合页内偏移(数据在物理页内的偏移量)定位到要找的数据,然后传回给CPU。
5.3 二级页表如何解决单级页表的不足
结合5.1节中的图。
1张页目录(4KB)有1024个页目录项,1个页目录项4B,可以映射1张页表(4KB)。
1张页表(4KB)可以映射4MB的物理内存(前面分析过了)。
综上,1张页目录(4KB)可以映射4GB的物理内存,也就是需要1024张页表。
在程序运行过程中,只有页目录常驻内存,页表可以需要的时候再从磁盘换入。按需置换页面的这种做法的理论依据就是程序的局部性原理。我们知道,局部性原理包括两方面,第一:时间局部性,如果某个数据项被访问,那么在不久的将来它很可能会被再次访问。第二:空间局部性,如果某个数据项被访问,那么与它相邻的数据项在不久的将来很可能会被访问。所以,没有必要程序一启动就加载所有的数据。不同于单级页表,一来上就要申请全部页表(这样才能保证页表是连续的),一共4MB的连续内存。进而从单级页表需要4MB的连续内存到二级页表的4KB,是一个可行的优化。这样,单级页表的不足就被解决了。
5.4 虚拟地址格式分析

至于一级页号、二级页号和页内偏移的含义,前面已经说过了。
因为只需要1张页目录即可映射4GB的物理内存,所以10个比特位(2^10)刚好可以表示所有页目录项的偏移量,所以一级页号只需要10位足矣。
1张页表中,有1024个页表项,也仅需10个比特位。
剩下的12为,完全可以表示物理页内的任意偏移量。
5.5 多级页表性能分析
二级页表的虚拟地址到物理地址的转换,第一步,需要先找到页目录并找到也目录项;第二步,找到页表并找到对应的页表条目;第三步,找到物理页,定位数据。
可见,页表的级数越多,查询的步骤越多。对CPU来说等待的时间就越长,效率就越低。所以,并不是级数越多越好。
为了提升虚拟地址到物理地址的转换效率,引入了快表(TLB)。
六、快表TLB
首先简单介绍内存管理单元,其次再引出快表。
6.1 内存管理单元MMU
内存管理单元(MMU)是计算机硬件中的一个关键组件,主要负责处理中央处理器(CPU)的内存访问请求。它的核心功能包括虚拟地址到物理地址的转换、内存保护以及缓存控制。
上面不管是基于单级页表虚拟地址到物理地址的转换,还是基于二级页表的地址转换,都是由内存管理单元MMU来控制的。
6.2 快表的作用
快表TLB(Translation Lookaside Buffer),本质上就是MMU的一个高速缓存,用于加速虚拟地址到物理地址的转换。它存储最近使用的页表条目(PTE),避免每次地址转换都访问主存中的页表,从而减少内存访问延迟。

当CPU发出虚拟地址时,硬件首先检查TLB中是否存在对应的物理地址。若命中(TLB hit),直接获取物理地址;若未命中(TLB miss),需访问页表并更新TLB。
七、MMU对内存的保护——权限位
回顾前面的内容,我们知道,一张物理页4KB,一个页表条目4字节(32位)。
那么4GB的内存可以分为4GB / 4KB = 2^20张物理页,那么页表条目中只需要20个比特位即可完全表示所有物理页的起始地址。于是就有了如下图设计的页表条目。

只需要高20位来表示物理页的起始地址,剩下的12为用来作为权限位。下面介绍几个常用的权限位。
- 1)P:对应的物理内存页是否在内存中。1表示在内存中,0表示不在。如果不在,就会触发缺页中断,把对应的数据换入内存。
- 2)RW:表示该进程对该内存页具有的读写权限。1表示具有读写权限,0表示具有只读权限。
- 3)US:值为0表示该物理内存只有内核才能访问,值为1表示用户空间的进程也可以访问。
- 4)PWT:值为1表示CPU的cache中数据发生修改后,采用全写的方式进行同步,为0表示采用写回的方式进行同步。
- 5)PCD:表示对应的物理内存页是否可以缓存在CPU的cache中,1表示不可以,0表示可以。
- 6)A:表示对应的物理内存最近是否被访问过。1表示被访问过,0表示没有。CPU的内存管理单元MMU会经常检查该位置,来判断对应的物理内存页是否活跃,作为换入换出的依据。
这里简单解释一下换入和换出。换入,缺页中断,将磁盘上的物理页换入内存;但是内存空间很有限,所以在换入的时候,MMU会选择那些不活跃的物理内存页作为牺牲页,将它们替换掉,然后那些不活跃的物理内存页,如果有被修改,那么就拷贝回磁盘,否则,丢弃,这个就是换出。
通过以上的这些权限位,如果某个进程不具有访问该物理内存的权限,那么在从虚拟地址到物理地址转换的时候,就被系统拦截了,最常见的就是Segmentation fault。所以,虚拟内存技术就起到了保护内存的作用。
八、缺页中断
当程序尝试访问的页面不在物理内存中时,就会触发缺页中断。进程从用户态切换到内核态,将缺页中断交给操作系统处理,将所需的页面从磁盘调入内存。当缺页中断处理返回时,系统重新启动导致缺页中断的指令,该命令重新发出导致缺页中断的虚拟地址,然而这时,所需的页面已经存在于内存中了,程序就可以继续正常执行了。
结语
第一次写这篇博客的时候,条理很模糊,也有很多地方没讲清楚。于是今天,花了一天时间上上下下修改了一遍。从当初的2千多字到现在的5千字,希望对你有帮助。
推荐阅读:https://www.cnblogs.com/binlovetech/p/17571929.html
感谢支持~
231

被折叠的 条评论
为什么被折叠?



