虚拟内存

引出

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

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

内存映射方法

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

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

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

分段

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

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

image-20210123154807909

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

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

如此, 同时解决了上面的问题1, 3, 4. 但并没有解决内存使用效率低的问题. 试想, 如果此时再运行一个需要50m 内存的进程 C, 因为剩余内存不足, 将进程 C 读入物理内存后, 势必会覆盖其他进程的内存空间. 这时, 要先将程序 A 的内存写回磁盘以便下次运行, 再将进程 C 从磁盘写入内存. 之后切换回进程 A 的时候, 又要重复刚才的整个操作.

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

分页

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

image-20210123182639758

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

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

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

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

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

------------

原文地址 https://hujingnb.com/archives/390

转载请保留原文连接: 虚拟内存 | 烟草的香味

guest
0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请发表评论。x