虚拟内存详解


使用了虚拟内存技术的操作系统,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存

一项新技术的产生是为了解决一些问题。在讲虚拟内存技术之前我们可以了解一下传统的内存管理方式的特征和缺点。

一次性:程序必须一次性全部装入内存才能开始执行。这会造成两个问题:1.程序很大时,不能全部装入内存,导致大程序无法运行。2.当大量程序要求运行时,由于内存无法容纳所有的进程,因此只有少量的程序能运行,导致多道程序并发度下降。

驻留性:一旦程序装入内存中,就会一直驻留在内存中,直到进程运行结束。事实上,在一个时间段内,只要访问作业的一小部分数据就能正常运行,这就会导致内存中驻留大量暂时用不到的数据,浪费了宝贵的内存资源。

而虚拟内存技术就是为解决上面的问题而提出的,它主要是基于时间局部性和空间局部性原理

时间局部性:如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行:如果某个数据被访问过,不久之后该数据很可能再次被访问。 (因为程序中存在大量的循环) 比如一个while循环

空间局部性:一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。(因为很多数据在内存中都是连续存放的,并且程序的指令也是顺序地在内存中存放的) 比如一个数组

概括的说如何实现虚拟内存?

基于局部性原理,在程序装入时,可以将程序中很快会用到的部分装入内存,暂时用不到的部分留在外存就可以让程序开始执行。

在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。(请求调页/请求调段)
若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。(页面置换/段置换)

因此,虚拟内存的实现需要建立在离散分配的内存管理方式基础上。离散分配管理内存方式有两种:内存分段和内存分页。

什么是内存分页?

将内存空间分为一个个大小相等的分区(比如:每个分区4KB),每个分区就是一个“页框”,每个页框有一个编号,页框号从0开始。

将进程的逻辑地址空间也分为与页框大小相等的部分,每个部分称为一个“页”,每个页面也有一个编号即“页号”,页号也是从0开始。

操作系统以页框为单位为各个进程分配内存空间。页框和页面一一对应,各个页面不必连续存放,可以放到不相邻的各个页框中。操作系统如何记录并且描述这种一一对应关系?使用了一种数据结构——页表

什么是内存分段?

进程的地址空间:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名,每段从0开始编址内存。

分配规则:以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻。

段表:程序分多个段,各段离散地装入内存,为了保证程序能正常运行,就必须能从物理内存中找到各个逻辑段的存放位置。为此,需为每个进程建立一张段映射表,简称段表。

分页机制和分段机制的区别和共同点

共同点

  • 首先他们都是非连续式内存分配方式,在内存中的存放都是离散式的
  • 分页和分段都是为了减少内存碎片和降低内存换入和换出的开销

不同点

  • 分页机制所分出来的页是固定大小的,而分段机制所分出来的段不是固定大小的,由运行时程序决定
  • 分页机制分出来的页是没有实际含义的,程序员无法根据某一页来判断里面的内容是什么,分段机制分出来的段是有具体含义的,程序员得到关于段的信息

具体的说是如何实现虚拟内存?

虚拟存储管理需要考虑的问题

  • 读取策略:某一页什么时候调入到内存中
  • 置换策略:选择哪一页换出到内存
  • 驻留集管理:给进程分配多少帧,置换时牺牲谁的帧
  • 清除策略:何时将脏页写回到磁盘中
  • 加载控制:调整系统的并发度,防止抖动

目前来说,虚拟内存的实现方案可以基于现成的内存管理机制进行实现,比如说有请求式分页,请求式分段,请求式段页,目前应用最为广泛的有请求分页式,这种方案中,为了能够淘汰掉一些无用的内存页让那些目前急需的内存页进行加载,就需要给这些页添加一个头,通过读取这个头得到依据。

目前来说,有请求分页式管理,这种管理方式是基于页式管理进行实现的,相比于传统的页式管理,请求分页式的根本不同在于进程在运行的时候不必将所有的内存页加载进去内存,而是在需要某段内存页的时候,请求调页,将内存页从外存中调入内存,在请求分页系统中,每当要访问的页面不在内存的时候,就会产生一个缺页的中断,请求中断处理函数将在外存中的页调入内存中。

一般来说在处理中断的时候需要经过以下的步骤:

  • 保存现场,也就是将当前进程的一些状态保存到PCB,比如说什么基地址寄存器,页表寄存器等,把这些东西存储到PCB的头部里面
  • 解析中断号,从中断向量表中查看需要调用什么中断函数
  • 执行中断处理程序,将页面调入内存
  • 恢复进程,将PCB首部的值恢复到寄存器中
  • 将进程恢复到就绪态

在指令的执行期间产生和处理中断信号,而非一条指令执行完毕后才处理中断。

虚拟内存的地址转换流程如下:首先是先从当前的页表寄存器中取出来页表的首地址,然后根据虚拟地址计算出来的页号,用页号*页表项的大小,然后用这个数值+页表的首地址,然后就是这一个虚拟地址所对应的物理页的地址(第一次访问内存完毕)

然后拿着这个物理页的地址,拼接虚拟地址的偏移量,就是实际的物理地址,然后用这个物理地址去访问对应的内存(第二次访问内存完毕)

那么这此处的MMU中也引入了TLB,这个TLB存储了对应的页号的信息,它相当于一层缓存,将内存中的页表项的数据存储到了TLB中,这样的话就减少了一次访问内存的时间

然而引入TLB的代价是需要多一层的数据一致性的维护,比如说:当MMU启动数据的查询之后,它会先查询TLB中是否有数据?如果TLB中没有数据,那么就回去查找页表,当页表中也没有数据的时候,就会发起一个缺页的中断,这时候会将寄存器中的值保存到PCB中,然后根据中断号从中断向量表中获取中断处理程序的入口,然后执行中断处理程序,替换通知磁盘读取该页,CPU启动磁盘IO,将这一页读取到内存中,如果内存满了,那么就会执行页面的置换,修改被置换页的驻留位P,修改置换页的驻留位P,访问位A,将该页植入到内存中。恢复PCB,继续执行。

如果内存有而TLB中没有会怎么样?会将内存写入到TLB中,如果TLB的空间不足则会执行页面的置换

页面置换算法有哪些?

首先第一种是最佳置换算法OPT,这种算法仅在理论上提出,实际上无法做到,其核心的思路就还是淘汰永不使用或者下次访问间距当前时间最长的页面

置换策略:LRU算法,叫做最近最少使用算法,这种算法的目的是从内存中淘汰掉目前驻留集中最少使用次数的页面,实现的思路是为每一页添加上次访问时间戳,它的理论基础是最近使用过的页面很有可能会被再次使用,而过去很久没有使用过的内存在短期内不会再被使用

FIFO算法:将页面按调度内存的时间先后排成一个队列,每次都淘汰队首页,性能比较差的

CLOCK时钟轮转算法,每个页表项设置一个使用位U,某个进程的所有页面排成一个循环缓冲链。

有一个指针从开头开始指向,具体的算法是:置换的时候顺序查找这个链,当U=0 的时候证明这一页在最近没有使用过,那么这时候就把它替换掉,当U=1的时候,就先放过它,但是会将U=0,如果下次到来还是U=0,那么就会替换掉这一页,通常还可以加入一个修改位进行辅助

如果无法替换页怎么办?最终就会导致OOM

如果最近没有使用,而且没有修改,那么也就是说这一页,既不需要刷回数据库,在缓存中也不需要,把它淘汰掉没有任何代价。

如果最近没有使用,但是被修改过了,那么也就是说这一页,需要刷回数据,具有一定的代价,需要将其淘汰掉。


文章作者: Fuchanglai
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Fuchanglai !
赏
  目录