typora/note/操作系统/内存虚拟化.md
2024-12-12 10:48:55 +08:00

13 KiB
Raw Blame History

1. 虚拟内存

  • 透明:对应用程序透明,程序觉得自己获得的都是私有的物理内存
  • 效率:
    • 时间上,分配虚拟内存要快,不要拖慢应用程序的执行速度
    • 空间上,不要占用太多内存来支持虚拟化
  • 保护:操作系统应该要对进程的内存进行保护,免受其他进程影响,操作系统也不应该受其他进程影响
    • 进程和进程之间隔离

2. 栈内存和堆内存

  • 栈内存:申请和释放操作由编译器来管理,常用于函数内部定义的变量
  • 堆内存:长期存在,函数结束后不会被释放掉,所有的申请和释放操作都显示完成

3. 地址转换

  • 基于硬件的地址转换
  • hardware-based address translation
  • 将应用程序中的内存引用重定向到实际物理内存地址
  • 操作系统提供内存的管理操作,记录被占用和空闲的位置

4. 基于硬件的重定位

  • 每个CPU含有两个寄存器
    • 基址寄存器:存储物理内存的开始地址
    • 限界寄存器:存储物理内存最大分配的范围,保护内存访问不会越界
  • 进程访问超过限界寄存器的地址或负数的地址,会被终止
  • 真实物理地址 = 基址寄存器地址 + 虚拟内存地址
  • 缺点:
    • 内存空间碎片
    • 需要空闲链表来表示哪个区块是空闲的

进程内存重定位

CPU负责内存地址转换的部分也称为MMU内存管理单元Memory Management Unit

5. 分段

出现原因:

  • 基于硬件的地址转换太过于浪费内存,在堆和栈之间的内存,并没有被完全使用,但是依然占用了物理内存
  • 剩下的内存如果连续范围不够大,就会造成进程无法运行
  • 原理:给内存中的每个段都分配一对基址+限界寄存器
    • 代码段
    • 栈段
    • 堆段
  • 分段的机制使操作系统可以将不同的段放在物理内存不同位置
image-20230725204859460

5.1 怎么确定当前地址访问哪个段

  • 地址二进制前两位,用来表示代码段,栈段和堆段
    • 用两位表示3个短空间浪费
  • 还可以用地址的产生方式来表示
    • 如果地址由程序计数器产生(即它是指令获取),那么地址在代码段
    • 如果基于栈或基址指针,它一定在栈段。其他地址则在堆段

5.2 段共享

  • 硬件和操作系统在地址中加标识,可以用来共享段
  • 代码段共享,每个进程都可以访问执行代码段中指令
  • 物理内存中的一个段可以映射到多个虚拟地址空间

5.3 分段机制缺陷

  • 空间割裂,造成内存浪费
    • 每个段之间的空闲空间发利用
  • 空间大小不一致,内存无法分配
    • 进程需要一个24KB的空间但是分段后没有一个足够大的内存空间来支撑分配
image-20230726113528993

上面展示了该问题的一个例子。在这个例子中,全部可用空闲空间是 20 字节,但被切成 两个 10 字节大小的碎片,导致一个 15 字节的分配请求失败

5.4 空闲空间管理

空闲空间列表:将所有空闲的空间统计到一起,方便统计和分配空间

image-20230726114634791
  • 分割与合并

    • 分割此时如果需要分配大小为1个的空间将会从前后两个free空间中分配1个假设分割20后的空闲空间则空闲列表的第二个空闲空间将会变成21

      image-20230726114850318
    • 合并此时如果中间使用的10个空间释放了则空闲列表会进行合并为一个节点而不是变成三个节点

      • 合并前三个连续的10个大小的空闲空间只能分配小于等于10空间给其他进程
      • 合并后,可以分配更大的空间给其他进程
      image-20230726115025892

5.5. 多种空闲空间分配方式

  • 最优匹配:先遍历整个空闲列表,找到和请求大小一样或更大的空闲块,然后返回这组候选者中最小的一块

    • 优点:减少空间浪费
    • 缺点:遍历列表有性能损失较多
  • 最差匹配:与最优匹配相反,尝试找最大的空闲块,分割并满足用户需求后,将剩余的块(很大)加入空闲列表。最差匹配尝试在空闲列表中保留较大的块,而不是向最优匹配那样可能剩下很多难以利用的小块

    • 缺点:遍历列表性能损耗,剩很多小碎块无法分配
  • 首次匹配:遍历列表,将遇到的第一个满足需求的空闲块返回给用户,分割后剩下的空间加入空闲列表

    • 优点:性能损耗较少,通常不需要遍历整个列表
    • 缺点:列表开头会有很多小碎空间
  • 下次查找:用指针记录上一次分配的位置,本次分配继续沿着上一次的指针向后遍历

    • 优点:避免了遍历查找,碎空间分布在列表各个位置
  • 分离空闲列表:如果某个应用程序经常申请一种(或几种)大小的内存空间,就用一个独立的列表,只

    管理这样大小的对象。其他大小的请求交给更通用的内存分配程序

    • 优点:解决碎片问题
  • 伙伴系统:空闲空间首先从概念上被看成大小为 2N 的大空间。当有一个内存分配请求时,空闲空间被递归地一分为二,直到刚好可以满足请求的大小(再一分为二就无法满足)

    • 优点合并速度快当有一个8kb的块释放后会检查伙伴8kb是否释放如果释放就进行合并操作然后递归向上合并
  • 平衡二叉树、伸展树和偏序树

6. 分页

分段固有缺陷:内存碎片化

  • 虚拟内存中将空间分割成固定长度的分片,分割成固定大小的一个单元,每个单元称为一个页
  • 把物理内存看成是定长槽块的阵列,叫作页帧

image-20230726180817486

  • 虚拟内存页4个每个大小16字节
  • 物理内存页帧8个每个大小也是16字节
  • 如果操作系统希望将 64 字节的小地址空间放到 8 页的物理地址空间中,它只要找到 4 个空闲页。操作系统保存了一个所有空闲页的空闲列表free list只需要从这个列表中拿出 4 个空闲页。在这个例子 里,操作系统将地址空间的虚拟页 0 放在物理页帧 3虚拟页 1 放在物理页帧 7虚拟页 2放在物理页帧 5虚拟页 3 放在物理页帧 2。页帧 1、4、6 目前是空闲的。

6.1 页表

  • 操作系统为每个进程保存一个数据结构,记录地址空间的每个虚拟页放在物理内存中的位置,称为页表page table
  • 主要作用是为地址空间的每个虚拟页面保存地址转换address translation让我们知道每个页在物理内存中的位置
  • 对应的例子有四个条目(虚拟页 0→物理帧 3VP 1→PF 7VP 2→PF 5VP 3→PF 2

6.2 虚拟地址到物理地址转换

首先将虚拟地址分成两个组件虚拟页面号virtual page numberVPN和页内的偏移量offset

  • 进程的虚拟地址空间是64字节2^6需要6位来表示
  • 虚拟页的数量为4个2^2需要2位来表示
  • 每个页的大小为16字节2^4需要4位来表示
  • 最高位Va5最低位Va0
  • 于是例子中的虚拟地址表示如下前两位为页号后4位为每个页面内的偏移量
image-20230726182834419
  • 虚拟地址21,二进制为010101则页号位第一页偏移量为5也就是第一页第5个字节处
  • 根据页表找出属于第7个物理页因此可以通过替换虚拟页号为物理页号进而找到物理地址
image-20230726183239264

6.3 页表存放内容

  • 假设页表是一个数组通过虚拟页号VPN检索到页表项PTN最终定位到物理页号PFN
  • 有效位valid bit通常用于指示特定地址转换是否有效。例如当一个程序开始运行时它的代码和堆在其地址空间的一端栈在另一端。所有未使用的中间空间都将被标记为无效invalid如果进程尝试访问这种内存就会陷入操作系统可能会导致该进程终止
  • 保护位protection bit表明页是否可以读取、写入或执行。以不允许的方式访问页会陷入操作系统
  • 存在位present bit表示该页是在物理存储器还是在磁盘上即它已被换出swapped out
  • 脏位dirty bit也很常见表明页面被带入内存后是否被修改过

image-20230726184526113

x86 架构的示例页表项[I09]。它包含一个存在位P确定是否允许写入该页面的读/写位R/W 确定用户模式进程是否可以访问该页面的用户/超级用户位U/S有几位PWT、PCD、PAT 和 G确定硬件缓存如何为这些页面工作一个访问位A和一个脏位D最后是页帧号PFN本身

6.4 芯片内的快速地址转换(地址转换旁路缓冲存储器 translation-lookaside buffer TLB

背景:页表的信息一般需要放在物理内存中,进行地址转换,需要在内存中查一次页表,进行一次内存读取,影响性能

  • 地址转换缓存address-translation cache对每次内存访问硬件先检查 TLB看看其中是否有期望的转换映射如果有就完成转换很快不用访问页表其中有全部的转换映射
  • 首先从虚拟地址中提取出页号VPN然后检查TLB中是否有对应页号的转换映射如果有则命中了TLB从映射中取出物理页号页帧号PFN原来虚拟地址中的偏移量组合形成期望的物理地址PA并访问内存
  • 如果没有命中TLB硬件就会访问页表找到对应的映射并将映射更新到TLB中然后重新执行指令
  • 连续访问同一个虚拟页中的数据只会在第一次TLB位命中后续都会命中如果TLB足够大缓存时间足够长则后续再次访问该虚拟页就都会命中TLB

6.5 更新TLB中内容

  • CISC 复杂指令集 x86 架构,由硬件进行更新
  • RISC 精简指令集,由操作系统进行更新

6.6 上下文切换TLB的变化

  • TLB中存储的映射关系只对当前进程生效对其他进程是没有意义的
  • 在上下文切换时操作系统通过某个特权寄存器存储当前进程的地址空间标识符asidaddress space identifier
  • 在TLB中增加asid字段标识每个映射对应的进程实现跨上下文切换的 TLB 共享

image-20230727111238640

6.7 优化较大的页表

32位的地址空间4KB的页和4字节的页表项一个地址空间中大约有一百万个虚拟页面2^32/2^12。乘以页表项的大小发现页表大小为 4MB通常每个进程都会持有一个页表。如果有上百个进程页表占用的内存空间就会打到数百兆

6.7.1 简单的解决方案:更大的页
  • 以 32 位地址空间为例,这次假设用 16KB2^14 的页。因此,会有 18 位的 VPN 加上 14 位的偏移量。假设每个页表项4字节的大小相同现在线性页表中有 2^18 个项,因此每个页表的总大小为 1MB页表缩到四分之一
6.7.2 混合方法:分段和分页
  • 分段依然是代码段,栈段和堆段,依然是用地址空间的前两位表示访问的是哪个段
  • 每个段都有一对寄存器
    • 基址寄存器:保存该段的页表的物理地址
    • 限界寄存器:页表的结尾(即它有多少有效页)
  • TLB未命中时用地址空间前两位确定访问哪个段然后利用基址寄存器存储的物理地址和虚拟页号结合形成页表项
6.7.3 多级页表
  • TODO 重新理解