目录

一、概述

二、虚拟内存的出现

三、虚拟内存发展和演进

3.1  Dynamic Relocation (base and bounds)

3.2 segmentation

3.3 paging

3.3.1 第一种优化——TLB

3.3.2 第二种优化——多级页表

四、参考


一、概述

OSTEP memory virtualization相关章节笔记,主要为了理清虚拟内存的为什么是现在这样,备忘。

二、虚拟内存的出现

  1. 起初,OS和一个程序分别放置在物理内存中,OS提供访问资源的接口。
  2. 由于机器价格昂贵,人们希望共享机器资源以节约成本,一个要求是计算机能运行多个程序,多道程序(multiprogramming)在这个时期出现,我们知道,进程等待I/O很耗时,如果在进程等待I/O时机将cpu切换出去运行另一个进程,避免了等待的同时也提高了cpu的使用率。
  3. 之后,随着对计算机更多的要求出现了time sharing,其针对多个交互式用户产生,基本原理是,每个用户运行一段时间,在这个时间内拥有计算机的全部资源,运行一段时间后进行switch,使其他进程运行。
  4. time sharing要实现对资源的完全控制,如在进程运行时是拥有整个物理内存的。实现time sharing的一种方法是运行一段时间后,将内存内容全部保存到磁盘上,当再次轮到自己,再将内存的内容从磁盘恢复到内存中。 这个实现方法简单,但是效率很低——保存整个内存的内容是非常耗时的,远远不如保存通用寄存器和PC那样快——这是不可接受的。因此希望再进程切换时不要将内存全部都保存到磁盘上而是保留再内存中。
  5. 当然4中的方法就做不到完全进程在其运行的过程中完全拥有内存了。做不到资源隔离的后果很严重,试想一个进程运行中修改了其他进程的数据,会造成很严重的后果!
  6. 当然,5中的保护问题可以通过标识访问权限的机制解决,但多个进程同时在物理空间中还会有加载和重定位等难题。对这个问题一个比较好的解决方法是引入地址空间(address space)的概念,地址空间是以进程的角度来看系统中的内存。在地址空间有code,heap,stack。在地址空间的基础上引入内存的虚拟化,如果进程对系统内存的角度(进程地址空间)是一致的,我们只需要建立物理地址空间和虚拟地址空间的映射就可以了,每个进程看到的地址都是虚拟的,运行中看到的是整个虚拟地址空间。进程对地址的视图一致,也使重定位得以简化。

三、虚拟内存发展和演进

首先要明确,程序的地址都是虚拟地址,我们要做的就是在保证在上述约束条件下,将虚拟地址转化为物理地址,需要考虑以下指标:

  • 透明:进程不感知其看到的内存是假的。
  • 效率:引入该机制对系统负担尽可能小,在可接受的范围内。
  • 保护:protection,进程间的资源隔

为了使虚拟地址转换物理地址更有效率,一般需要硬件的支持,这称为hardware-based address translation,简称address translation。当然,只靠硬件支持还是不够的,还需要OS的配合。

先来看一条指令有关于内存存取的操作:

128: movl 0x0(%ebx), %eax ;load 0+ebx into eax

流水线中相关的步骤是fetch和execution,针对上面的指令,fetch时要先将128这个虚拟地址翻译成对应的物理地址,excution时要将%ebx中的虚拟地址翻译成物理地址。

硬件提供地址翻译的部件称为MMU,位于CPU中。

3.1  Dynamic Relocation (base and bounds)

先来看一种最简单的硬件翻译的方式Dynamic Relocation ,该方法引入base和bound寄存器,分别代表物理地址的基址和允许访问物理地址的范围。

硬件通过下面的方式进行翻译:

  • PhyAddr = base + VirtAddr

如果PhyAddr的值超过了bound,那么硬件raise exception,产生protection fault

通过上面的流程总结一下硬件提供的功能:

MMU {

base

bound                            //提供base和bound寄存器

SetBase/SetBound       //提供设置base和bound寄存器的方法。必须在privileged mode下

RaiseExceptioin            //提供产生异常的能力,如protection fault

AddressTranlation        //提供将虚拟地址转换为物理地址的能力

}

OS需要配合硬件实现地址转换而实现的功能有:

  • 物理内存管理(Memory management):当一个进程运行时,要为其分配物理地址,OS要对系统的物理内存进行管理(freelist),如在进程初始化时分配一个物理地址。在进程结束时要回收分配的物理地址
  • 在进行context switch时要增加额外的流程:在目前的情况下要将base/bound save到PCB中,新进程根据PCB restore base/bound寄存器——每个进程的base/bound寄存器都是不同的,这样才能隔离资源
  • Exception handling:必须能够处理MMU RaiseException

下面的时序图很好的说明了这个过程:

3.2 segmentation

Dynamic  Relocation的方法可以满足2.1中7对应的要求,但是也有它的问题:程序可能只使用很少的空间,但是Dynamic Relocation每次需要分配的物理地址大小是整个进程地址空间大小,这显然会造成地址浪费——Dynamic Relocation会导致内部碎片(internal fragmentation)。

我们可以使用分段来解决这个问题。假设使用三个段(code, heap, stack)对应一个进程,那么可以根据实际使用的情况,大大减少实际分配的内存,实现的方法可以从base-and-bounds改进——可以实现多个base/bound。这时候需要区分地址属于哪个段,所以地址使用逻辑地址即段:偏移的形式使用下面的逻辑操作:

1 // get top 2 bits of 14-bit VA

2 Segment = (VirtualAddress & SEG_MASK) >> SEG_SHIFT

3 // now get offset

4 Offset = VirtualAddress & OFFSET_MASK

5 if (Offset >= Bounds[Segment])

6       RaiseException(PROTECTION_FAULT)

7 else

8      PhysAddr = Base[Segment] + Offset

9      Register = AccessMemory(PhysAddr)

分段的特性:、

  • 内存增长的方向不一样(upwards/downwards)
  • 可以为每个段增加R/W权限
  • code sharing (通过设置段属性read-only)、

由此可以推广增加段的个数(fine-grained),使用多个段管理内存需要更多的寄存器。

分段解决了Dynamic  Relocation内存管理内部碎片的问题,但同时也引入的新的问题

  1. 在context switch时,需要save/store段寄存器,段越多开销越大。
  2. 每个进程若干段大小不一致,一个是管理起来很复杂,另一个是可能会产生外部碎片。

3.3 paging

分段仍是一种粗粒度的内存管理方式,除了会产生外部碎片,当一个段很大但是很稀疏的时候,也会产生分配物理空间浪费的情况。那么我们考虑一种更细粒度的管理方式:管理固定大小的小块内存,称为分页。

将物理内存看做固定大小的小块(称为页)的数组,数组的下标称为PFN(page frame number),数组元素大小为一个page,每个物理地址向下面这样:

  • 虚拟地址转换为物理地址就是查找VPN->PFN的映射关系!

存储VPN<->PFN对应关系的表称为页表(page table),页表是以VPN为索引的页表项(page table entry)的数组。一个页表项包含PFN和指示标志。

PTE中一般包含几个关键位:

  • present bit  表项不在内存中
  • protect bit   没有置为产生保护异常,不允许访问
  • dirty bit    表项对应的页被修改
  • reference bit (a.k.a. accessed bit) 用于page replacement,页是否被访问过

启用分页访问内存的过程

VPN = (VirtualAddress & VPN_MASK) >> SHIFT           // Extract the VPN from the virtual address

PTEAddr = PTBR + (VPN * sizeof(PTE))                         // Form the address of the page-table entry (PTE)

PTE = AccessMemory(PTEAddr)                                    // Fetch the PTE

if (PTE.Valid == False)                                                    // Check if process can access the page

RaiseException(SEGMENTATION_FAULT)

else if (CanAccess(PTE.ProtectBits) == False)

RaiseException(PROTECTION_FAULT)

else     // Access is OK: form physical address and fetch it

offset = VirtualAddress & OFFSET_MASK

PhysAddr = (PTE.PFN << PFN_SHIFT) | offset

Register = AccessMemory(PhysAddr)

3.3.1 第一种优化——TLB

paging机制也面临自己的问题,第一个问题是访问访问慢,如上面的例子仍需要两次访问内存,为了解决这个问题引入TLB进行加速,TLB是页表的缓存,相当是全相连的cache,其结构示意如下:

VPN     |           PFN         |           other bits

TLB流程如下:

1 VPN = (VirtualAddress & VPN_MASK) >> SHIFT

2 (Success, TlbEntry) = TLB_Lookup(VPN)

3 if (Success == True) // TLB Hit

4        if (CanAccess(TlbEntry.ProtectBits) == True)

5                 Offset = VirtualAddress & OFFSET_MASK

6                 PhysAddr = (TlbEntry.PFN << SHIFT) | Offset

7                 Register = AccessMemory(PhysAddr)

8          else

9                 RaiseException(PROTECTION_FAULT)

10 else      // TLB Miss

11        RaiseException(TLB_MISS)

产生tlb miss后,可能会由硬件或者软件处理,软件处理要注意:

  • trap返回要重新执行产生tlb miss的指令而不是下一条。
  • 注意不要引入无限tlb miss

TLB加速了页表的访问,但同时TLB也引入了新问题:

  1. context switch 时要进行flush操作,因为它存储的映射关系只对本进程有效。这样开销很大,即下一个进程会产生tlb miss,解决办法是引入ASID避免刷新整个tlb
  2. cache replacement

3.3.2 第二种优化——多级页表

paging的第二个问题是页表占据的空间太大了,由于页表是基于进程的,假设在32-bit系统下,页大小为4K,那么有1M个页表项,每个页表项4byte,即每个页表占用4M内存,随着进程的增多占据空间增加,如果是64-bit,会占据更大的空间,这是不可接受的。

解决的方法包括以下几种:

  1. 大页,增加page的大小,那么PTE的数量自然会降低,从而减少页表项的大小。(大页的目的主要还是为了减少TLB miss)大页的使用场景还是收到限制,因为大页会产生内部碎片——很多程序只用物理内存中很少的一部分,会造成大量的浪费。
  2. Hybrid Approach: Paging and Segments
  3. 多级页表(multi-level page table) 多级页表的概念很简单:如果一个PTE不可用,就先不用为其分配内存,这样就节省了空间
    1. 只分配要使用的页表
    2. 每一级的页表放在一个页面中,那么内存管理变得更简单,也可以使表项的分配变得更灵活。(32-bit 系统10|10|12, 64-bit系统 9|9|9|9|12,假设页面大小是4K,想想为什么是这样?)
  4. Inverted Page Tables 不再是每个进程一个page table,只有一个page table管理所有的页面,每个entry指示使用这个page的进程以及和该物理页面进行映射的虚拟页面。

多级页表访问流程:

1 VPN = (VirtualAddress & VPN_MASK) >> SHIFT
2 (Success, TlbEntry) = TLB_Lookup(VPN)
3 if (Success == True) // TLB Hit
4       if (CanAccess(TlbEntry.ProtectBits) == True)
5               Offset = VirtualAddress & OFFSET_MASK
6               PhysAddr = (TlbEntry.PFN << SHIFT) | Offset
7               Register = AccessMemory(PhysAddr)
8 else
9               RaiseException(PROTECTION_FAULT)
10 else // TLB Miss
11        // first, get page directory entry
12        PDIndex = (VPN & PD_MASK) >> PD_SHIFT
13        PDEAddr = PDBR + (PDIndex * sizeof(PDE))
14        PDE = AccessMemory(PDEAddr)
15        if (PDE.Valid == False)
16              RaiseException(SEGMENTATION_FAULT)
17        else
18              // PDE is valid: now fetch PTE from page table
19              PTIndex = (VPN & PT_MASK) >> PT_SHIFT
20              PTEAddr = (PDE.PFN << SHIFT) + (PTIndex * sizeof(PTE))
21             PTE = AccessMemory(PTEAddr)
22             if (PTE.Valid == False)
23                     RaiseException(SEGMENTATION_FAULT)
24              else if (CanAccess(PTE.ProtectBits) == False)
25                     RaiseException(PROTECTION_FAULT)
26              else
27                     TLB_Insert(VPN, PTE.PFN, PTE.ProtectBits)
28                     RetryInstruction()

四、参考

【1】OSTEP

编程基础(五)—— 虚拟内存相关推荐

  1. 人工智能AI编程基础(五)

    TensorFlow中,如果要对一个参数进行训练,那么这个参数必须是一个变量类tf.Variable,tf.Variable类会在反向传播的时候进行梯度计算并保存梯度信息. # coding: utf ...

  2. Linux网络编程基础和一步一步学

    ·Linux网络编程 基础(一) ·Linux网络编程 基础(二) ·Linux网络编程 基础(三) ·Linux网络编程 基础(四) ·Linux网络编程 基础(五) ·Linux网络编程 基础(六 ...

  3. QT开发(五十)——QT串口编程基础

    QT开发(五十)--QT串口编程基础 一.QtSerialPort简介 1.串口通信基础 目前使用最广泛的串口为DB9接口,适用于较近距离的通信.一般小于10米.DB9接口有9个针脚. 串口通信的主要 ...

  4. 【零基础学Java】—网络编程(五十三)

    [零基础学Java]-网络编程(五十三) 一.软件结构 C/S结构:全称为Client/Server结构,是指客户端和服务器结构,常见的程序有QQ.迅雷等软件 B/S:全称为Browser/Serve ...

  5. 深度学习入门笔记(五):神经网络的编程基础

    欢迎关注WX公众号:[程序员管小亮] 专栏--深度学习入门笔记 声明 1)该文章整理自网上的大牛和机器学习专家无私奉献的资料,具体引用的资料请看参考文献. 2)本文仅供学术交流,非商用.所以每一部分具 ...

  6. PL/SQL编程基础(五):异常处理(EXCEPTION)

    PL/SQL编程基础(五):异常处理(EXCEPTION) 参考文章: (1)PL/SQL编程基础(五):异常处理(EXCEPTION) (2)https://www.cnblogs.com/thes ...

  7. 游戏编程基础(五)背景地图滚动显示

    在游戏过程中,背景地图需要跟着人物的移动而动态的滚动变换.C++游戏编程基础中介绍了2D游戏中常用的3种动态背景表现手法.其原理和实现技巧分析如下: 方法一:单一背景滚动         原理是:利用 ...

  8. WPF编程基础入门 ——— 第三章 布局(五)布局面板WrapPanel

    WPF布局--布局面板WrapPanel WPF--WrapPanel布局控件 WrapPanel实例--十个按钮 WPF--WrapPanel布局控件 WrapPanel(自动折行面板),允许任意多 ...

  9. 【C++】多线程与原子操作和无锁编程【五】

    [C++]多线程与原子操作和无锁编程[五] 1.何为原子操作 前面介绍了多线程间是通过互斥锁与条件变量来保证共享数据的同步的,互斥锁主要是针对过程加锁来实现对共享资源的排他性访问.很多时候,对共享资源 ...

  10. c语言职专试题及答案,中等职业学校计算机应用专业c语言编程基础科试卷及答案.doc...

    中等职业学校计算机应用专业c语言编程基础科试卷及答案.doc 中等职业学校计算机应用专业C语言编程基础科试卷及答案一.填空(共35分)1.Unix系统诞生于 年,是由 实验室的K和用汇编语言开发成功的 ...

最新文章

  1. Java JSON 之 Xml 转 JSON 字符串
  2. Learning to Rank:X-wise
  3. 两线怎么接三线插座图_一文搞懂电工配电二线制、三线制、四线制
  4. JavaScript实现heapsort堆排序算法(附完整源码)
  5. 前端知识区别和学习路线_个人收藏
  6. python文件操作总结
  7. 南科大计算机系实力a,五大竞赛学科A+高校排行榜发布!北大实力碾压,科大赶超清华...
  8. Spring MVC-表单(Form)标签-下拉框(Dropdown)示例(转载实践)
  9. java删除xml文件中尖括号之外的内容
  10. EXTRONICS推出IRFID500便携式UHF RFID读写器
  11. 动词ing基本用法_哪些动词后面只能接动名词背诵口诀
  12. 操作系统中涉及的各种调度算法
  13. webform(八)组合查询
  14. autojs刷网课之一、刷视频篇
  15. 半导体物理学——(一)半导体中的电子状态
  16. matlab 画短时平均幅度谱
  17. spring boot整合JDBC
  18. 基于PT2262/PT2272的4路遥控电路
  19. VueRouter导入
  20. 双网卡电脑的Internet连接共享

热门文章

  1. CSS3 Gradient 渐变
  2. javaee版eclipse导包出现未找到类问题
  3. 11款极酷Chrome浏览器插件推荐
  4. 【转】HTML标签大全
  5. java spring maven excel 导出
  6. iOS10 Xcode 8 中provisioning file 相关bug
  7. 系统架构技能之设计模式-单件模式
  8. [转]自定义UITableView各种函数
  9. Iocomp控件教程之LinearGauge--线性刻度尺控件
  10. 保持新投资技术先进性和保护既有投资的完美均衡 —— 成都地铁4号线二期PIS车地无线通信...