上一篇介绍完了内存分页,接下来就是内存管理的重头戏——虚拟内存了。
在正式介绍虚拟内存之前,先简单地介绍一下相关技术的发展过程。虽然我们的物理内存相较于过去已经变大了很多,但是程序所要占用的内存也一直在增大,而且增长速度甚至超过了物理内存的扩大速度。所以科学家们经常会想,有没有办法在有限的内存条件下,能够运行尽可能多的程序。
于是慢慢发展出了覆盖技术,再到交换技术,再到我们现在所使用的虚拟内存技术。
把程序按照其自身逻辑结构,划分为若干个功能上相对独立的程序模块,那些不会同时执行的模块共享同一块内存区域,按时间先后来覆盖运行,从而共用一个分区。
缺点:
以进程为交换的单位,需要把进程的整个地址空间换入换出,增加了处理器的开销。
缺点:
在内存分段或分页的基础上,通过将内存中暂时不使用的页或段调出保存在外存上,从而腾出更多空间存放将要装入的页或段。
虚存技术 = 内存分页(或分段)+ 请求调页 + 页面置换
虚拟内存的理论大小 = min(内存+外存,CPU寻址范围)
虚拟内存的代价:
由于程序的局部性原理,在某一时刻,内存中会存在许多暂时不使用的页,如果能将这些页先换出内存暂存,等需要时再换入,就可以使用更大的内存空间。相当于是拿时间换空间。
程序的局部性原理(Principle of locality):程序在执行过程中的一个较短时期,所执行的指令地址和指令的操作数地址,分别局限于一定区域。
大部分虚拟存储系统都采用虚拟页式内存管理技术,即在页式存储管理的基础上,增加请求调页和页面置换功能。
内存分页管理和请求调页(即缺页中断)都在上一篇中讲过了,这里就不再赘述了。接下来讲一下页面置换的实现。
页面置换分为局部和全局两种:
局部页面置换只针对一个进程来进行操作。通过以下几种算法实现,常见的有以下几种:
最佳页面置换算法:OPT(Optimal),置换在未来最长时间不访问的页面。是理想中的算法,在实际系统中难以实现(无法准确预测未来的使用情况)。
先进先出置换算法:FIFO,First In First Out,选择在内存驻留时间最长的页面进行置换。可以用一个先入先出的队列实现。
最近最少使用置换算法:LRU,Least Recently Used,选择最长时间没有被访问的页面进行置换。可以使用链表或者栈实现。
最近最不常用置换算法:LFU,Least Frequently Used,选择一段时间内访问次数最少的页面进行置换。实现:对每个页面设置一个访问计数器,访问时加一,在发生缺页中断时,淘汰计数值最小的页。也可以用定期位移的办法。
时钟页面置换算法:Clock,将页表组织成环形链表,并把指针指向最老的页面(依靠页表项中的访问位,最近被访问过为 1,否则为 0)。
二次机会算法:在时钟算法的基础上,同时使用访问位和脏位来指导置换。只有两个位都为 0,才置换页面;若两个位都为 1,先将访问位置为 0。
局部页面置换算法都针对一个进程来进行操作的,然而OS可以同时执行多个程序,如果每一个程序都采取一个固定的局部页面置换算法会带来一些问题(并发的程序访问会降低程序的局部性,从而导致缺页率上升和 CPU 利用率降低),所以引入了全局页面置换算法。
当前时刻,进程正在使用的逻辑页面集合,用二元函数 W=(t,△)
表示。t 为当前的执行时刻,△ 为工作集窗口。t 是一直变的,工作集窗口也一直在滑动。
当前时刻,进程实际驻留在内存中的页面集合。常驻集越接近工作集,表示发生缺页率越小。
随着工作集窗口的移动,只要页面不在工作集中,就置换出去。
PFF(Page Fault Frequency),根据缺页率,动态地调整常驻集的大小,从而使缺页率保持在一个合理的范围内。
工作集算法每一次访问都在考虑置换哪一个页面到外存;而缺页率算法只有在时间间隔够大的时候才置换到外存。
Thrashing,如果分配给一个进程的物理页面太少,不能包含整个的工作集,那么进程将会造成很多的缺页中断,需要频繁地在内存与外存之间替换页面,从而使进程的运行速度变得很慢。
Backing Store,指的是暂时存储从内存中换出的代码和数据。
虚拟内存空间分为用户空间和内核空间。
用户空间中的代码被限制了只能使用一个局部的内存空间,我们说这些程序在用户态(User Mode) 执行。内核空间中的代码可以访问所有内存,我们称这些程序在内核态(Kernal Mode) 执行。
应用程序如果需要进入内核空间,就需要通过系统调用。
虽然每个进程都各自有独立的虚拟内存,但是每个虚拟内存中的内核地址,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。
用户空间的分布情况:
用户空间内存,从低到高分别是 6 种不同的内存段:
上述几种内存区域中数据段、BSS 段、堆通常是被连续存储在内存中,在位置上是连续的,而代码段和栈往往会被独立存放。
代码段、数据段、BSS 段的大小固定。
在 BSS 段上方有一个随机的 gap 区域,
在这 6 个内存段中,堆和文件映射段的内存是动态分配的。比如说,使用 C 标准库的 malloc()
或 mmap()
,就可以分别在堆和文件映射段动态分配内存。
为了在有限的内存空间中尽可能地运行更多的程序,渐渐地发展出覆盖技术、交换技术以及目前所使用的虚拟内存技术。
虚拟内存技术是在段页式管理(以 Linux 为例)的基础上加上页面置换实现的。页面置换算法有局部和全局两种,其中局部页面置换算法只考虑单个进程的使用情况,而全局页面置换算法则考虑所有可置换的页面。
虚拟内存空间分为用户空间和内核空间,内核空间只有操作系统可以使用,且是进程间共享的;而用户空间是操作系统分配给用户使用的,是进程私有的。用户空间又依据类型分为了六种不同的内存段供用户使用。