《深入理解计算机系统》第8周

虚拟内存是现代系统提供的一种对主存的抽象概念,它将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域按需在磁盘和主存之间来回传送数据;为每个进程提供了一致的地址空间,简化了内存管理;并且保护每个进程的地址空间不被其他进程破坏。通过本章学习可以了解虚拟内存是如何工作的,以及如何在程序中使用和管理虚拟内存,避免一些和内存有关的错误出现。
现代系统通过内存映射将虚拟内存和磁盘文件关联,为共享数据、创建新进程以及加载程序提供了一种高效的机制。应用可以使用mmap函数来手工地创建和删除虚拟地址空间的区域。对于运行时才知道大小的数据结构,大多数程序通过动态内存分配器管理虚拟地址空间中的堆区域,并且显式或自动地释放内存。

  • 早期的PC通过物理寻址,现代处理器多采用虚拟寻址来访问主存,虚拟地址在被送到内存之前通过MMU地址翻译转换为物理地址。
  • 虚拟页,VM将虚拟内存分割为大小固定的块,虚拟页有以下三种:
    • 未分配的,没有任何数据与之关联,不占用任何磁盘空间;
    • 缓存的,当前已缓存在物理内存中的已分配页;
    • 未缓存的,未缓存在物理内存中的已分配页
  • DRAM缓存表示虚拟内存系统的缓存,是全相联的,因为虚拟页通常为4K-2M,不命中时替换开销较大,因此替换策略非常重要。因为对磁盘访问时间长,DRAM缓存总是使用写回而非直写。
  • 局部性原则保证了任意时刻,程序将趋向于在一个较小的active page集合上工作,即常驻集合/工作集。如果工作集的大小超出了物理内存大小,程序将产生一种不幸的状态,即抖动。页面将不断地换进换出。
  • 将一组连续的虚拟页映射到任意一个文件中的任意位置的表示法称为内存映射,Linux系统提供一个成为mmap的系统调用,允许应用程序自己做内存映射。
  • 正常命中时CPU硬件执行步骤:
    • 处理器生成一个虚拟地址并传送给MMU;
    • MMU生成PTE地址,从高速缓存/主存请求得到它;
    • 高速缓存/主存向MMU返回PTE;
    • MMU构造物理地址,并把它传送给高速缓存/主存;
    • 高速缓存/主存返回所请求的数据字给处理器
  • 当缺页时,需要硬件和操作系统内核写作完成:
    • 前三步相同
    • PTE有效位为0,MMU触发异常,传递CPU中的控制到操作系统内核中的缺页异常处理程序;
    • 缺页处理程序确定出物理内存中的牺牲页,如果该页已经被修改,则把它换出到磁盘;
    • 缺页处理程序页面调入新的页面,并更新内存中的PTE
    • 缺页处理程序返回到原来的进程,再次执行导致缺页的指令。主存将请求字返回给CPU。
  • 频繁读取PTE会使得内存多取一次数据,为了消除开销,现代系统利用TLB(MMU中一个PTE的小缓存)
  • 如果每个进程都在物理内存中保存常用代码的副本会比较浪费,内存映射提供了一种机制控制多个进程共享对象。一个对象可以被映射到虚拟内存的一个区域,作为共享对象或者私有对象。对于共享对象,进程对该区域的任何写操作,其他把该对象映射到虚拟内存的进程也可见。私有对象使用写时复制的技术被映射到虚拟内存中,延迟私有对象中的副本到最后可能的时刻来充分利用物理内存。
  • Fork调用时,内核为新进程创建虚拟内存,创建了当前进程的mm_struct、区域结构和页表的原样副本,将两个进程的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。当fork在新进程中返回后,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当两个进程中的任何一个后来进行写操作时,写时复制都会创建新页面,即为每个进程保持了私有地址空间的抽象概念。
  • 程序使用动态内存分配的最重要原因是直到程序实际运行时才知道某些数据结构的大小,数据大小的最大值可以由可用的虚拟内存数量来限制。
  • 管理和使用虚拟内存中常见的错误类型:间接引用坏指针,读取未初始化的内存、允许栈缓冲区溢出、假设指针和它们指向的对象大小相同,引用指针而不是它指向的对象、误解指针运算、引用不存在的变量、以及引起内存泄漏等。