建站提交历史文章,原文写作时间 2023 年 2 月前后。

虚拟内存地址

整理自:为什么需要虚拟内存?_小林coding的博客-CSDN博客

虚拟地址

为什么需要虚拟地址?

  • 在做单片机时,因为没有操作系统的存在,我们通常直接访问物理内存地址Physical Memory Address),程序需要烧录到单片机中。如果在同一个单片机中,同时运行两个程序,那么这时就可能会出现各种错误。产生错误的原因就是为两个程序分配了包含相同的物理内存地址,两者的数据相互干预。
  • 现代计算机应用程序建立在操作系统之上,操作系统为我们完成了这部分冲突的解决,使得多道程序同时在操作系统上运行,而使得在应用程序开发中基本无需关心物理内存地址冲突的问题。
  • 解决这一问题的技术虚拟内存地址Virtual Memory Address)、以及由此衍生出的内存分段Segmentation)与内存分页Paging)技术,就是本章讲解的内容。需要注意的是,虚拟内存由内核而无需用户关注,本章讲解的均为原理。

虚拟地址空间

  • 虚拟内存映射表表存储于PCB中,每个进程都拥有一个虚拟内存映射表,不同进程的虚拟内存地址可以相同但物理内存地址通常不同。

  • 在操作文件与内存中得到的所有地址都是虚拟地址物理地址对于用户是不可见的,也是无需关心的。

  • MMUMemory Management Unit,通过获取来自进程虚拟内存映射表的信息,用于实现虚拟地址物理地址之间的映射,从而完成CPU的内存管理请求。

  • 虚拟内存映射表并不是直接给出物理内存地址,而是通过内存分段内存分页实现,将在后面介绍。

内存分段

内存分段原理

  • 内存分段是最早的虚拟内存地址技术,该虚拟地址由段选择因子段内偏移量组成。内存分段会创建一段连续的长度可变的连续虚拟内存。
  • CPU中的MMU获取虚拟地址,并得到段号段内偏移量段号将在段表中查找,获取段基地址base)与段界限bound),通过段基地址段内偏移量获取准确物理地址。物理地址=段基地址+段内偏移量(段内偏移量<段界限)物理地址=段基地址+段内偏移量(段内偏移量<段界限)
  • 分段机制会将进程的虚拟地址空间分为四个段:

内存分段缺陷

  • 由于内存分段大小可调节、长连续地址的性质,内存分段往往产生大量内存碎片且内存交换效率低。

例如:用户同时执行三个程序:游戏、浏览器、音乐。

  • 在此情况下,如果物理内存充足,新进程可以向高地址载入内存,内存碎片一直存在;如果如图内存紧张,会进行交换swap)操作:将最近最少使用的段换出swap out)到磁盘,换入等待使用的段,如果下次需要被换出的段可以重新换入
  • 由于内存与磁盘之间的交换IO操作速度极慢,而段的体积通常很大,内存分段原理造成的频繁交换操作很容易成为性能瓶颈。

内存分页

内存分页原理

  • 内存分段不同的是,内存分页Paging)具有固定的大小。Linux中页的大小固定为4KB内存分页下的虚拟地址由页号页内偏移量组成内存分页的固定大小减小了交换体积与内存碎片
  • CPU中的MMU获取虚拟地址,并得到页号页内偏移量页号将在页表中查找,通过物理页号页内偏移量计算准确物理地址。物理地址=物理页号×单页大小+页内偏移量(页内偏移量<单页大小)物理地址 = 物理页号 \times 单页大小 + 页内偏移量(页内偏移量 < 单页大小)

简单分页缺陷

  • 通常每个进程占用内存空间不受限制,直到将内存占满。页表需要为每个进程预留空间足够的大小,对于32bit操作系统,地址空间为4GB,页表一条记录需要占用4B,每个页指向4KB空间,因此需要2202^{20}条记录,合计4MB。如果同时有100个进程运行在操作系统上,那将占用1/10的内存资源,这是一个无法接受的内存占用。对于64bit操作系统,需要支持更多空间,这个问题更加突出。
  • 由此产生了多级页表,用于解决这个问题。

多级页表

  • 32bit系统为例,通常采用二级页表就能很好的解决上述问题。
  • 一级页表通过一级页号指向2102^{10}二级页号,由二级页号再指向物理内存。指向4GB物理内存需要2202^{20}二级页号记录,指向2202^{20}二级页号需要2102^{10}一级页号记录,可以发现合计4MB+4KB内存。然而,事实上物理内存不会被完全占用,对于多数进程其占用的内存远小于4GB,其一级页表多数是空的,而二级页表根据一级页表动态加载。一个二级页表的大小为4KB,一个进程通常加载不了几个二级页表。多个进程不可能同时占用4GB内存,这样一个进程的均摊页表占用为4KB~8KB
  • 64bit系统中,二级页表显然不够用,通常使用四级页表,分别为:
    • 全局页目录项(Page Global Directory
    • 上层页目录项(Page Upper Directory
    • 中间页目录项(Page Middle Directory
    • 页表项(Page Table Entry

页表缓存(快表)

  • 由于多级页表的存在,页表的层层访问自然减慢了访问效率,因此页表缓存TLBTranslation Lookaside Buffer转址旁路缓存,后称快表)被加入。
  • 频繁访问的页将被存入快表中。TLB是一个高速缓存(cache),位于CPUMMU近端。MMU查找物理内存地址时首先会在快表中查找,如未找到再在页表中查找。
  • 事实上,快表很大程度上提高了物理内存地址效率,因为常用的页表并不多。

段页式内存管理

  • 内存分段内存分页并不冲突。可以将进程分为多个有逻辑意义的端(如内存分段中的四段机制)指向连续的页表,再由页表指向物理内存地址

Linux 内存管理

  • Linux的内存管理可以认为是分页式存储,但不可避免的涉及了分段式存储,这与Intel处理器的发展有关。
  • 如今被广泛应用的X86处理器构架在Intel 80286使用的是分段式存储,由于不能满足需求,Intel 80386很快使用了分页式存储。但是Intel 80386实际使用的是段页式存储虚拟内存地址首先通过段表映射到页表,再由页表映射到物理内存地址
  • 内核的开发需要处理器的支持,Linux中主要使用分页式存储,由于历史原因,Linux不得不处理段表映射。
  • 事实上,在Linux中,每个段都是段基地址0段界限4GB32bit系统)的,这样实际上跳过了段机制。在Linux中,段机制只被用于访问控制与内存保护。
  • 栈空间从高地址向低地址占用,堆空间从低地址向高地址占用。