漫谈虚拟内存

时间:2022-05-07
本文章向大家介绍漫谈虚拟内存,主要内容包括内存管理的要求、物理地址、逻辑地址、分页、页面置换算法、分段、段页结合、虚拟内存具体实现、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

虚拟内存是什么?它是对主存和I/O设备的抽象,这一点在漫谈进程和线程中已经提及过,也就是说,虚拟内存是将内存看做硬盘的高速缓存,内存中只保存程序的活动区域,根据需要在硬盘和内存之间传输数据;同时,虚拟内存为每个进程提供一个一致的地址空间,比如说32位计算机,每个进程的地址范围是0,4G。此外,虚拟内存保护每个进程的地址空间不被其他进程破坏,那么,通过何种方法使得每个进程的地址空间是独立的?看完本文,这个问题你自然就知道答案了。

内存管理的要求

  • 针对批处理系统,程序顺序执行,程序依次装入内存运行,一个新装入的程序会完全覆盖老的程序。
  • 针对分时系统,多个程序并发执行,要把尽可能多的程序装入内存,但是物理内存的空间是有限的,经常需要将程序换入换出,这样一来,系统性能就变差了;程序在内存中要区分开。
    内存分配

如上图,程序1、程序2、程序3装入到内存,而程序2运行完成被换出,内存空闲出20k,然后进来程序4,大小为25K,此时,只有两处空闲块,10K和20K,没有一处是符合条件的,应该怎么办?一个明显的办法就是将两块空闲区域进行合并,形成一个大小为30K的空闲块满足程序4。

注意:此时描述的内存的物理地址,物理内存使用的是连续地址,下面详细介绍物理地址。

物理地址

使用物理地址的缺点:当多个程序同时对某个地址进行操作时就会引起冲突,如下图所示,程序1和程序2中都有指令movl eax,(100)。针对这个问题怎么解决呢?

在装载程序的时候,修改指令的地址。例如程序2中的(100)+1000,其中1000代表这个程序的开始地址,而程序1中的(100)+0。这样做是很困难的,因为需要我们理解所有的指令。既然这个方法不好,那么有没有其他方法呢?答案就是逻辑地址

程序1、2地址冲突

逻辑地址

CPU有一个内存管理单元(MMU),它有一个基址寄存器,它保存着每个程序的开始地址,比如说程序1的基址寄存器是1000,偏移量是200,转换成物理地址是1200。

逻辑地址

分页

假设一个程序很大,需要占据所有内存,而内存管理的一个要求就是把尽可能多的程序装入内存,两者相互矛盾。应对方法就是分页,就是说每个程序开始运行时只会加载部分数据到内存中操作系统会为每个进程维护一个页表,页表是维护虚拟页和物理页的映射关系,当页表中的虚拟页对应的物理页是空白时,操作系统会发生缺页中断。分页的理论依据是局部性原理(空间局部性+时间局部性),也正是这个原因,程序在大部分时间内不需要进行页面置换。如果发生缺页中断,缺页中断处理程序读取磁盘,选择一个空闲物理页面,修改页表,重新执行程序。

  • 注意事项
  • 每个进程都要有一个页表,进程PCB有指向页表的指针
  • 页表访问要非常快(硬件缓存来拯救:转换缓冲区--TLB)
  • 页表可能非常大(2^32 的内存空间,每个页大小2^12 ,页表中需要2^20个页表条目,假设每个条目4Byte,需要4M空间来存放表,而且每个进程都需要4M,这是非常占用空间的。可以采用多级页表,反向页表等技术来解决)。
分页
  • 分页具体流程 以CPU执行MOV (0x560) EAX为例,CPU内部会将逻辑地址进行拆分成页号和偏移量,然后将逻辑地址转换成物理地址。
    分页流程

页面置换算法

  • 内存是有限的,不可能把所有的页面都装进来,缺页时需要进行页面置换。
  • 页面置换背后是个通用的问题(Web服务器的缓存、Redis、Memcached的缓存等等)。FIFO(先进先出)先进先出算法思想很简单,当内存满了,优先置换出最先进入内存的页面。但是它存在一个问题:经常被访问的数据有可能被换入换出,下面我就举个简单的栗子。假设只有3个物理页面,逻辑页面的访问次序是: 7 0 1 2 0 3 0 4
FIFO

LRU(最近最少使用)

LRU算法就是所有页用栈组成,当栈满了,且新增加元素没有命中,则将栈底元素淘汰,新增元素放到栈顶;当栈满了,且新增元素命中,则只需要将新增元素移动到栈顶位置即可。假设只有3个物理页面,逻辑页面的访问次序是: 7 0 1 2 0 3 0 4

LRU

Clock算法

Clock算法是LRU算法的近似实现,它为每个页加一个引用位,默认值为0,无论读还是写,都置为1,它把所有的页组成一个循环队列,选择淘汰页的时候,扫描引用位,如果是1则改成0(相当于再给该页面一次存活的机会),并扫描下一个;如果该引用位是0,则淘汰该页,换入新的页面。假设只有3个物理页面,逻辑页面的访问次序是: 3 4 2 6

Clock

分段

上述介绍的页表机制是面向机器的,而为了程序员更好的理解程序,我们的先辈们又提出了分段概念,就是将程序划分为若干段部分,每一段都有独立的功能,例如:代码段,数据段,栈,堆等。通过分段技术,我们把内存空间分成一个个可以自治的段,而且把内存从一维空间变成了一个二维空间。

段表结构

段页结合

段页结合流程:首先根据段表信息,将逻辑地址转换成另一个逻辑地址,在转换的过程中会判断偏移量是否超过指定长度,如果没有超过,则,则根据页表将逻辑地址转换成物理地址。

段页结合

虚拟内存具体实现

这里介绍Linux中的虚拟内存的具体实现,如下图,task_struct结构体是进程描述符,属于进程管理(PCB),其中,mm(memory manage)表示内存管理,它指向mm_struct结构体,它描述linux下进程的虚拟地址空间,它又包含两个重要字段:pgd、mmap,其中,pgd指向第一级页表的基址,而mmap指向一个vm_area_struct(区域结构)的链表,vm_start,vm_end分别表示数据的起始地址,vm_prot描述的是这个区域包含的所有页的读写许可权限;vm_flags描述这个区域是和别的进行共享的,还是该进程私有的

Linux是如何组织虚拟存储器

Linux缺页中断:MMU(内存管理单元)试图翻译一个虚拟地址A,当这个虚拟地址对应的物理地址不在内存中是,触发一个缺页中断。虚拟地址A是合法的吗?地址A在某个区域地址内吗(vm_start,vm_end),如果不存在,segement fault!段错误。如果存在,则接着判断进程是否可以读,写,执行这个区域内页面的权限?如果没有权限,触发保护异常。经过上诉两项判断,如果都是正常的,最后才开始真正的缺页处理,从硬盘装载数据,修改页表。


image

欢迎关注微信公众号:木可大大,所有文章都将同步在公众号上。