第十四周(12.07-12.13):
一、学习目标
理解虚拟存储器的概念和作用
理解地址翻译的概念
理解存储器映射
掌握动态存储器分配的方法
理解垃圾收集的概念
了解C语言中与存储器有关的错误
二、学习资源
1. 教材:第九章《虚拟存储器》
虚拟存储器
为了更加有效地管理存储器并且少出错,现代系统提供了一种对主存的抽象概念,叫做虚拟存储器。
三个重要的能力
- 他将主存看成一个存储在磁盘上的地址空间的高速缓存,在主存中只保存可活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存
- 它为每个进程提供了一致的地址
- 它保护了每个进程的地址空间不被其他的进程破坏
为什么需要理解:
- 虚拟存储器是中心的
- 虚拟存储器是强大的
- 虚拟存储器是危险的
9.1物理和虚拟寻址
使用虚拟寻址时,CPU通过生成一个虚拟地址来访问主存,这个虚拟地址在被送到存储器之前先转换成适当的物理地址。
9.2地址空间
地址空间是一个非负整数地址的有序集合:
{0,1,2,...}
虚拟地址空间
{0,1,2,...,N-1}
物理地址空间
{0,1,2,...,M-1}
9.3虚拟存储器作为缓存的工具
在任意时刻,虚拟页面的集合都分为三个不相交的子集:
- 未分配的
- 缓存的
- 未缓存的
9.3.1DRAM缓存的组织结构
为了有助于清晰理解存储层次结构中的缓存概念,使用术语SRAM缓存来表示位于CPU和主存之前的L1,L2和L3高速缓存,并且用术语DRAM缓存来表示虚拟存储器系统的缓存,他在主存中缓存虚拟页。
9.3.2页表
由许多软硬件联合提供的,包括操作系统软件.MMU中的地址翻译硬件和一个存放在物理存储器中的就叫做页表。
9.3.4缺页
在虚拟存储器中,DRAM缓存不命中的叫做缺页。
9.3.5分配页面
p539图9-8
- 展示了当操作系统分配一个新的徐您存储器页时对我们示例表的影响。
- 通过在磁盘上创建空间并更新PTE5,使它指向磁盘上这个新创建的页面,从而分配VP5
又是局部性拯救了我们
统计缺页次数
- 可以领用Unix的getrusage函数监测缺页的数量(以及许多其他的信息)
9.4虚拟存储器作为存储器管理的工具
- 简化链接
- 简化加载
- 简化共享
- 简化存储器分配
9.5虚拟存储器作为存储器保护的工具
9.6地址翻译
形式来说,地址翻译是一个N元素的虚拟地址空间(VAS)中的元素和一个M元素的物理地址空间(PAS)中元素之间的映射,
- MAP:VAS到PAS和0
P543中图9-13a展示了当页面命中时,CPU硬件执行的步骤
- 第一步:处理器生成一个虚拟地址,并把它传送给MMU
- 第二步:MMU生成PTE地址,并从高速缓存/主存请求得到它
- 第三步:高速缓存/主存向MMU返回PTE
- 第四步:MMU构造物理地址,并把它传送给高速缓存/主存
- 第五步:高速缓存/主存返回所请求的数据字给处理器
9.6.1结合高速缓存和虚拟存储器
将VM于物理寻址的高速缓存结合起来
- VA:虚拟地址
- PTEA:页表条目地址
- PTE:页表条目
- PA:物理地址
9.6.2利用TLB加速地址翻译
- 第一步:CPU产生一个虚拟地址
- 第二步和第三步:MMU从TLB中取出相应的PTE
- 第四步:MMU将这个虚拟地址翻译成一个物理地址,并且把它发送到高速缓存/主存
- 第五步:高速缓存/主存将所请求的数据字返回给CPU
9.6.3多级页表
注意:
- 地址是从上往下增加的
9.6.4综合:端到端的地址翻译
- TLB
- 页表
- 高速缓存
9.7.1Core i7地址翻译
- PT:页表
- PTE:页表条目
- VPN:虚拟页号
- VPO:虚拟页偏移
- PPN:物理页号
- PPO:物理页偏移量
9.7.2Linux虚拟存储器系统
1.Linux寻存储器区域
- vm_start:指向这个区域的起始处
- vm_end:指向这个区域的结束处
- vm_prot:描述这个区域包含的所有页的读写许可权限
- vm_flags:描述这个区域的页面是与其他进程共享的,还是这个进程私有的
- vm_next:指向链表中一个区域结构
2.Linux缺页异常处理
9.8存储器映射
Linux通过将一个虚拟存储器区域与下一个磁盘上的对象关联起来,以初始化这个虚拟存储器可以区域的内容,这个过程称为存储器映射.
虚拟存储器区域可以映射到两种类型的对象的一种:
- (1)Unix文件系统中的普通文件
- (2)匿名文件
无论在哪种情况下,一但一个虚拟页面被初始化了,它就由内核维护的专门的交换文件之间换来换去。
9.8.1再看共享对象
- 一个对象可以被映射到虚拟存储器的一个区域,要么作为共享对象,要么作为私有对象。
- 一个映射到共享对象的虚拟存储器区域叫做共享区域。类似地,也有私有区域。
9.8.2再看fork函数
9.8.3再看execve函数
- 删除已经存在的用户区域
- 映射私有区域
- 映射共享区域
- 设置程序计数器
9.8.4使用mmap函数的用户级存储器映射
- PROT_EXEC:这个区域内的页面由可以被CPU执行的指令组成
- PROT_READ:这个区域内的页面可读
- PROT_WRITE:这个区域的页面可写
- PROT_NONE:这个区域内的页面不能被访问
9.9动态存储器分配
分配器有两种:
- 显式分配器
- 隐式分配器
9.9.1malloc和free函数
P563图9-34
- a:程序请求一个4字的块
- b:程序请求一个5字的块
- c:程序请求一个6字的块
- d:程序释放b中分配的那个6字的块
- e:程序请求一个2字的块
9.9.2为什么使用动态存储器分配
9.9.3分配器的要求和目标
显式分配器必须在一些相当严格的约束条件下工作:
- 处理任意请求序列
- 立即响应请求
- 只使用堆
- 不修改已分配的块
- 目标1:最大化吞吐率
- 目标2:最大化存储器利用率
9.9.4碎片
- 内部碎片
- 外部碎片
9.9.5实现问题
需要考虑下面几个问题:
- 空闲块组织
- 放置
- 分割
- 合并
9.9.6隐式空闲链表
9.9.7放置已分配的块
分配器执行搜素方式是由放置策略确定的。
- 首次适配
- 下一个适配
- 最佳适配
9.9.8分割空闲块
9.9.9获取额外的堆存储器
9.9.10合并空闲块
9.9.11带边界标记的合并
考虑当前分配器释放当前块时所有可能存在的情况:
- (1)前面的块和后面的块都是已分配的
- (2)前面的块是已分配的,后面的块是空闲的
- (3)前面的块是空闲的,而后面的块是已分配的
- (4)前面的和后面的块都是空闲的
9.9.12综合:实现一个简单的分配器
1.一般分配器设计
2.操作空闲链表的基本常数和宏
3.创建初始空闲链表
4.释放和合并块
5.分配块
9.9.13显式空闲链表
- 一种方法是后进先出(LIFO)的顺序维护链表
- 另一种方法是按照地址顺序来维护链表
9.9.14分离的空闲链表
- 1.简单分离存储
- 2.分离适配
- 3.伙伴系统
9.10垃圾收集
9.10.1垃圾收集器的基本知识
9.10.2Mark&Sweep垃圾收集器
- ptr isptr(ptr p)
- int blockmarked(ptr b)
- int blockallocated(ptr b)
- void unmarkblock(ptr b)
- ptr nextblock(ptr b)
9.10.3C程序的保守Mark&Sweep
- 第一:C不会用任何类型信息来标记存储器位置
- 第二:即使我们知道P是一个指针,对isptr也没有明显的方式来判断p是否指向一个已分配块的有效载荷中的某个位置。
9.11C程序中常见的与存储器有关的错误
间接引用坏指针
读未初始化的存储器
允许栈缓冲区溢出
假设指针和它们指向的对象是相同大小的
造成错位错误
引用指针,而不是它所指向的对象
误解指针运算
引用不存在的变量
引用空闲维块中的数据
引起存储器泄露