《深入理解计算机系统》之虚拟内存总结

前言

最近看了《深入理解计算机系统》,重温了许多操作系统和组原的知识。本篇博客主要介绍虚拟内存,让我们先从ELF-->进程引入。

ELF文件

对于每个程序,其在经历预处理、编译、汇编之后,都要经过链接器将其链接成一个单一的可执行文件。在现在Unix和x86-64 Linux系统上,其使用的可执行格式为ELF,如下:

《深入理解计算机系统》之虚拟内存总结

 

可以看到ELF涵盖了程序中的各种信息,加载器就是通过读取ELF文件中的数据和代码,将其从磁盘复制到内存中,生成相应的进程并跳转到第一条指令或入口点来运行该程序。

进程

进程地址空间如下图所示:

《深入理解计算机系统》之虚拟内存总结

 

可以看到,只读代码和数据对应于elf文件的(.init,.text,.rodata),读写段对应于elf文件的(.data,.bss)。

问题来了,如果我们有很多个程序要运行,所需内存已经超过了我们物理内存的容量,此时该怎么处理呢?

其实,当程序运行时,如果内存空间足够大,操作系统会按分页机制,将程序调入内存中。否则,操作系统会分批将程序的部分内容调入内存,再通过磁盘上的虚拟内存来实现内存置换,达到按需加载的目的。

到这里对虚拟内存有一定的概念了,似乎其作用就是“虚拟地扩充我们的内存”。

虚拟内存

由以上我们引入虚拟内存。

什么是虚拟内存

为了更加有效地管理内存,操作系统对主存提出了一种抽象的概念:虚拟内存。其通过硬件+软件的支持,为进程提供了更大的、一致的和私有的地址空间。虚拟内存主要提供一下三个能力:

  • 将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效地使用了主存;

  • 它为每个进程提供了一致的地址空间,从而简化了内存管理;

  • 它保护了每个进程的地址空间不被其他进程破坏。

物理和虚拟寻址

物理寻址

《深入理解计算机系统》之虚拟内存总结

 

首先输入一条加载指令,当CPU执行这条加载指令时,会生成一个有效物理地址,通过内存总线,把它传递给主存。主存取出从物理地址4开始的字节,并将它返回给CPU,CPU会将其放到一个寄存器中。

早期的PC就是使用物理寻址。

虚拟寻址

《深入理解计算机系统》之虚拟内存总结

 

现代PC多使用虚拟寻址的方式。

使用虚拟寻址,CPU通过生成一个虚拟地址来访问主存,这个虚拟地址在被送到内存之前先转换成适当的物理地址。将一个地址从虚拟地址转换成物理地址的过程称为地址翻译。CPU芯片上存在MMU(Memory Management Unit)内存管理单元的专用硬件,其通过内存中的进程页表来动态翻译地址。

  • 页表是什么?

    每个进程都有各自的页表,用来实现虚拟页到物理页的映射。

  • 页表存储在哪里?

    单级页表存储在内存中,多级内表最高级页表于内存,其他需加载。

  • 页表从哪里来?

    在每个程序被加载时,其相应的页表就产生了(内存映射,下文会提及)。

页表

地址翻译过程由操作系统软件、MMU中的地址翻译硬件和页表实现。

页表:将虚拟页映射到物理页。

页表就是一个页表条目(Page Table Entity:PTE)的数组。每个PTE由一个有效位和一个N位地址组成。有效位表明该虚拟页是否被缓存在物理内存中。如果设置了有效位,那么地址字段就表示物理页的起始位置。如果没有设置有效位,如果地址位非空则这个地址指向虚拟页在磁盘上的起始位置,空就表明此虚拟也还未被分配。

《深入理解计算机系统》之虚拟内存总结

 

设想一下几种情景

页命中

当CPU要读取VP1地址上的字节时,地址翻译硬件将虚拟地址定位到VP1,发现其为cached状态,此时可以直接从内存中读取到PP1。

缺页

当CPU引用VP3时,发现其为uncached状态,此时触发缺页异常,缺页异常由内核处理,内核中的缺页异常处理程序会从物理内存中选择一个牺牲页,将VP3存储在此牺牲页的内存空间上,并修改页表,如果此牺牲页被修改过,需将其复制写回磁盘。当异常程序返回时,它会从重新启动导致缺页的指令,此时VP3已在主存中了,那么页将命中,如情景1。

malloc

当程序调用malloc时,需要分配一个新的虚拟页面,此时我们在磁盘上创建空间VP5并更新PTE5,让其指向磁盘上的VP5。

注意:多个虚拟页面可以映射到同一个共享物理页面上。

虚拟内存:内存管理工具

虚拟内存是一个有用的机制,因为其大大地简化了内存管理。

  • 简化链接。独立的地址空间允许每个进程的内存映像使用相同的基本格式,而不管代码和数据实际存放在物理内存的何处。

  • 简化加载。虚拟内存使得容易向内存中加载可执行文件和共享对象文件。

  • 简化共享。独立地址空间为操作系统提供了一个管理用户进程和操作系统自身之间共享的一致机制。

  • 简化内存分配。虚拟内存向用户进程提供一个简单的分配额外内存的机制。当一个用户程序要求额外的堆空间时候,操作系统分配 k 个适当的连续的虚拟内存页面,并且将他们映射到物理内存的中的 k 个任意页面,操作系统没有必要分配 k 个连续的物理内存页面。

虚拟内存:内存保护工具

在页表中,可以该页表项添加控制位,来实现权限的控制。例如,每个 PTE 添加SUP、WRITE、EXRC控制位, SUP 位表示进程是否必须运行在超级用也就是内核模式下才能访问该页,WRITE 位控制页面的写访问, EXRC 位控制页面的执行。如果有指令违反了这些控制条件,那么 CPU 会触发一个一般保护故障,将控制传递给内核中的异常处理程序。

虚拟内存地址空间

在进程地址空间的图解中,最高位的地址为留给内核使用的虚拟内存,其中包含了每个进程都一样的内核代码和数据+物理内存,以及对每个进程都不相同的进程相关结构(与页表、task和mm结构,内核栈等)。

linux虚拟内存区域

《深入理解计算机系统》之虚拟内存总结

linux内核为每个进程维护一个单独的任务结构(task_struct),task_struct是linux下的PCB(进程控制块),其中包含进程运行时所需的各种信息(pid、指针、elf的名字等)。在其中有一个条目指向mm结构体。

mm_struct,其描述了虚拟内存的当前状态。其中具有两个重要的指针:pgd和mmap。其中pgd指向第一级页表的基址,而mmap指向vm_area_structs的链表,其中链表每个节点表示当前虚拟内存的一个区域。

节点T包含以下字段:

  • vm_start:区域起始处

  • vm_end:区域结束处

  • vm_port:区域内所有页的权限

  • vm_flags:区域内页面是否共享

  • vm_next:下一个区域

linux缺页异常处理

当MMU发现某虚拟地址并未在物理内存中时,触发缺页中断。

此时缺页异常处理程序会经过以下步骤:

  1. 检查虚拟地址是否合法。通过当前区域,与链表节点中的vm_start+vm_end比较(在实际中,linux在链表中构造了一颗红黑树,并在这棵树实现快速的查找,避免O(n)时间复杂度的遍历)。

  2. 检查内存访问是否合法。即进程是否有此区域的访问权限。

  3. 当检查通过后,内核将选择一个牺牲页面(判断其是否被修改过,修改过则写回磁盘),然后换入新的页面并更新页表。当程序返回时,再次调用此指令,下次将不会存生缺页中断。

内存映射

回到之前页表从哪里来的问题?

linux将一个虚拟内存区域与磁盘上对象关联的过程,称为内存映射。

其包括:文件映射、匿名映射。

文件映射

一个区域可以映射到磁盘文件上的连续部分,文件区被划分成页大小的片,每一片包含一个虚拟页面的初始内容。因为按需进行页面调度,所以这些虚拟页面没有实际交换进入物理内存,指导CPU第一次引用到页面。如果区域比文件区大,则用零来填充区域剩下的部分。

用户可以在用户空间通过open\read\write等函数来操作文件内容。

匿名映射

匿名文件是内核创建的。包含的全是二进制零。因为其不属于任何文件,CPU第一次引用此区域的虚拟页面时,内核就在物理内存中找合适的牺牲页面,将其存储到物理内存上。

即用户空间对malloc分配的内存,如果不进行实际的访问,内核只分配虚拟地址,而不将虚拟地址通过映射与物理地址一一对应,只有CPU实际访问了这些区域,才真正发生匿名映射,也才发生缺页处理。

  • CPU第一次引用匿名区域时?

    由于此时匿名区域只有虚拟地址,没有和物理内存相关联,内核就产生一个缺页错误,缺页异常处理程序从swap区获得一块物理内存与页映射,这里虚拟内存就与物理内存挂钩,产生相应的页表,页表相关信息存储在TLB或CACHE中以实现虚拟地址到物理地址的转换。

  • SWAP?

    swap分区就是linux中的虚拟内存,是linux中一种内存策略。

    swap涉及的换页,就是将不常使用的匿名页置换到磁盘(虚拟内存)上。

    注意:文件映射与虚拟文件系统相关,其有自己的缓冲和缓存,当需要这些程序的内存置换时,直接回放文件(不与swap打交道)所有文件映射的物理页帧不来自于swap区域。对于malloc生产的对象数据,由于缺乏存储文件空间,它们需要swap空间。

    简而言之,swap区域就是匿名映射的交换空间,这个区域由内核创建和管理。

总结

以上就是近期看黑皮书的一些总结,计算机系统很多底层的知识自己还是了解地比较浅显,趁着毕业前这段空档期还是需要及时补补。

由于本人才疏学浅,如有不足之处,还望看官们指出!

最后,还是推荐大家自己去看看《深入理解计算机系统》这本书,包罗计算机中的万象,本人也是受益良多。

参考

《深入理解计算机系统》——布赖恩特(Bryant,R.E.)

https://bbs.****.net/topics/391860037——大神Buddy.Zhang的解答