操作系统是如何一步步发明虚拟内存的?

那时引以为傲的System/360大型机虽然配备了豪华的256KB物理内存(价格相当于今天的数百万美元),但在引入多进程后内存相关的问题开始出现,因为多个进程可以同时运行在内存中。

你面临的核心问题是:如何保证多进程能够高效共享有限的物理内存?

最初的尝试:固定分区

你的第一个尝试是最直观的方法,将物理内存划分为几个固定大小的区域,每个区域分配给一个程序:

图片

这就是所谓的固定分区(Fixed Partitioning),这个想法很简单,你很快实现了这个机制:

复制
// 固定分区内存管理的简单实现 struct memory_partition { void* start_address; // 分区起始地址 size_t size; // 分区大小 bool is_occupied; // 是否被占用 int process_id; // 占用进程ID };1.2.3.4.5.6.7.

这个简单的分区系统确实解决了一些问题。它允许多个程序同时驻留在内存中,并提供了基本的内存隔离。然而,它很快就暴露出了严重的缺陷。

问题出在内存利用率上:一个只需要10KB内存的小程序占用了整个64KB的分区,而一个需要70KB的程序却无法运行,因为没有任何一个分区足够大,尽管系统中空闲内存超过了70KB!

你意识到,固定分区虽然简单,但极其浪费内存资源。

它无法适应程序大小的变化,也无法解决运行大型程序的问题。

这个方案本质就是吃大锅饭,不管你可执行程序本身有多大都给你固定内存,打破大锅饭的最佳方法就是按劳分配。

动态分区:按需分配

既然是按劳分配那就不能预先划分内存,而是根据程序的实际需求动态分配内存块,用多少给多少:

复制
// 动态分区内存管理 struct memory_block { void* start_address; // 内存块起始地址 size_t size; // 内存块大小 bool is_free; // 是否空闲 struct memory_block* next; // 链表中的下一个块 }; struct memory_block* free_list; // 空闲内存块链表1.2.3.4.5.6.7.8.9.

这就是你在数据结构课上学到的链表。

动态分区确实提高了内存利用率,程序可以获得刚好满足其需求的内存量,这种内存分配方法开始流行起来。

然而,随着系统运行时间的增长,大量用户开始反馈物理内存很快耗尽导致程序崩溃,一通debug后你发现了问题:内存碎片。

只需要几周的运行,系统中就会出现了大量的小内存块,它们分散在各处,虽然总和足够大,但没有一个连续的块能满足新程序的需求。

更糟糕的是,即使使用动态分区,仍然无法运行那些需要超过物理内存总量的程序

因为在20世纪60-80年代,虽然计算机物理内存有限(如KB级别),但程序规模却在逐渐增大(如大型科学计算、数据库系统),这是一个根本性的限制,你需要一种全新的思路...

覆盖技术:程序员的自我管理

面对内存不足的问题,你开始思考,既然内存一次性装不下大型程序,那么为什么不把这个大型程序拆开了、用到哪些就装哪些呢

看上去好像能解决问题,你进一步思考,程序其实可以被划分为多个独立的功能模块,一些核心的模块可能需要始终驻留在内存(如主控制逻辑、核心函数),而非核心的功能模块可以按需动态加载到共享内存区域,覆盖前一个模块。

假设可执行程序A划分为一个核心模块和4个功能模块,那么当需要运行模块1时就把模块1加载到共享内存区域,当需要运行模块2时就把模块2加载到共享内存中覆盖掉原来的模块1:

图片

这样就能实现在有限的物理内存中运行超大程序的目的,这就是早期操作系统中的"覆盖技术"(Overlay)。

这种方法要求程序员手动将程序分割成多个模块,并在运行时根据需要将不同模块加载到同一块内存区域。

复制
// 程序员使用覆盖技术的伪代码 void main() { // 主模块始终在内存中 // 需要模块A时 load_module("module_A", OVERLAY_REGION); execute_module_A(); // 需要模块B时,覆盖同一内存区域 load_module("module_B", OVERLAY_REGION); execute_module_B(); // 再次需要模块A时 load_module("module_A", OVERLAY_REGION); execute_module_A_again(); }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.

覆盖技术确实突破了物理内存限制,可以在有限的物理内存上运行大型程序,是一种非常聪明的方法。

但它有严重的缺点:

程序员必须手动管理内存,这极其复杂且容易出错程序必须预先知道哪些模块可以共享内存区域频繁的模块加载会导致性能下降

这让你开始认识到:内存管理太重要了,绝不能完全依赖程序员自己手动管理

因此你需要一个系统级的解决方案,能够自动管理内存,对程序员透明,同时允许程序使用超过物理内存的地址空间,这就是后来的虚拟内存技术。

THE END