仅用于站内搜索,没有排版格式,具体信息请跳转上方微信公众号内链接
作为一名1960年代中期的系统工程师,在那个年代,你只有一个体会,内存是真的贵。
那时引以为傲的System/360大型机虽然配备了豪华的256KB物理内存(价格相当于今天的数百万美元),但在引入多进程后内存相关的问题开始出现,因为多个进程可以同时运行在内存中。
你面临的核心问题是:如何保证多进程能够高效共享有限的物理内存?
你的第一个尝试是最直观的方法,将物理内存划分为几个固定大小的区域,每个区域分配给一个程序:
这就是所谓的固定分区(FixedPartitioning),这个想法很简单,你很快实现了这个机制:
这个简单的分区系统确实解决了一些问题。它允许多个程序同时驻留在内存中,并提供了基本的内存隔离。然而,它很快就暴露出了严重的缺陷。
问题出在内存利用率上:一个只需要10KB内存的小程序占用了整个64KB的分区,而一个需要70KB的程序却无法运行,因为没有任何一个分区足够大,尽管系统中空闲内存超过了70KB!
你意识到,固定分区虽然简单,但极其浪费内存资源。
它无法适应程序大小的变化,也无法解决运行大型程序的问题。
这个方案本质就是吃大锅饭,不管你可执行程序本身有多大都给你固定内存,打破大锅饭的最佳方法就是按劳分配。
既然是按劳分配那就不能预先划分内存,而是根据程序的实际需求动态分配内存块,用多少给多少:
这就是你在数据结构课上学到的链表。
动态分区确实提高了内存利用率,程序可以获得刚好满足其需求的内存量,这种内存分配方法开始流行起来。
然而,随着系统运行时间的增长,大量用户开始反馈物理内存很快耗尽导致程序崩溃,一通debug后你发现了问题:内存碎片。
只需要几周的运行,系统中就会出现了大量的小内存块,它们分散在各处,虽然总和足够大,但没有一个连续的块能满足新程序的需求。
更糟糕的是,即使使用动态分区,仍然无法运行那些需要超过物理内存总量的程序。
因为在20世纪60-80年代,虽然计算机物理内存有限(如KB级别),但程序规模却在逐渐增大(如大型科学计算、数据库系统),这是一个根本性的限制,你需要一种全新的思路…
面对内存不足的问题,你开始思考,既然内存一次性装不下大型程序,那么为什么不把这个大型程序拆开了、用到哪些就装哪些呢?
看上去好像能解决问题,你进一步思考,程序其实可以被划分为多个独立的功能模块,一些核心的模块可能需要始终驻留在内存(如主控制逻辑、核心函数),而非核心的功能模块可以按需动态加载到共享内存区域,覆盖前一个模块。
假设可执行程序A划分为一个核心模块和4个功能模块,那么当需要运行模块1时就把模块1加载到共享内存区域,当需要运行模块2时就把模块2加载到共享内存中覆盖掉原来的模块1:
这样就能实现在有限的物理内存中运行超大程序的目的,这就是早期操作系统中的”覆盖技术”(Overlay)。
这种方法要求程序员手动将程序分割成多个模块,并在运行时根据需要将不同模块加载到同一块内存区域。
覆盖技术确实突破了物理内存限制,可以在有限的物理内存上运行大型程序,是一种非常聪明的方法。
但它有严重的缺点:
程序员必须手动管理内存,这极其复杂且容易出错
程序必须预先知道哪些模块可以共享内存区域
频繁的模块加载会导致性能下降
这让你开始认识到:内存管理太重要了,绝不能完全依赖程序员自己手动管理。
因此你需要一个系统级的解决方案,能够自动管理内存,对程序员透明,同时允许程序使用超过物理内存的地址空间,这就是后来的虚拟内存技术。。。
推荐一下我写的专栏《深入理解操作系统》,第二版焕新升级,600+精美手绘图、87节精讲,如果你对操作系统感兴趣并且喜欢这篇文章的风格,那么这个专栏就是为你准备的: