善用搜索

虚拟内存相关原理教程

内存管理

1.虚拟内存

众所周知:单片机是没有操作系统的,所以单片机的CPU是直接操作内存的物理地址(physical address,PA)

在这种情况下,程序内存中是无法同时运行多个程序。例如第一个程序在10位置的位置存放立一个数值,当运行第二个程序时,会擦掉第一个程序在10位置上存储的数值。因此,单片机是无法同时运行多个程序的。
但我们用的电脑几乎都可以同时运行多个个程序,这是如何实现的呢。

由单片机的示例可知,两个程序都使用了绝对物理地址,那么这就是我们需要解决的问题。

我们可以使用虚拟寻址的方法解决,即让操作系统为每个进程分配独立的一套虚拟地址,每个程序都有,互不干涉。但每个进程都不能直接操作物理内存地址。那么虚拟地址如何与物理地址相对应呢,这就需要操作系统出手了

操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来

如上图,程序访问虚拟地址的时候,由操作系统将其转换为不同的物理地址,这样不同的程序运行时,写入的物理地址是不同的,这样就不会产生冲突了。

这就引出了两种地址的概念

操作系统引入虚拟内存,进程持有的虚拟地址会通过CPU芯片中的内存管理单元(MMU)1 的映射关系

那么操作系统是如何管理虚拟地址与物理地址之间的关系呢?

2.内存分段

程序是由若干逻辑分段组成的,如可有代码分段,数据分段,栈段,堆段组成。不同的段是有不同的属性的,所以就用分段(segmentation)的形式把这些段分离出来。

分段机制下,虚拟地址和物理地址是如何映射的?

分段机制下的虚拟地址由两部分组成,段选择子段内偏移量

我们知道了虚拟地址是通过段表与物理地址进行映射的,分段机制会将程序的虚拟地址分成4个段,每个段在段表中有一个项,在这一项中找到段的基地址再加上段内偏移量,这样就得到段的物理地址了。

假如我们要访问段 2 中偏移量为3000 的虚拟地址,我们可以计算出物理地址为 3000+3000 = 6000 ,那么物理地址 3000~6000 就是段 3 对应的物理地址范围。

分段的方法虽然很好,但是也有两个缺点:

我们接下来讨论为什么会有这两个问题。
产生内存碎片的的原因
我们来看个例子。假设有 1G 的物理内存,用户执行了多个程序,其中

这个时候如果我们关闭浏览器,则空闲内存还有 1024 - 512 - 256 = 256 MB。

如果这个 256 MB 内存不是连续的 被分成两个128 MB 内存,这样就会导致无法打开一个 200 MB 内存的程序。

这里的内存碎片主要有两部分

针对以上两种内存碎片,解决方式有所不同。

解决外部内存碎片的方式j就是内存交换

我们可以把音乐程序占用256 MB 内存写到硬盘上,然后再从硬盘上加载到内存中,但我们不能加载回原来的位置,而是加载到 512 MB 内存的后面,这样就可以空出 258 MB 内存 ,这样就满足了 200 MB 内存的要求。

在Linux系统中,这个内存交换空间就是我们常(其实不常)看到的swap 空间,这块空间是从硬盘中划分出来的,专门用于内存交换。

但是这就引出了第二个问题:内存交换效率低

因为硬盘的访问速度要比内存慢多了

3.内存分页

分段的好处就是能够产生连续的内存空间,但是会出现内存碎片和内存交换速率低的问题

要解决这些问题,那么就要想出能少出现一些内存碎片的方法。另外当需要进行内存交换是让需要交换写入或从磁盘装载的数据更少一点,这样就解决问题了 。这个办法就是内存分页
内存分页是吧整个虚拟和物理内存空间切成一段段固定尺寸的大小

这样一个连续并且尺寸固定的内存空间,我们叫。在Linux下,每一页的大小为 4 KB。
虚拟地址与物理地址之间通过页表2进行映射,如下图

页表实际上存储在CPU的内存管理单元(MMU)
中,于是CPU可以通过MMU,找出实际要访问的物理内存地址。

而当进程访问的虚拟内存空间在页表中查询不到时,系统会产生一个缺页异常,j进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程运行。

分页是怎么解决分段的内存碎片、内存交换效率低的问题?
由于内存空间都是预先划分好的,也就不会像分段会产生间隙非常小的内存,这正是分段会产生内存碎片的原因。**而采用分页那么释放内存都是以页为单位释放的,就不会产生无法给进程使用的小内存。

如果内存不够用了,那么操作系统会将其他正在运行的进程的【最近没有使用内存页面】给释放掉,也就是暂时写在硬盘上,成为换出空间(swap up)。一旦需要的时候,在加载进来,称为换入。所以,一次性写入磁盘的也只有少数的一个页或者几个页,不会花太多时间,内存交换的效率就此相对比较高

进一步讲,分页的方式使得我们在加载程序的时候,不需要一次性都把程序加载到物理内存中。我们完全可以在虚拟内存的页和物理内存的页进行映射后,并不需要真的把页加载到物理内存中,而只是在需要的时候将需要的虚拟内存中的数据或指令加载到物理内存中

分页机制下,虚拟地址和物理地址是如何映射的?

在分页的机制下,虚拟地址分为两部分,页号页内偏移。页号作为页表的索引,页表包含虚拟内存页每页所在的物理内存的基地址,这个基地址与页内偏移的组合就形成了物理内存的地址,见下图

总结一下,对于每个内存地址的转换,有三个步骤:

但是这种分页在实际操作中会有缺陷的。

简单的分页有什么缺陷呢

有空间上的缺陷

当操作系统运行非常多的的进程时,那就意味着页表会非常的大。

至于为什么会非常大呢,我就不写了,嘿嘿嘿

这时就需要一个更高端的方式来解决简单的分页带来的问题了

多级页表

要解决上面的问题,就需要采取多级页表的解决方案

假设有100多万个【页表项】的单级页表再分页,将页表(一级页表)分为1024个页表(二级页表),每个表(二级页表)中包含1024个页表项,形成二级分页

如下图所示:

这是你就有可能问了,整这么·多的表那所占的空间不就更大了吗

其实不是这样的,我们应该换一个角度去看问题,记得计算机组成原理里面无处不在的局部性原理么?

每个进程都有 4GB 的虚拟地址空间,而显然对于大多数程序来说,其使用到的空间远未达到 4GB,因为会存在部分对应的页表项都是空的,根本没有分配,对于已分配的页表项,如果存在最近一定时间未访问的页表,在物理内存紧张的情况下,操作系统会将页面换出到硬盘,也就是说不会占用物理内存。

如果使用了二级分页,一级页表就可以覆盖整个 4GB 虚拟地址空间,但如果某个一级页表的页表项没有被用到,也就不需要创建这个页表项对应的二级页表了,即可以在需要时才创建二级页表。
那么为什么不分级的页表就做不到这样节约内存呢?我们从页表的性质来看,保存在内存中的页表承担的职责是将虚拟地址翻译成物理地址。假如虚拟地址在页表中找不到对应的页表项,计算机系统就不能工作了。所以页表一定要覆盖全部虚拟地址空间,不分级的页表就需要有 100 多万个页表项来映射,而二级分页则只需要 1024 个页表项(此时一级页表覆盖到了全部虚拟地址空间,二级页表在需要时创建)。

我们把二级分页再推广到多级页表,就会发现页表占用的内存空间更少了,这一切都要归功于对局部性原理的充分应用。

TLB

多级页表虽然解决了空间上的问题,但是虚拟地址到物理地址的转换就多了几道工序,那么所花费的时间就更长了

程序是有局限性的,即在某一段时间内,整个程序的执行仅限于程序中的某一部分。相应的,执行所访问的存储空间页局限于某个内存区域。


我们就可以利用这一特性,把最常访问的几个表项存储到访问速度更快的硬件,于是计算机科学家们,就在 CPU 芯片中,加入了一个专门存放程序最常访问的页表项的 Cache,这个Cache 就是 TLB (Translation lookaside buffer),通常称为页表缓存,转址旁路缓存,快表等。

在cpu 芯片里面,封装了内存管理的单元芯片,它用来完成地址转换和与TLB的交互。
在CPU 寻址时,会先查找TLB ,如果没找到,在查找常规表。

  1. CPU上的芯片叫做内存管理单元(memory management unit,MMU) 的专用硬件,利用存放在主存中的查询表来动态翻译虚拟地址,该表的内存由操作系统管理 ↩︎
  2. 页表将虚拟页映射到物理页。每次地址翻译硬件将一个虚拟地址装转换为物理地址时,都会读取页表。操作系统负责维护页表的内容,以及在磁盘与DRAM之间来回传页 ↩︎
发表评论
退出移动版