4_虚存与 MIPS 32 内存管理

虚存与 MIPS 32 内存管理

目录

  • 虚存概念初步

    • 设计背景

    • 基本管理流程

  • MIPS 32 的内存管理

虚存设计背景

你在编程的时候不用考虑如何去和别人程序合作使用内存, 实际上因为虚存的引入, 它是由软硬件协同实现的一个概念, 给你造成了这么一个理想的编程的假象. 虚存的作用有三个, 如下

  • 将物理内存作为磁盘数据的 "缓存"

    • 因为进程的地址空间可能会超过物理内存的大小
  • 简化程序的内存管理

    • 多个进程同时运行

    • 每个都有自己的地址空间

  • 存储保护功能

    • 某进程不能访问其它进程的空间

    • 一个进程的不同的空间区域具有不同的访问权限

将物理内存作为磁盘数据的快速 "缓存"

我们首先要有一个概念, 你写程序的时候, 你程序里的任何一个地址, 包括你 CPU 处理的时候, 就是 CPU 这个内部, 流水线内部里任何的一个地址, 除了真的涉及到虚实转换的一个模块之外, 所有的 CPU 内部的地址, 都叫作虚拟地址, 或者叫程序地址.

虚拟地址需要进行转换, 才能转换成物理地址. 物理地址就是, 你真正读写内存条的时候, 总线走的那个地址. 其它的, 程序内部的, CPU 内部的都是虚拟地址. 所以肯定有一个模块, 在那边做了一个虚实的一个转换.

  • 地址转换 (Address Translation) : 一般由处理器硬件通过页表 (page table) 将虚拟地址 (程序地址) 转换为物理地址.

为什么物理内存可以作为磁盘数据的快速缓存呢 ?

CPU 里面所有的地址都是虚的, 它通过一个页表, 软硬件结合来把虚拟地址转换成物理地址. 那么这个虚实转换到物理地址, 它转换出来可能有这么几种情况.

首先就是说这个地址是有效的, 那么有的时候可以发现, 你这个地址所对应的那个物理地址, 已经实实在在的分配给你了, 确实在这个内存里, 物理内存里面确实有, 就成功.

还有一种情况是, 因为你虚拟内存, 或者多个进程虚拟内存加起来, 要比物理内存大得多, 那有的时候我这个不可能同时满足这么多的内存需求, 所以从大量的虚实转换, 转换完了之后的结果, 它并不是一个有效的物理地址, 而只是告诉你, 你的地址是有效的, 但是当前物理内存还不够用, 或者说你是第一次用, 我还没有在物理内存里面分配这么个空间给你, 你所要的数据还在磁盘上面.

你看, 有的转换时直接转换成一个物理内存里面的一个有效地址, 有的转换这个箭头指向磁盘, 或者闪存.

数据来自硬盘上面, 那么这个硬盘上面的空间, 在 win 下叫 page file, linux 下叫 SWAP 分区.

一些程序, 多个程序在那边同时跑, 它需要的大量的虚拟空间, 这些虚拟地址, 并没有放到真正的物理内存里面, 毕竟放不下, 这些空间, 大部分放在了磁盘上面了. 你查这个表的时候就会发现, 如果已经被分配到物理内存里面去, 那么虚实转换就很成功, 直接物理地址就出来了. 那么对于大量的数据来说, 物理内存不够用, 我就放到磁盘上面去, 这个时候需要把磁盘上面的数据装载到物理内存里面去, 相当于重新分配一下, 然后再把 page table 里面这个表给它填上, 这时候才能完成一个虚实地址的转换.

那如果物理内存被填满了的话, 新的一块地址转载进去之后, 势必会有一块旧的地址被替换出来. 那么这里就有一个替换的策略. 替换出来的数据放到什么地方呢, 那就放到你的磁盘上, 刚才提到过了, page file, 或者说交换空间里面去.

页缺失 (Page fault)

  • 页表项纪录某虚存地址 (以页为单位) 是否在物理内存中

当然了, 虚存空间的分配是以页为单位的, 页最小一般来说是 4k, 往上大了还有, 甚至最大还有 1G 的页面, 有的 x86 的服务器的处理器有 1G 的页面. 通常是 4k, 一次分配 4k 给你, page table 也是这么组织的.

  • 如果不是, 就触发 "页缺失" 异常, 由异常处理代码将所需数据从外部存储 (硬盘) 读入内存

这样就查一查, 你虚存地址是不是在物理内存中, 如果不是就触发页缺失这个异常, 由异常处理代码, 将所需的数据从外部存储, 比如硬盘或者 u 盘, 读入内存, 然后注意页缺失异常是同步异常, 因为是由一条返回指令引发的, 是程序无意触发的.

页缺失本身不是错, 你的访问本身是完全合法的, 但是因为我空间有限, 合法的这个地址目前还没有被装载到物理内存里面去, 那么在这种情况下, 我要通过操作系统的异常处理代码, 将这些数据从外部存储里面读入到内存, 以页的方式读入到内存, 然后让你这条触发页缺失的指令重新执行一遍.

这个方式确保, 在物理空间有限的情况下, 而虚存空间因为有多个程序一块运行, 它的虚拟空间是无限的, 所以我们可以用相当大的硬盘外部存储, 把它一部分虚拟化位物理空间的一部分, 这样一旦发生缺失的话, 就进行交换. 这样就可以把经常使用的那些数据放到内存里面去, 这样来回切换.

所以在程序的角度看来, 你好像拥有了一大片的连续的空间, 同时你又不需要去跟别的进程去考虑共享这个问题.

简化程序的内存管理

那么第二个作用就是简化程序的内存管理

  • 虚拟空间与物理空间都划分成相同大小的内存块 (称为页)

  • 每个进程都有其私有的虚拟地址空间

  • 进程虚拟空间映射到物理空间

如图, 有两个进程, 它们都有自己的虚拟地址空间, 以页为单位从 0 到 n-1 这个页. 然后, 通过地址转换, 我把进程 1 里面的 vp1 vp2, 包括进程 2 里面的每个 vp1 vp2 这些, 都映射到你的物理空间里面去, 是以页为单位进行映射.

那么这个时候需要注意, 因为是以页为单位进行映射, 所以说你进程 1 里面, 这个页面 vp1 映射到某个页, 下面的另外一个进程 2 的 vp1 可以映射到另外一个物理页面去, 二者完全没有关系. 就是我有一个虚实转换在那边做这个事情, 相当于你们每个进程看自己空间都是感觉是连续的, 但是映射到物理空间的时候, 完全是以页为单位独立分配, 相互之间没有任何关系.

但这边有个特殊的例子, 我第一个进程的 vp2, 跟第二个进程的 vp1 是映射到了同一个页面去, 这是怎么回事呢 ?

因为在默认的情况下, 大家各自映射各自的, 相互之间完全没有任何共享的物理空间, 那么有一种方式, 通过内存映射之类的方式, 是可以把一个物理页面同时映射到两个不同的进程的不同的虚拟页面去, 这样实际就造成两个进程可以共享这个物理数据.

但是这样问题就来了, 如果你这个页面是只读就还好, 但如果你这个页面是可写的, 怎么去保护它就又是个问题了, 但这个超出我们的范围了, 有兴趣自己去查.

存储保护功能

  • 一般由硬件来完成访问控制

  • 页表项也记录该页的访问控制信息

就是告诉你这个页面是可读可写, 还是你用户态模式下可以访问

虚实地址转换 : 命中

刚才一直说地址转换, 现在我们看看地址转换是个什么一个过程

虚实地址转换 : 页缺失

虚存地址转换

页表结构


使用快表 (TLB) 方式来加速

把常用的页表项放到 CPU 芯片里面去

MIPS 32 内存管理

  • MIPS 处理器在执行单元和访存部件 (包括缓存) 间设计了存储管理单元 (MMU)

    • 基于 TLB 实现的

    • 基本功能是将数据/指令的虚拟地址送到 TLB 转换成物理地址

你所有跟 tlb 相关的指令, 操作的都是 jtlb, itlb 和 dtlb 是完全不可见的, 这两个完全由硬件来控制.

  • MMU 结构

    包括三个地址转换缓冲 : 一个全相联联合 TLB (JTLB), 一个指令 TLB(ITLB), 和一个数据 TLB (DTLB)

  • MMU 根据所处流水部件的不同区分是指令虚拟地址还是数据虚拟地址

    只有在用户空间 (Kuseg)/Kseg2 的虚拟地址使用 TLB 转换, 其他段的虚拟地址直接转换成物理地址

工作模式

  • 用户模式 (User Mode)

用户模式经常用在应用程序

  • 核心模式 (Kernel Mode)

核心模式主要用于例外处理和特权操作系统功能 (包括 CP0 管理和 I/O 设备访问)

  • 调试模式 (Debug Mode)*

调试模式主要用于软件调试, 经常用于软件开发中

地址转换依赖于处理器的操作模式. 在不同模式下, 虚拟存储空间的划分是不同的, 在将虚拟地址转换成物理地址时必须考虑到操作模式.

处于核心模式的软件可以访问整个地址空间, 也可以访问所有的 CP0 寄存器.

用户模式仅能访问 0x0000_0000 到 0x7FFF_FFFF 的虚拟地址空间, 不能进行 CP0 的操作.

在用户模式, 0x8000_0000 到 0xFFFF_FFFF 的虚拟地址空间是无效的, 对这个地址空间的访问会产生异常.

  • Unmapped 段

    • 非映射段不使用 TLB 进行虚拟地址到物理地址的转换

    • 系统启动后首先进入非映射的存储段是非常重要的

      • 因为这时候 TLB 还没初始化
    • Kseg1 总是不缓存的 (Uncached)

    • Kseg0 是否缓存是由 CP0 的寄存器 Config 中的 k0 字段决定的

  • Mapped 段

    • 映射段使用 TLB 进行虚地址和物理地址的转换

    • 映射转换是以页为单位的

    • 包含是否能缓存 (Cacheable) 以及页的保护特性

用户态

最终在用户态看来, 用户态就提供了单一的连续 2G bytes 的虚拟地址空间.

系统通过 TLB 实现访问, 在转换前, 虚拟地址加上 8 位 ASID (进程标识符) 产生一个唯一的虚拟地址

TLB 结构 (以 MIPS 4KC 处理器为例)

它实际上是全相联的 JTLB, 映射 32 位虚拟地址到相应的物理地址, 什么叫全相联, 就是一个虚拟地址进来, 它可以同时的跟多个的 JTLB 的表项进行匹配, 哪个匹配到了, 那就同时进行, 这样会很快. 但是反过来讲 JTLB 的项也不会太多, 一般几项到十几项, 否则速度就慢了.

默认情况下, MIPS 4KC 包含 16 对奇偶项, 每项所对应的页的大小从 4k 到 16m bytes, 最小 4k, 最大 16m. 那么奇偶项是什么意思呢, 就是说一个 vpn, 一个虚拟地址进来, 它可以匹配到两个连续的物理的页表项. 一个虚拟地址进来之后, 它给你以你下一个虚页所装载的虚拟地址都给你映射出来了. 那么它隐含的前提是, 你的程序连续访问了.

它分成这么微观上的几项

pagemask 用于指定你当前页表的大小, 24 到 13, 这种情况下最大的页是 4k, 最小的页是 16m, 带数组为数

vpn 就是你把 vpn 的 page offset 去掉之后, 剩下就是这个 vpn2, 注意它最小的页是 4kb, 所以理论上来说, 它应该 vpn to 31 到 32, 因为 0 到 11 实际就是 4kb 的页内 page offset, 因为它是 1 比 2, 所以说可以再向上走一位, 所以说第 12 位被去掉了, 因为我是一个虚页对应两个实页. 那反过来讲, 实页位数页号是需要 31 到 12 的, 因为从 0 到 11 是最小的 4kb 的, 那个页的 page offset 不需要.

G 提到过了, 是不是全局的, 如果是全局的话 ASID 这一项就不起作用了, 它就表名你访问的这个虚页是属于那个进程的

v0 v1 就看它是不是有效

d0 d1 就看它是不是可写

c0 到 c2 表示你所对应的空间是物理地址空间, 是 cacheabled 还是 uncacheabled

  • 操作 TLB

    软件通过 TLBWI 和 TLBWR 指令填充 JTLB 的某个入口

    在填充 TLB 之前, 首先用 CP0 指令更新和 TLB 相关的 CP0 寄存器, 然后用相关 CP0 寄存器的内容填充 TLB 入口的不同字段

  • ITLB & DTLB

    硬件管理, 对软件透明

    如果虚拟地址在 ITLB 或者 DTLB 中缺失, 则由 JTLB 在下一个周期处理; 后者如果成功, 则将相应表项复制到 ITLB (或者 DTLB)

虚拟地址到物理地址的详细转换流程

如果发生了 TLB 缺失异常, 软件从内存页表中读取相关表项重填 TLB.

TLB 缺失和多匹配

  • 如果 TLB 项没有匹配 (TLB 缺失), 异常发生

    软件从驻留在内存中的页表重填 TLB

    软件可以写一个选定的 TLB 项或者利用硬件机制写一个随机的 项里 (CP0 的 Random 寄存器选择哪个 TLB 项用于 TLBWR)

  • 通过 TLB 写比较机制来保证多 TLB 项匹配不能发生

    在 TLB 写操作中, 要写的 VPN2 域与 TLB 中的所有其它项比较.

    如果有一个匹配发生, 处理器引发一个机器检查例外 (machine-check excepition)

TLB 操作指令*