Linux虚拟内存系统

概述

Linux为每个进程维护一个单独的虚拟地址空间,包括代码,数据,堆,共享库以及栈段。


内核虚拟内存包含内核中的代码和数据结构。内核虚拟内存的某些区域被映射到所有进程共享的物理页面。例如每个进程共享内核的代码和全局数据结构。并且linux也将一组连续的虚拟页面(大小等于DRAM的总量)映射到相应的一组连续的物理页面。这就为内核提供了一种便利的方法来访问物理内存中任何特定的位置,比如当需要访问页表,或在一些设备上进行内存映射的IO操作,而这些设备被映射到特定的物理内存位置时。
内核虚拟内存的其他区域包含每个进程都不相同的数据。比如页表,内核在进程上下文中执行代码所使用的栈,以及记录虚拟地址空间当前组织的各种数据结构。

Linux虚拟内存区域

linux将虚拟内存组织成一些区域的集合。一个区域就是一些已经存在着的(已分配的)虚拟内存的连续片,它允许虚拟地址空间由间隙,这些页是以某种方式相关联的。例如:代码段,数据段,堆,共享库段以及用户栈都是不同的区域。每个已分配的虚拟页面都保存在某个区域中,而不属于某个区域的虚拟页则是不存在的,也不能被进程所引用。内核不用记录那些不存在的虚拟页,而这样的页也不占用内存,磁盘,或者内核本身中的任何额外资源。

内核为每个进程维护一个单独的任务结构(task_stuct),任务结构中的元素包含或者指向内核运行该进程所需要的所有信息(例如PID,程序计数器,指向用户栈的指针,可执行目标文件的名字)。
任务结构中的一个条目指向mm_struct,它描述了虚拟内存的当前状态,有两个字段需要提下,pgd和mmap,pgd指向第一级页表(页全局目录的基址),mmap指向一个vm_area_structs(区域结构)的链表,其中每个vm_area_structs都描述了当前虚拟地址空间的一个区域。当内核运行这个进程时,就将pgd存放在CR3控制寄存器中。
一个具体区域的区域结构包含下面字段:

  • vm_start:指向这个区域的起始处。
  • vm_end:指向这个区域的结束处。
  • vm_prot:描述这个区域内包含的所有页的读写权限。
  • vm_flags:描述这个区域内的页面是与其他仅共享的还是这个进程私有的。
  • vm_next:指向链表中的下一个区域结构。

Linux缺页异常处理


假设MMU在试图翻译某个虚拟地址A时,触发了一个缺页,这个异常导致控制转移到内核的缺页异常处理程序,处理程序随后执行以下步骤:

  • 虚拟地址A是合法的么?也就是说,A在某个区域结构定义的区域内么?缺页处理程序会搜索区域结构的链表(应该是用红黑树维护链表),把A和结构中的vm_start和vm_end做比较。如果指令不和法,则触发一个段错误,终止该进程。

  • 试图进行的内存访问是否合法?即进程是否有读,写或者执行这个区域内页面的权限?如果试图进行的访问不合法,那么缺页处理程序会触发一个保护异常,从而终止这个进程。

  • 刺客,内核知道了这个缺页是由于合法的虚拟地址进行合法操作造成的。此时它选择牺牲一个页面,如果这个牺牲页面被修改过,那么将它交换出去,换入新的页面并更新页表。当缺页处理程序返回时,CPU重新启动引起缺页的指令,这条指令重新发送A到MMU,这次MMU就能正常的翻译A,而不会产生缺页中断了。


参考资料:
《深入理解计算机系统 3th》(CSAPP)