虚拟内存介绍

虚拟内存是现代操作系统中内存管理的关键技术,它为每个进程提供独立的、连续的地址空间,实现在物理内存有限的情况下运行大型程序。通过内存分段和分页,系统解决了内存碎片和效率问题。分页机制中,多级页表解决了地址空间映射的存储需求,64位系统通常采用四级页表。此外,TLB缓存加速了地址转换过程。段页式内存管理结合了分段和分页的优点,提供了一种有效的内存组织方式。
摘要由CSDN通过智能技术生成

虚拟内存简介

  1. 虚拟内存是计算机系统内存管理的一种技术。

  2. 它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。与没有使用虚拟内存技术的系统相比,使用这种技术的系统使得大型程序的编写变得更容易,对真正的物理内存(例如RAM)的使用也更有效率。

  3. 对虚拟内存的定义是基于对地址空间的重定义的,即把地址空间定义为「连续的虚拟内存地址」,以借此「欺骗」程序,使它们以为自己正在使用一大块的「连续」地址。

  4. 也就是说虚拟内存能提供一大块连续的地址空间,对程序来说它是连续的,完整的,实际上虚拟内存是映射在多个物理内存碎片上,还有部分映射到了外部磁盘存储器上。虚拟内存有以下两个优点:

    1. 虚拟内存地址空间是连续的,没有碎片

    2. 虚拟内存的最大空间就是cup的最大寻址空间,不受内存大小的限制,能提供比内存更大的地址空间

  5. 当每个进程创建的时候,内核会为每个进程分配虚拟内存,这个时候数据和代码还在磁盘上,当运行到对应的程序时,进程去寻找页表,如果发现页表中地址没有存放在物理内存上,而是在磁盘上,于是发生缺页异常,于是将磁盘上的数据拷贝到物理内存中并更新页表,下次再访问该虚拟地址时就能命中了。

虚拟内存由来

  1. 在计算机发展的早期,CPU是直接操作物理内存(Physical Memory)的(单片机的 CPU 是直接操作内存的「物理地址」)。
    在这里插入图片描述
    直接操作物理内存有以下一些问题:

    1. 不同的程序的地址空间很难完全互相隔离。如果一个程序修改了另一个程序的内存空间,可能会导致程序崩溃,安全性低。
    2. 可用内存大小受到实际物理内存大小的限制,如果一个程序使用的内存超过了物理内存大小就会崩溃。
  2. 为了解决这些问题,人们引入了虚拟内存(Virtual Memory)。

在这里插入图片描述

  1. 每一个进程都有独立的虚拟地址空间,不同进程的虚拟地址空间互相不干扰,提高了安全性。在每个进程看来,就像它自己独享了整个内存似的。当物理内存不够是,可以将一部分不常使用的内存块换出(Swap-out)到磁盘中,下次使用时再换入到内存中(Swap-in),这样能允许程序使用超过实际物理内存大小的空间。应用虚拟内存之后,程序中看到的地址都是虚拟地址。在访存时,虚拟地址首先会被转换成物理地址,然后再访问实际物理内存。
  2. 虚拟地址到物理地址的映射由操作系统和硬件完成。操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。
    在这里插入图片描述
  3. 如果程序要访问虚拟地址的时候,由操作系统转换成不同的物理地址,这样不同的进程运行的时候,写入的是不同的物理地址,这样就不会冲突了。(程序所使用的内存地址叫做虚拟内存地址(Virtual Memory Address)``实际存在硬件里面的空间地址叫物理内存地址(Physical Memory Address)。)
  4. 操作系统引入了虚拟内存,进程持有的虚拟地址会通过 CPU 芯片中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存
    在这里插入图片描述

OS如何管理虚拟内存和物理内存

主要有两种方式,分别是内存分段(较早提出)和内存分页。

内存分段

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

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

在这里插入图片描述
3. 段选择子就保存在段寄存器里面。段选择因子里面最重要的是段号,用作段表的索引。段表里面保存的是这个段的基地址、段的界限和特权等级等。
4. 虚拟地址中的段内偏移量应该位于 0 和段界限之间,如果段内偏移量是合法的,就将段基地址加上段内偏移量得到物理内存地址。

举例

如果要访问段 3 中偏移量 500 的虚拟地址,我们可以计算出物理地址为,段 3 基地址 7000 + 偏移量 500 = 7500。

在这里插入图片描述

缺陷

内存碎片的问题内存交换的效率低的问题

内存碎片

在这里插入图片描述

  1. 外部内存碎片,也就是产生了多个不连续的小物理内存(如上图),导致新的程序无法被装载;
  2. 内部内存碎片,程序所有的内存都被装载到了物理内存,但是这个程序有部分的内存可能并不是很常使用,这也会导致内存的浪费;

解决外部内存碎片的问题就是内存交换

可以把音乐程序占用的那 256MB 内存写到硬盘上,然后再从硬盘上读回来到内存里。不过再读回的时候,不能装载回原来的位置,而是紧紧跟着那已经被占用了的 512MB 内存后面。这样就能空缺出连续的 256MB 空间,于是新的 200MB 程序就可以装载进来。

内存交换空间,在 Linux 系统里,也就是常看到的 Swap 分区,这块空间是从硬盘划分出来的,用于内存与硬盘的空间交换。

因为硬盘的访问速度要比内存慢太多了,每一次内存交换,都需要把一大段连续的内存数据写到硬盘上。所以,如果内存交换的时候,交换的是一个占内存空间很大的程序,这样整个机器都会显得卡顿。

内存分页

  1. 为了解决内存分段的内存碎片和内存交换效率低的问题,就出现了内存分页。

  2. 分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。这样一个连续并且尺寸固定的内存空间,叫做页(Page)。在 Linux 下,每一页的大小为 4KB。

  3. 虚拟地址与物理地址之间通过页表来映射:
    在这里插入图片描述

  4. 页是存储在内存里,由 CPU 的内存管理单元 (MMU) 负责映射转换的工作,这样CPU 就可以直接通过 MMU,找出要实际要访问的物理内存地址。

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

解决分段的内存碎片、内存交换效率低

由于内存空间都是预先划分好的,也就不会像分段会产生间隙非常小的内存,采用了分页,那么释放的内存都是以页为单位释放的,也就不会产生无法给进程使用的小内存。

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

在这里插入图片描述
分页的方式使得加载程序的时候,不再需要一次性都把程序加载到物理内存中。完全可以在进行虚拟内存和物理内存的页之间的映射之后,并不真的把页加载到物理内存里,而是只有在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存里面去。

映射

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

  1. 把虚拟内存地址,切分成页号和偏移量;
  2. 根据页号,从页表里面,查询对应的物理页号;
  3. 直接拿物理页号,加上前面的偏移量,就得到了物理内存地址。

多级页表

  1. 在 32 位的环境下,虚拟地址空间共有 4GB(232 byets) ,如果一个页的大小是 4KB(212 bytes),那么就需要大约 100 万 (220) 个页,每个「页表项」(页表项N)需要 4 个bytes大小来存储,那么整个 4GB 空间的映射就需要有 4MB(222 bytes)的内存来存储页表。

  2. 但每个进程都是有自己的虚拟地址空间的,也就说都有自己的页表。那么,100 个进程的话,就需要 400MB 的内存来存储页表,这是非常大的内存了,更别说 64 位的环境了。

  3. 要解决上面的问题,就需要采用的是一种叫作多级页表(Multi-Level Page Table)的解决方案。

  4. 把这个 100 多万个「页表项」的单级页表再分页,一级页表分为 1024 个二级页表),二级页表中包含 1024 个「页表项」,形成二级分页。
    在这里插入图片描述

  5. 如果使用了二级分页,一级页表就可以覆盖整个 4GB 虚拟地址空间,但如果某个一级页表的页表项没有被用到,也就不需要创建这个页表项对应的二级页表了,即可以在需要时才创建二级页表。

  6. 页表一定要覆盖全部虚拟地址空间,不分级的页表就需要有 100 多万个页表项来映射,而二级分页则只需要 1024 个页表项(此时一级页表覆盖到了全部虚拟地址空间,二级页表在需要时创建)。

  7. 做个简单的计算,假设只有 20% 的一级页表项被用到了,且整个 4GB 空间的映射就需要有 4MB(2**22 bytes)的内存来存储页表。那么页表占用的内存空间就只有 4KB(4mb被分为了1024个后,一级页表项就变成了记录二级页表项的元信息,只有4kb) + 20% * 4MB(二级页表,覆盖4G的内存)= 0.804MB,这对比单级页表的 4MB 是一个巨大的节约。

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

64位机

对于 64 位的系统,就变成了四级目录,分别是:

  1. 全局页目录项 PGD(Page Global Directory);
  2. 上层页目录项 PUD(Page Upper Directory);
  3. 中间页目录项 PMD(Page Middle Directory);
  4. 页表项 PTE(Page Table Entry);

在这里插入图片描述

TLB

  1. 多级页表虽然解决了空间上的问题,但是虚拟地址到物理地址的转换就多了几道转换的工序,这显然就降低了这俩地址转换的速度,也就是带来了时间上的开销。
  2. 程序是有局部性的,即在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域。于是计算机科学家们,就在 CPU 芯片中,加入了一个专门存放程序最常访问的页表项的 Cache,这个 Cache 就是 TLB(Translation Lookaside Buffer) ,通常称为页表缓存。
    在这里插入图片描述

有了 TLB 后,那么 CPU 在寻址时,会先查 TLB,如果没找到,才会继续查常规的页表。TLB 的命中率其实是很高的,因为程序最常访问的页就那么几个。

段页式内存管理

内存分段和内存分页并不是对立的,它们是可以组合起来在同一个系统中使用的,那么组合起来后,通常称为段页式内存管理。

段页式内存管理实现的方式:

  1. 先将程序划分为多个有逻辑意义的段,也就是前面提到的分段机制;
  2. 接着再把每个段划分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页;

这样,地址结构就由段号、段内页号和页内位移三部分组成。

在这里插入图片描述
用于段页式地址变换的数据结构是每一个程序一张段表,每个段又建立一张页表,段表中的地址是页表的起始地址,而页表中的地址则为某页的物理页号:
在这里插入图片描述

  1. 第一次访问段表,得到页表起始地址;
  2. 第二次访问页表,得到物理页号;
  3. 第三次将物理页号与页内位移组合,得到物理地址。

总结

  1. 为了在多进程环境下,使得进程之间的内存地址不受影响,相互隔离,于是操作系统就为每个进程独立分配一套虚拟地址空间,每个程序只关心自己的虚拟地址就可以,实际上大家的虚拟地址都是一样的,但分布到物理地址内存是不一样的。

  2. 作为程序,也不用关心物理地址的事情。每个进程都有自己的虚拟空间,而物理内存只有一个,所以当启用了大量的进程,物理内存必然会很紧张,于是操作系统会通过内存交换技术,把不常使用的内存暂时存放到硬盘(换出),在需要的时候再装载回物理内存(换入)。

  3. 那既然有了虚拟地址空间,那必然要把虚拟地址「映射」到物理地址,这个事情通常由操作系统来维护。

  4. Linux 系统主要采用了分页管理,但是由于 Intel 处理器的发展史,Linux 系统无法避免分段管理。于是 Linux 就把所有段的基地址设为 0,也就意味着所有程序的地址空间都是线性地址空间(虚拟地址),相当于屏蔽了 CPU 逻辑地址的概念,所以段只被用于访问控制和内存保护。另外,Linxu 系统中虚拟空间分布可分为用户态和内核态两部分,其中用户态的分布:代码段、全局变量、BSS、函数栈、堆内存、映射区。

参考文献

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Generalzy

文章对您有帮助,倍感荣幸

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值