虚拟存储器是利用主存和外存(磁盘)构建的逻辑上更大的存储空间。每个进程拥有独立的虚拟地址空间,通过地址转换映射到物理内存。它实现了内存保护、共享和自动管理。
程序使用的地址,每个进程有自己的虚拟地址空间。
实际内存中的地址,所有进程共享物理地址空间。
虚拟地址 → MMU(内存管理单元)→ 物理地址
虚拟地址 = [虚拟页号 VPN][页内偏移 Offset] 物理地址 = [物理页框号 PFN][页内偏移 Offset] 页内偏移位数 = log₂(页大小) 例:页大小 4KB → Offset = 12 位
每个进程有一个页表: 虚拟页号 → [物理页框号, 有效位, 修改位, 访问权限...] 页表项(PTE)内容: | 物理页框号 | V(有效位) | D(脏位) | R(访问位) | 权限 |
1. 用虚拟页号查页表 → 得到页表项 2. 检查有效位: - 有效位 = 1 → 取物理页框号,拼接偏移得物理地址 - 有效位 = 0 → 缺页异常(Page Fault) 3. 缺页处理: - 从磁盘调入页面到主存 - 更新页表 - 若主存满,需要页面置换
问题:单级页表太大(32 位地址 / 4KB 页 → 1M 个页表项) 解决:多级页表 二级页表: 虚拟地址 = [一级页号][二级页号][Offset] 第一级页表 → 指向第二级页表 第二级页表 → 指向物理页框 只加载需要的二级页表,节省空间
32 位虚拟地址,4KB 页,4B 页表项: 页数 = 2^32 / 2^12 = 2^20 = 1M 页表大小 = 1M × 4B = 4MB(单级) 二级页表: 一级页表:2^10 × 4B = 4KB(刚好一页) 二级页表:每级 2^10 × 4B = 4KB(按需加载)
虚拟地址 = [段号][段内偏移]
段号 → [段基址, 段长, 访问权限...] 段表项内容: | 段基址(Base) | 段长(Limit) | V(有效) | 权限 |
1. 用段号查段表 → 得到段表项 2. 检查:段内偏移 < 段长?(越界检查) 3. 物理地址 = 段基址 + 段内偏移
先分段,再在段内分页,结合段式和页式的优点。
虚拟地址 = [段号][页号][页内偏移] 转换过程: 1. 查段表 → 得到页表基址 2. 查页表 → 得到物理页框号 3. 物理地址 = 物理页框号 + 页内偏移
| 特性 | 页式 | 段式 | 段页式 |
|---|---|---|---|
| 分割单位 | 固定页 | 可变段 | 段内分页 |
| 碎片 | 内部碎片 | 外部碎片 | 内部碎片 |
| 共享保护 | 页级 | 段级 | 段级 + 页级 |
| 地址转换 | 1 次查表 | 1 次查表 | 2 次查表 |
| 实现复杂度 | 中 | 高 | 最高 |
| 实际应用 | Linux/Windows | 少用 | x86 架构 |
| 虚拟页号 VPN | 物理页框号 PFN | 有效位 | ASID |
1. CPU 发出虚拟地址 2. 用虚拟页号查 TLB - TLB 命中 → 直接得到物理页框号(1 个时钟周期) - TLB 未命中 → 查页表(可能多次访存) 3. 同时用物理页框号查 Cache 4. Cache 命中 → 取数据
| 组合 | 代价 |
|---|---|
| TLB 命中 + Cache 命中 | 最快(1–2 个周期) |
| TLB 命中 + Cache 未命中 | 访问主存 |
| TLB 未命中 + Cache 命中 | 先查页表(可能主存) |
| TLB 未命中 + Cache 未命中 | 最慢 |
有效访问时间 = 命中率 × (TLB时间 + Cache时间)
+ (1 − 命中率) × (页表访问时间 + Cache时间)
+ 缺页率 × 缺页处理时间
↑ 以上为站内 HTML 相对链接(纯网页可浏览);本页右上「在 Obsidian 中打开」跳回源笔记。