虚拟存储器的三个重要能力:
它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,高效的使用了主存。
它为每个进程提供了一致的地址空间,从而简化了存储器管理
它保护了每个进程的地址空间不被其他进程破坏。
计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。
每个字节都有唯一的物理地址。
第一个字节的地址为0,接下来的字节地址为1,在下一个为2,依次类推。
给定这种简单的结构,CPU访问存储器的最自然的方式就是使用物理地址。我们把这种方式称为物理寻址。
现代处理器使用的是一种称为虚拟寻址的寻址形式。使用这虚拟寻址时,CPU通过生成一个虚拟地址来访问主存,这个虚拟地址再被送到存储器之前先转换成适当的物理地址。
地址空间是一个非负整数地址的有序集合:{1、2、3.....}
线性地址空间:地址空间的整数是连续的。
虚拟地址空间:CPU从一个有 N=2^n 个地址的地址空间中生成虚拟地址,这个地址空间成为称为虚拟地址空间。
物理地址空间:与系统中的物理存储器的M个字节相对应{1,2,3.....,M-1}
1)未分配的:VM系统还没分配/创建的页,不占用任何磁盘空间。
2)缓存的:当前缓存在物理存储器中的已分配页
3)未缓存的:没有缓存在物理存储器中的已分配页
用术语DRAM缓存来表示虚拟存储器系统的缓存,它在主存中缓存虚拟页。
不命中处罚很大,因为大的不命中处罚和访问第一字节的开销,虚拟页往往很大。
缓存是全相联的——任何虚拟页都可以放在任何的物理页中。
不命中时的替换策略也很重要,因为替换错了虚拟页的处罚也非常之高。总是对磁盘访问时间很长。替换算法精密,总是使用写回而不是直写。
页表:是一个数据结构,存放在物理存储器中,将虚拟页映射到物理页,就是一个页表条目的数组。
页表组成:有效位+n位地址字段。
缺页:就是指DRAM缓存不命中。
缺页异常:会调用内核中的缺页异常处理程序,选择一个牺牲页。
变换/页面调度:在磁盘和存储器之间传送页的活动。
局部性:局部性原则保证了在任意时刻,程序将往往在一个较小的活动页面集合上工作,这个集合叫做工作集或者常驻集。
如果工作集的大小超出了物理存储器的大小,那么程序将产生一种不幸的状态,叫做颠簸。
1)简化链接:独立的地址空间允许每个进程的存储器映像使用相同的基本格式,而不管代码和数据实际存放在物理存储器的何处。
2) 简化加载:虚拟存储器使得容易想存储器中加载可执行文件和共享文件对象。
3)简化共享:独立地址空间为操作系统提供了一个管理用户进程和操作系统自身之间共享的一致机制。
4)简化存储器分配:虚拟存储器为向用户进程提供一个简单的分配额外存储器的机制。
任何现代计算机系统必须为操作系统提供手段来控制对存储器系统的访问。
地址翻译机制以一种自然的方式扩展到提供更好的访问控制。每次CPU生成一个地址时,地址翻译硬件都会读一个PTE,所以通过在PTE上添加一些额外的许可位来控制对一个虚拟页面内容的访问十分简单。
SUP:表示进程是否必须运行在内核模式下才能访问该页.
READ和WRITE:分别表示对页面的读写访问控制。
形式上来说,地址翻译是一个N元素的虚拟地址空间(VAS)中的元素和一个M元素的物理地址空间(PAS)中元素之间的映射:MAP:VAS->PAS U ∅
MAP = A' :如果虚拟地址A处的数据在PAS的物理地址A'处。
MAP = ∅ :如果虚拟地址A处的数据不在物理存储器中。
1)处理器生成虚拟地址,传给MMU。
2)MMU生成PTE地址,并从高速缓存/主存请求得到他。
3)高速缓存/主存向MMU返回PTE。
4)MMU构造物理地址,并把它传给高速缓存/主存。
5)高速缓存/主存返回所请求的数据给处理器。
1)处理器生成虚拟地址,传给MMU。
2)MMU生成PTE地址,并从高速缓存/主存请求得到他。
3)高速缓存/主存向MMU返回PTE。
4)PTE中有效位为0,触发缺页异常。
5)确定牺牲页。
6)调入新页面,更新PTE。
7)返回原来的进程,再次执行导致缺页的指令,会命中。
存储器是按字节寻址的。
存储器访问是针对1字节的字的(不是4字节的字)。
虚拟地址是14位长的(n=14)。
物理地址是12位长的(m=12)。
页面大小是64字节(p=64)。
TLB是四路组相联的,总共有16个条目
L1 d-cache 是物理寻址、直接映射的,行大小为4字节,而总共有16个组。
一个运行Linux的Intel Core i7是基于Nehalem微体系结构的。虽然Nehalem设计允许完全的64位虚拟和物理地址空间,而现在以及可预见的未来的Core i7实现支持48位(256TB)虚拟地址空间和52位(4PB)物理地址空间,还有一个兼容模式,支持32位(4GB)虚拟和物理地址空间。
处理器包:包括四个核、一个大的所有核共享的L3高速缓存,以及一个DDR3存储器控制器。
linux将虚拟存储器组织成一些区域(也叫做段)的集合。一个区域就是已经存在着的(已分配的)虚拟存储器的连续片,这些页是以某种方式相关联的。代码段、数据段、堆、共享库段,以及用户栈都是不同的区域。
存储器映器:指Linux通过将一个虚拟存储器区域与一个磁盘上的对象关联起来,以初始化这个虚拟存储器区域的内容的过程。
共享对象:共享对象对于所有把它映射到自己的虚拟存储器进程来说都是可见的。即使映射到多个共享区域,物理存储器中也只需要存放共享对象的一个拷贝。
私有对象:私有对象运用的技术:写时拷贝,在物理存储器中只保存有私有对象的一份拷贝。
mmap函数要求内核创建一个新的虚拟存储器区域
PROT_EXEC:这个区域的页面由可以被CPU执行的指令组成。
PROT_READ:这个区域的页面可读。
PROT_WRITE:这个区域的页面可写。
PROT_NONE:这个区域的页面不能被访问。
堆:一个请求二进制0的区域,紧接在未初始化的bss区域后开始,并向上(更高的地址)生长。有一个变量brk指向堆的顶部。
显式分配器:要求应用显示地释放任何已分配的块。
隐式分配器:要求分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。隐式分配器也叫做垃圾收集器,而自动释放未使用的已分配的块的过程叫做垃圾收集。
malloc和free函数:
程序通过调用malloc函数来从堆中分配块。
#include <stdlib.h>
void *malloc(size_t size)
成功返回指针,指向大小至少为size字节的存储器块,失败返回NULL
这个块会为可能包含在这个块内的任何数据对象类型做对齐。
还可以用sbrk函数:
#include <unistd.h>
void *sbrk(intptr_t incr);
返回:若成功则为旧的brk指针,若出错则为-1.
程序通过调用free函数来释放已分配的堆块:
#include <stdlib.h>
void free(void *ptr);
返回:无
ptr参数必须指向一个从malloc、calloc或者realloc获得的已分配块的起始位置。
如果不是,那么free的行为就是未定义的。
要求:
a.处理任意请求序列
b.立即响应请求
c.只使用堆
d.对齐块
e.不修改已分配的块
目标:
a.最大化吞吐率
b.最大化存储器利用率——峰值利用率最大化
void garbage()
{
int *p = (int *)Malloc(15213)
return;
}
是一种动态存储分配器,它自动释放程序不再需要的已分配块。这些块称为垃圾。自动回收堆存储的过程叫做垃圾收集。
在对Mark&Sweep的描述中使用下列函数:其中ptr定义为typedef void *ptr
ptr isPtr(ptr p):如果p指向一个已分配块中的某个字,那么就返回一个指向这个块起始位置的指针b,否则返回NULL。
int blockMarked(ptr b):如果已经标记了块b,就返回true。
int blockAllocated(ptr b):如果块b是已分配的,就返回true。
void markBlock(ptr b):标记块b。
int length(ptr b):返回块b的以字为单位的长度(不包括头部)。
void unmarkBlock(ptr b):将块b的状态由已标记的改为未标记的。
ptr nextBlock(ptr b):返回堆中块b的后继。
《深入理解计算机操作系统》