操作系统学习笔记 | 5. 虚拟内存
虚拟内存的起因——内存空间不足
随着程序规模的增长,其对内存的需求远远超过了物理内存的发展速度。因此,我们希望能在有限的内存中运行更多程序,而不受物理内存容量的限制。
理想的存储器应当具备以下特性:
更大:能够存储更多数据和程序。
更快:提供更高的访问速度。
更便宜:降低存储成本。
非易失性:断电后数据仍然可以保存。
但实际存储器的情况如下:
内存(RAM)速度快但价格昂贵,容量有限。
硬盘(HDD/SSD)价格低廉但速度较慢。
因此,我们可以引入硬盘作为存储层级的一部分,在操作系统的管理下,利用硬盘的空间来扩展可用内存。
解决内存不足的方案
主要有以下三种方式:
覆盖技术(Overlay):仅将当前需要执行的部分装入内存,程序员手动管理。
交换技术(Swapping):将不活动的进程换出到硬盘,系统自动管理。
虚拟存储技术(Virtual Memory):以更小的粒度进行内存管理,由操作系统负责调度。
覆盖技术
目标:在较小的可用内存里运行较大的程序,通常用于多道程序系统,与分区存储管理配合使用。
原理:
把程序划分为多个独立的模块,并确保不会同时执行的模块共享同一块内存区域。
常用代码和数据常驻内存,不常用部分存放在外存,需要时才加载。
互不依赖的模块可以动态加载和覆盖,以节省内存。
缺点:
开发复杂度较高,需要程序员手动划分模块。
运行时频繁从外存加载模块,导致性能下降(以时间换空间)。
交换技术
目标:多个程序共享内存资源,尽可能让更多的进程运行。
方法:
换出(Swap-out):把暂时不运行的进程保存到外存,释放内存空间。
换入(Swap-in):当进程需要执行时,再从外存加载回内存。
示意图如下:
关键问题:
交换时机:尽量减少交换次数,仅在内存不足时进行交换。
交换区域大小:必须足够大,以存储进程的完整地址空间。
地址重映射:进程换入内存后可能位于不同的地址,需要支持动态地址映射。
覆盖 vs 交换
虚拟存储技术
覆盖和交换技术虽然能缓解内存不足的问题,但各自存在局限性:
覆盖技术需要程序员手动管理,开发成本高。
交换技术每次换入换出整个进程,开销较大。
目标:
无需手动划分,由操作系统自动管理。
只加载当前需要的部分,减少内存占用。
支持进程部分换入换出,减少交换开销。
核心思想——程序的局部性原理 程序在执行过程中,访问的指令和数据往往局限在一定范围内,称为局部性原理:
时间局部性(Temporal Locality):近期访问过的数据很可能在短时间内再次被访问。
空间局部性(Spatial Locality):一次访问的数据附近的内容很可能也会被访问。
示例代码对比:
// 写法 1(列优先访问)
for(int j = 0; j < 1024; j++){
for(int i = 0; i < 1024; i++){
A[i][j] = 0;
}
}
// 写法 2(行优先访问)
for(int i = 0; i < 1024; i++){
for(int j = 0; j < 1024; j++){
A[i][j] = 0;
}
}
分析:
写法 1 会导致 1024 × 1024 次缓存未命中(Cache Miss),因为每次访问的新列数据可能导致缺页。
写法 2 只会发生 1024 次缓存未命中,因为数据按行存储,能充分利用缓存。
虚拟页式内存管理
关键点:
请求分页:按需加载页面,减少内存占用。
页面置换:当内存不足时,选择替换某些页面。
页表项结构:
驻留位:表示该页是否在内存中。
保护位:标识可读、可写、可执行权限。
修改位:记录该页是否被修改过,决定是否需要回写外存。
访问位:记录该页最近是否被访问,辅助页面置换策略。
缺页中断处理流程:
检查是否有空闲页帧,有则直接分配,否则执行页面置换。
如果需要替换某个页面,先检查该页是否被修改过,如果是,则将其写回外存。
将新页面从外存读取到内存中。
更新页表,设置驻留位,并更新物理页帧号。
重新执行被中断的指令。
虚拟内存性能
有效存储器访问时间(EAT)
EAT = TLB 查找时间 + (1 - TLB 命中率) × 页表查找时间 + 缺页率 × 缺页处理时间
影响因素:
TLB(快表):提高页表查找速度,减少内存访问延迟。
页面置换策略:如 LRU(最近最少使用)等,减少缺页率。
预取策略:提前加载可能访问的页面,提高性能。
过度换页(Thrashing):如果缺页率过高,系统可能陷入不断换页的低效状态。
总结
虚拟内存通过分页管理,实现按需加载,减少内存占用。
操作系统通过TLB 加速地址映射,降低访存开销。
局部性原理是提高性能的关键,应尽量优化数据访问模式。
合理的页面置换策略能减少缺页,提高系统响应速度。