CSAPP笔记06虚拟内存(上)
CSAPP笔记06虚拟内存(上)
0x00 概述
进程之间共享CPU和主存资源的,cpu的共享是通过时间片流转的方式实现的,因此无论有多少进程,都能顺利执行。但是主存空间则不同,如果进程过多主存空间不够用,那么根部无法运行新进程。另一方面,内存容易被破坏,如果某个进程无意/有意写了另一个进程使用的内存,那么就可能出现令人困惑的错误。
为了更有效管理内存并减少出错,现代计算机系统提供了一种对主存的抽象,叫做虚拟内存(virtual memory)。虚拟内存的实现依赖于cpu和内核的配合,它为每个进程提供一个大的、一致的、私有的地址空间。它为计算机系统提供了三个重要的功能:
①它将地址空间上的数据储存在磁盘中,将主存当作磁盘的高速缓存,在主存中只保存活动区域,根据需要在磁盘和主存之间传送数据,高效使用主存。
②为每个进程提供一致的地址空间,简化内存管理
③它保护不同进程的地址空间不被其他进程破坏
0x01 物理和虚拟寻址
物理地址:计算机系统的主存被组织成一个连续的字节数组,每个字节都有唯一的一个编号,即物理地址。
物理寻址:cpu访问内存的最直接的方式,就是向主存的控制器发送物理地址,拿到对应的主存空间里的数据,称为物理寻址。
虚拟寻址:现代处理器使用的是一种虚拟寻址的寻址形式。cpu根据指令的要求生成一个虚拟地址(virtual adress)来访问主存,虚拟地址cpu里又被转换成物理地址,称为地址翻译(address translation)。地址翻译同样需要cpu和内核的配合,内核提供一个转换表,cpu上的内存管理单元(memory manage unit,MMU)负责按照该表来进行地址翻译/转换。
地址空间
地址空间是一个地址的集合,如果地址是连续的,那么称为线性地址空间。cpu能够访问的虚拟地址的集合,称为虚拟地址空间。一个系统的最大地址,由地址总线的位数决定,为2^n-1.
地址空间区分了地址和空间(也就是空间内的数据)。
0x02 虚拟内存作为缓存的工具
我们可以把虚拟内存看作储存在磁盘上的字节数组,每个字节都有唯一的虚拟地址,作为数组的索引。磁盘上数组的内容被缓存在主存中,同样的,较低层(磁盘)上的数据被分割成块,作为和缓存之间的传输单元。VM系统将虚拟内存分割称为虚拟页(virtual page,VP)的大小(一般是4k)固定块。同样的主存(物理内存)被分割成同样大小的块,称为物理页(physical page,PP),物理页也称为页帧。
在任意时刻,虚拟页面的集合都由三个不相交的子集构成:
①未分配的:vm系统还未分配/创建的页,未分配的页没有任何数据关联,不占用磁盘空间。
②缓存的:当前已经缓存到物理内存中的已分配页。
③未缓存的:未缓存的在物理内存中的已分配页(即在对应数据在磁盘中)。
图上一共有8个虚拟页,两个未分配,没有任何数据,三个未缓存,数据在磁盘里,三个已缓存,数据在主存里缓存了。
主存作为缓存的组织结构
一般而言,缓存指的是SRAM,即cpu寄存器和主存之间的缓存。而主存作为磁盘和cpu之间缓存,我们将其称为DRAM缓存。
DRAM比SRAM大约要慢10倍,而磁盘又比DRAM要慢1000多倍。因此DRAM缓存不命中的代价要比SRAM不命中代价要高得多!因此DRAM缓存的块一般比较大,即虚拟页和物理页相较于SRAM缓存的块大很多,并且对于DRAM缓存使用更加复杂更加精密的替换算法。
并且,由于对磁盘的当问时间很长,DRAM缓存总是将修改写回到缓存,而不是直接写到磁盘(除非程序指定要写磁盘)。
页表
同任何缓存一样,VM系统必须要有某种方法来判定一个虚拟页(块)是否在DRAM缓存中,如果在缓存中,在哪个块中?如果不命中,如何换入?
VM系统中,物理内存中存在一个页表(page table)的数据结构,页表将虚拟页映射到物理页,每次地址翻译都将一个虚拟地址转换成物理地址,都要读取页表(其实页表也有缓存),而页表由内核负责维护,内核还负责决定缓存如何的换入和换处。
页表是页表条目(page table entry)的数组,虚拟地址空间中每个页对应页表中的对应索引的条目。
每个条目分为属性字段和地址段组成。属性字段有个有效位(valid字段),表明对应条目的虚拟页是否被缓存在DRAM中。如果设置了有效位,那么表明该虚拟页被缓存在DRAM中,并且缓存的地址在地址段;如果没有设置有效位,那么地址段表示的是虚拟页数据在磁盘上的位置,如果地址段为0,表示虚拟页没有被分配,不对应任何数据!
页命中
对应上图,如果访问VP2中的虚拟内存中数据,mmu会将虚拟地址作为一个索引来定位PTE2,并从内存中读取出来。PTE2设置了有效位,因此mmu知道其缓存在主存里,然后根据地址段构造处这个数据的物理地址。
缺页
在VM中,DRAM缓存不命中称为缺页(page fault),比如上图中我们访问VP3中的数据,mmu将虚拟地址作为索引来访问PTE3,发现其有效位未设置,但是地址段不为0,知道未被缓存,数据在磁盘上,触发缺页异常。
缺页异常调用内核中的缺页处理程序,该程序会选择一个牺牲页,比如VP4,处理程序会先看VP4是否被修改(PTE4上的dirty位),如果被修改了,就将VP4复制回磁盘,并且将VP4的有效位置0,表明VP4已经不在缓存了。然后处理程序从磁盘复制VP3的数据到内存的PP3,更新PTE3,随后返回。当处理程序返回后,会重新执行导致缺页的指令。由于PTE3已经被更新,因此再次执行时,可以从DRAM缓存中正确访问数据。
在VM系统中,和SRAM缓存不同,在磁盘和主存之间传送页,叫做交换(swapping)或者页面调度(paging)。页面从主存传送到磁盘称为页面换出,从磁盘传送到主存称为页面换入。
直到有页面不命中发生时,才换入页面的策略称为按需页面调度(demand paging)。现代所有系统都使用按需页面调度。
分配页面
当我们在进程的虚拟地址空间中分配新的数据空间时,比如调用malloc,他会在对应的PTE的地址字段,写上对应的磁盘地址,也就是,先在磁盘中分配空间。当首次访问之前分配的空间时,发生缺页异常,将该页面换入,跟新PTE的有效位,地址位也指向对应的物理地址。
内存抖动
磁盘的访问开销很大,而物理内存一般又远远小于虚拟地址空间,那么我们可能会担心,磁盘的换入换出会不会经常发生?
程序对数据的使用具有局部性,程序倾向于在一个小的活动页面(active page)的集合上工作,这个集合称为工作集。除了程序刚开始被执行时,第一次访问工作集,会调度页面,换入工作集的虚拟页,产生开销,以后对这个工作及的访问,不再产生开销。
但是,如果工作集的大小超过了物理内存,那么,必然有一部分要放在磁盘上,也就是未缓存。这时对工作集的访问,将大概率进行页面的换入和换出,称为内存抖动(thrashing),这个时候程序运行的很慢。
0x03 虚拟内存对内存的管理
在早期的某些系统中,支持比物理内存更小的虚拟地址空间。之所这样,是因为除了可以作为磁盘的缓存外,提高主存利用率外。还可以更方便的管理内存,并且更自然地保护内存。
操作系统为每个进程提供一个单独的页表,也就是为每个进程分配一个独立的地址空间。并且,可以将多个虚拟页(即使是不同进程的虚拟页)映射到同一个共享的物理页上。
按需页面调度和独立的虚拟地址空间结合,对系统中内存的使用和管理造成了巨大影响:
①简化链接:由于虚拟内存让每个进程都有一致的,独立的 从0开始的虚拟地址空间,因此链接时,不用考虑系统中其他进程对地址的影响,简化了重定位过程。比如可以让代码段总是从0x40000开始,数据段地址总是从0x600000。
②简化加载:由于对页的调度总是按需调度的,因此,加载时并不需要将对应的.text和.data节加载到物理内存里,而是直接在对应PTE里,将有效位置0,地址先写磁盘上对应的可执行文件里的对应节。然后当程序执行,引用对应页时,VM系统会按照需要自动调用数据页。
③简化共享:一般而言,每个进程都有自己私有的代码、数据、堆 栈,不和其他程序共享。然而,有些情况还是需要进程共享代码和数据的,比如每个进程都调用内核代码,每个程序都调用C标志库中的函数。这种情况下,操作系统将不同进程中间的虚拟页(可能处于进程的地址空间的不同位置,也可能相同)映射到相同的物理页面,从而实现共享。
④简化内存分配:VM向进程提供了一个简单的分配额外内存的机制。比如,当调用malloc时,操作系统只需要从该进程的虚拟地址空间中分配k个连续的虚拟页,将他们映射到物理内存中不连续的k个物理页(页表的映射没有要求,可以不连续)。因为从物理页中连续分配总是麻烦和容易碎片化的,所以内存分配相较而言简单很多。
0x04 虚拟内存对内存的保护
多任务的系统中,实现不同任务之间的访问控制是很重要的。①不应该允许一个进程修改它的只读代码段,②也不该允许其修改内核的代码和数据,③也不能允许其读写其他进程的私有内存,④不允许其修改共享的虚拟页面。
对于1,不允许进程修改自己的只读代码段,VM系统在PTE上提供了R/W位,可以控制虚拟页对应数据的访问,当前写只读的PTE对应的数据时,会触发异常segmentation fault。
对于3,由于每个进程都有私有的地址空间,并且它们无权修改页表,因此它们总是只能访问到自己的数据。
对于2,内核的代码和数据处在特权级0,内核在进程地址空间里的虚拟页对应的PTE条目 SUP字段是1,表明只允许内核访问,因此进程无法修改内核。
0x05 地址翻译
命中情况:
未命中情况:
对PTE的缓存
如果每次对一个虚拟地址的访问,mmu都要访问内存中对应的PTE,那么我们访问一个内存中的数据,实际上要读取两次内存(如果目标数据也不在SRAM缓存)。
因此,我们应该把PTE也缓存在SRAM,这样可以加速地址翻译。
多级页表
使用一级页表的缺点是,64位处理器的虚拟地址空间高达2^64 byte,虚拟页本身足够大,每个进程的虚拟地址空间需要的PTE数量还是大得惊人,我们需要非常大的物理内存来储存PTE,多个进程时更是如此!
因此我们使用多级页表,每一级页表负责翻译虚拟地址的一部分,这样的好处在于,对于未分配的虚拟页,我们只用先保存其对应的第一级PTE,并将第一PTE地址置0,低级PTE不用保存,不占用内存。