你有没有同时打开过好几个程序 浏览器开十几个标签页 微信挂着 VS Code在写代码 音乐在后台放着 你的电脑可能只有8GB或16GB内存 但所有程序加起来占用的空间早就超过了这个数字 电脑怎么还没崩 答案就是虚拟内存
一个精巧的骗局
虚拟内存(Virtual Memory)的本质是操作系统对程序撒的一个谎 它告诉每个程序:整块内存都是你的 随便用
程序信了 于是它开始放心地分配空间 完全不知道旁边还跑着十几个兄弟程序也在做同样的事 操作系统在背后默默调度 确保它们不会互相踩到对方的脚
32位系统下每个程序能看到的虚拟地址空间是4GB(2的32次方) 64位系统下这个数字大得离谱——16EB(exabytes) 远超你物理内存的总量 这就是第一个谎言:我给你的比你实际拥有的多得多
地址翻译器
程序用的是虚拟地址 电脑里的物理内存(RAM)用的是物理地址 中间需要一张对照表来翻译 这张表叫做页表(Page Table)
操作系统把虚拟地址空间切成固定大小的块 叫做页(page) 通常是4KB一块 物理内存也被切成同样大小的块 叫做页框(page frame) 页表记录了每个虚拟页对应哪个物理页框
当程序访问一个虚拟地址时 CPU里的内存管理单元(MMU)会自动查页表 把虚拟地址翻译成物理地址 这个过程硬件完成 程序毫不知情
缺页中断:程序的等等让我去拿
如果程序访问的虚拟页在页表里找不到对应的物理页框呢 这时候会发生缺页中断(Page Fault)
操作系统收到这个中断后 会做以下几件事:暂停当前程序 在磁盘上找到这块数据 如果物理内存满了 先把某块不常用的数据写回磁盘(换出) 把需要的数据从磁盘读到物理内存(换入) 更新页表 让程序继续执行
从程序的角度看 这一切毫无感知 它只是正常访问了一个地址 稍微多等了一小会儿 但它不知道 在它被暂停的那几毫秒里 操作系统在磁盘和内存之间搬了一大块数据
局部性原理:为什么这个骗局行得通
虚拟内存能工作 靠的是程序的一个天然特性——局部性原理(Principle of Locality)
时间局部性:刚刚访问过的地址 很可能很快又被访问 比如循环里的变量 空间局部性:刚访问过的地址附近的地址 很可能也会被访问 比如遍历数组 遍历完索引i 下一个就是i+1
操作系统利用这个原理做预读取 当你访问了第一个数据页 它会把相邻的几页也提前加载进来 下次访问附近数据时 不用再去磁盘拿了
这就是为什么你的电脑虽然同时跑着几十个程序 但大部分时间里 每个程序只用了它虚拟空间里很小的一部分 操作系统只需要在物理内存里维护这些热点页面就够了 其余部分躺在磁盘上 用到时再换进来
隔离与保护
虚拟内存还有一个极其重要的功能:隔离
每个程序都有自己独立的虚拟地址空间 程序A不可能知道程序B的内存里有什么 更不可能修改它 因为A的虚拟地址0x1000和B的虚拟地址0x1000 翻译到物理内存上是完全不同的两个位置
这种隔离机制是现代操作系统安全的基石 一个崩溃的程序不会把整个系统拖垮 一个恶意程序也不能直接偷看别的程序的内存
写在最后
虚拟内存是操作系统最成功的骗局之一 它让每个程序都以为自己是这栋楼的唯一住户 事实上你和几百个房客共享着一个沙丁鱼罐头般的空间 但因为有操作系统这个尽职的二房东 所有人都住得舒舒服服的
下次打开十几个标签页也不卡的时候 可以默默感谢一下那个在后台帮你搬数据的操作系统 它在替你撒谎 而谎言让你觉得世界足够大