Loading

虚拟内存

引出

众所周知, 在早期, 操作系统还没有分时的概念, 当时都是单进程执行, 只有一个进程结束了, 才能执行后一个进程. 但是这样的执行很容易想到的一个问题, 若进程在空闲状态, 则 CPU 就空下来了, 造成无谓的浪费. 后来为了解决这个问题, 于是进程可以主动申请轮换, 将当前时间交由其他进程. 但若是一个进程一直不出让控制权的话, 又退回到之前的情况了. 于是有了现在的分时系统, 即每个进程执行一小段时间, 然后强制切换到另一个进程执行, 由于切换的时间很短, 视觉上造成了很多进程同时执行的错觉.

但是, 当允许同时运行多个进程的时候, 就出现了新的问题, 内存.

内存映射方法

假设你现在共有内存128m, 程序 A 运行需要100m, 程序 B 需要10m. 当分别运行的时候, 内存是足够的, 若同时运行程序 A 和 B, 将内存的0-100分给 A, 100-110 分给 B, 也够. 但是这种简单的内存分配有几个问题:

  1. 内存空间不隔离. 在早期的程序都是直接访问内存的, 同样也能够访问到相邻的地址, 恶意程序就有可能篡改其他程序的数据.
  2. 内存使用效率低. 因为运行程序的时候, 需要将整个程序全部读到内存中, 而其中很多内容其实并不会被访问到.
  3. 运行地址不确定. 因为早期程序使用汇编编写, 读取数据一般通过地址硬编码的.
  4. 不可同时运行总内存超出128m 的程序.
  5. 等等吧

于是衍生成了虚拟内存的技术, 虚拟内存将内存存储在磁盘中, 待到需要的时候再读取到物理内存中.

分段

计算机中的一切问题, 都可以通过增加一个中间层来解决.

为了解决内存空间的隔离问题, 通过在程序与内存中添加一个中间层来解决. 于是, 将每一个程序的内存, 分别和物理内存进行映射, 如下:

image-20210123154807909

操作系统维护着这样一个虚拟内存到物理内存之间的映射关系, 进程访问的地址通过映射, 转换为实际的物理地址.

这样, 当操作系统检测到访问的内存超出范围时可以进行干预. 同时, 映射关系对于进程来说是透明的, 每个进程不需要关心实际物理内存的变化, 只需要认为地址从0开始即可. 使用分段的操作, 解决了内存空间不隔离运行地址不确定的问题.

但是, 分段操作也存在这一定的问题:

问题1. 不可同时运行100mb 的进程

若总内存100mb, 进程 A 占用50mb, 进程 B 占用30mb, 此时想运行进程 C 需要50mb内存,因为剩余内存不足, 将进程 C 读入物理内存后, 势必会覆盖其他进程的内存空间. 这又该如何是好呢?

其实也是有解决方法的, 那就是利用磁盘. 在进程 C 运行期间, 进程 A 肯定是不会得到执行的, 只需要暂时将进程 A 放到磁盘中, 就可以将进程 C 装入内存了. 之后再重复这个操作, 就可以重复执行进程 A 了. 但是磁盘的速度是十分缓慢的, 效率较低

问题2.内存碎片严重

可以看到, 不同进程之间是存在内存碎片的. 如果内存中剩余空闲空间50mb, 但因为碎片的存在, 此时也无法申请一块50mb的连续内存.

当然, 内存碎片也是提出过解决方案的, 和 GC 的标记复制思路一样, 将进程挪动到一块连续的地址空间中, 就可以将碎片连起来了, 但是内存复制的开销着实有点大.

以上..

试想一下, 现在运行两个进程, 每个占用内存100m, 那么每次切换进程时几乎都要重写整个内存. 而这个切换操作对于分时系统来说又十分频繁. 根据程序的局部性原理, 在一段时间内仅使用了一小部分数据. 也就是说, 如果分别给两个进程50m 的空间, 就可以满足一段时间内的运行需要的所有数据, 大大避免了内存的频繁置换.

分页

于是人们想到, 如果在上面的基础上将虚拟内存再切割成一个一个小块, 用到哪块读哪块, 岂不是就解决这个问题了么. 于是有了这样的模型:

image-20210123182639758

进程能够看到的仍然只有虚拟内存, 不过, 操作系统将虚拟内存按照4k(比如) 的大小分成了很多块, 每一块称为一页. 其维护了虚拟内存中每一页到物理内存的映射关系, 这样就可以做到, 只将目前需要的部分内容读取到内存中.

同时, 可以针对页设置读写权限, 仅特定的进程可以对页进行读或写的操作, 非法读写会被系统捕捉到.

另外这种虚拟内存到物理内存转换, 是可以通过硬件支持的, 及内存管理单元MMU. CPU 将虚拟地址, 通过MMU转换后, 得到物理地址进行访问. MMU在进行地址翻译的时候, 会在物理内存中读取查询表来动态翻译地址, 而这张查询表是由操作系统进行维护的. 若读取时, 发现还没有读到物理内存内存中, 则交由操作系统将其读取到物理内存中, 并更新查询表.

因为有了虚拟内存的存在, 才可以在一个物理内存128m 的机器上, 运行需要内存200m 的进程, 虽然相比直接运行在物理内存上, 速度上要有一些牺牲. 在32位机器上, 虚拟内存最大为4G.

分页技术也是现在的操作系统使用的技术, 可以看到, 在进程看来连续的内存, 在物理内存中不一定连续. 也就是说你定义的数组, 可能分布在物理内存相隔很远的两个地方.


最后, 其实在虚拟内存出现之前, 内存分页就已经存在了

posted @ 2021-01-23 18:32  烟草的香味  阅读(67)  评论(0编辑  收藏  举报