一文理解操作系统虚拟内存

原文地址An introduction to virtual memory

计算机是用来执行简单任务的复杂机器:比如 上网、文本编辑、网页服务、视频游戏……,还可以对数据进行操作,图片 音乐 文本 数据库……

当计算机不使用的时候,程序和数据都安静地躺在磁盘里,即便你关机了数据也会在。运行一个应用就是让处理器(CPU)读取和执行程序代码的机器指令处理数据。

磁盘可以保存大量的信息,但存取的时候都非常非常慢,比CPU慢得多,如果CPU直接从磁盘中读取指令,显然会成为整个系统的性能瓶颈。为此,主存/内存(RAM)就诞生了,内存是比磁盘小,但读写速度快得多的存储设备。应用运行时其程序和数据首先拷贝到内存中,这样处理器就可以在内存中读写数据,从而避免了大量的等待。

主存可以看作是一个很长的单元格列表,每个单元格包含一些二进制数据,并用一个称为内存地址的数字进行标记。根据系统中可用的主存数量,内存地址的范围从0到N。程序使用的地址范围称为地址空间。

在这里插入图片描述

早期计算机的内存使用

在早期的计算机中(现在也见于某些嵌入式系统),程序是可以访问整个内存空间的,内存的管理也得由程序员自己实现。在这种类型的计算机上写程序是一种挑战,因为程序员得找到一种好的内存管理方式,以确保各程序之间内存不会覆盖和干扰。
问题在多任务的时候更复杂,因为程序员必须面对更严峻的问题?

  1. 内存布局 —— 当第一个程序分配走特定数目的内存空间后,可用内存初始范围将不再是0-n了,开发者得妥善处理内存偏移。
  2. 内存分段。当内存被不断地分配回收之后,可用空间会逐渐变成越来越小的碎片,会越来越难以找到整块的空间分配给新的程序或者数据。
  3. 安全性。 如果程序A不小心覆盖了程序B的数据?或者有人故意从其他进程中读取敏感数据,比如密码和信用卡信息?

所以在1960年代初期,找到一种能自动管理内存方式尤为关键,这可以大幅度简化代码编写,并修复潜在的内存问题。最终诞生了我们今天要说的虚拟内存

虚拟内存简介

在虚拟内存中程序并不直接访问物理内存,而是和虚拟内存地址空间交互。操作系统和处理器将虚拟内存地址转化为物理内存地址。

进程每次的内存的读写都是在虚拟内存地址之上的,虚拟地址并不执行特定的物理地址,所以每次内存访问时程序并不知道硬件层面发生了什么。
在这里插入图片描述

虚拟内存的优点

在上图中我们可以看到虚拟地址和物理地址之间的映射关系,这种映射关系带来了两个好处。

  1. 每个程序都可以有一个从0开始的虚拟内存地址空间,这大大简化了程序猿的编码,因为不需要再去手动维护内存地址的偏移了。
  2. 即便物理内存地址不连续但虚拟内存地址可以做到总是连续的,这样操作系统算是间接完成了将内存碎片合并成一块可用内存的艰巨工作。

虚拟内存机制也解决了内存有限的问题,因为操作系统可以给每个进程分配比实际内存大的多的虚拟内存空间。另外虚拟内存也可以保证安全性,程序A无法在不触发操作系统错误的情况下读取到程序B的数据,下文中我们将会介绍这一切是如何实现的。

分页

虚拟内存机制需要一个地方来存储虚拟地址和物理地址之间的映射关系,因为我们需要将虚拟地址X转化为物理地址Y,当然你不能用1:1的映射,因为这样的映射关系数据将和实际内存一样大。

现代虚拟内存将多个固定大小的整块物理内存合并成一个列表管理,解决了上述映射关系过大的问题,这种实现方式就叫做分页。其中每一块在虚拟内存中叫页面在物理内存中叫页框,每一个页面和页框是对应的。CPU的内存管理单元(MMU)以一种叫做页表的特殊数据结果存储这虚拟页框到物理页框的映射关系。页表好比是有个数据库,每一行都存储这页号+页框对应的物理内存地址。每个进程在MMU中都会有自己的页表,如下图。
在这里插入图片描述

页表到页框的转化

虚拟内存地址由两部分组成

  1. 页号(页索引),标识这个虚拟内存地址属于哪个页面。
  2. 页内偏移,标识这个地址在页框中的具体位置。
    这些信息足够MMU将一个虚拟地址转化为物理地址了。当一个进程读写一个虚拟地址时,它先唤醒MMU从虚拟地址中截取出页号并根据页表找到相应的页框,当页框根据页内偏移计算出实际的物理地址,到这里转化就完成了。这时候程序就有了一个实际可读写的物理内存地址。

虚拟内存的背后

当程序有了连续、整洁的虚拟内存空间后,操作系统和硬件在后台对物理内存做一些很疯狂的事了。
例如:操作系统的延时加载,数据并不是在程序开始运行前就加载数据,而是等到程序实际需要使用时才加载。所有你会发现有些时候可能某个程序的页面对应一些不存在的页框或者是还没有分配的页框。比如上图中的最后两个页面就没有指向任何页框。

像这样的取巧的手段对应用程序是完全透明的,它保持读取和写入自己的虚拟地址空间而不受背景噪音的影响。但是,程序迟早要访问一个没有映射到RAM的虚拟地址:该怎么办

缺页错误(中断)

缺页中断发生于当程序尝试去访问一个没有映射到物理页框的虚拟地址时。更准确地讲,缺页中断发生于程序访问一个虚拟内存地址存在但在物理内存中没有对应地址的情况。

当MMU检测到缺页中断后会将中断信息转交给操作系统,操作系统会尝试去找到虚拟地址到物理地址的映射,大多数情况下这个是一个很简单的操作,除非物理内存已经耗尽。

分页,当物理内存不足时如何实现?

分页也带来一个其他的好处。当物理内存不足时,操作系统可以把部分页面写入到磁盘中腾出空间。尽管不够百分百准确,但这种方法有时也叫做swapping(交换),Swapping其实是把整个进程都挪到磁盘中,当然现在有些操作系统在必要的时候也会这么做。

分页给了程序一种有无限可用内存的假象。操作系统乐观地允许一个比物理内存更大的虚拟内存地址空间,因为在需要的情况下数据可以被换进和换出硬盘。有些系统(例如Windows)会使用一个称为分页文件的特殊文件来达到这个目的。其他操作系统(例如Linux)有一个专用的硬盘分区,称为交换分区(由于历史原因,现代Linux执行分页而不是交换)。

抖动

当操作系统花更多的数据在执行分页而不是应用程序的时候就会发生抖动,一般是由一系列的缺页中断导致的。这种情况极易发生在当你运行大量超过物理内存大小的程序时或者硬盘交换分区没有做优化时。这时候操作系统会努力执行大量的缺页中断,持续把数据从硬盘中移动到物理内存中,最终可能让系统卡住。解决方法是加大内存或者减少进程数量或者调整交换分区大小。

内存保护

虚拟内存也提供了跨进程的安全性。你的浏览器无法在不侵入操作系统的情况下窥探你文本编辑器里的内容,因为它无法访问不属于自己的内存空间。
内存保护机制是由MMU和其管理的页表实现的,也许其他硬件有不同的实现策略。当程序试图访问不属于它的虚拟内存时,会触发invalid page 错误。MMU和操作系统捕捉到这个信号,并引发一个名为段错误(segmentation fault)(Unix)或无效访问(access violation)(Windows),操作系统然后就会直接杀死这个进程。

段错误和无效访问可能会程序错误而产生。能够手动管理内存的编程语言允许你自己管理一部分内存用来存储程序数据,操作系统会给你划分出一段空闲内存(又名缓冲区),以便根据你的程序需要进行读写。但是,没有什么可以阻止你在缓冲区边界之外读写,访问不属于您的程序或根本不存在的内存时,操作系统就会报出非法访问的信号。

更多内容

虚拟内存的技术为很多有趣的课题铺平了道路,比如内存文件就颠覆了传统的文件读取方式,传统的文件读取方式是把文件拷贝到内存里,取而代之内存映射的方式是把整个文件都加载到内存后直接在内存里操作。在必要时,虚拟内存机制将像往常一样负责将数据从硬盘驱动器移动到RAM。内存映射文件简化了程序员的工作也加快文件访问。更多信息参考这里

虚拟内存也让统计内存消耗变得更加困难。假设你的一个程序占用了300m的内存:它是虚拟的还是物理的?该空间的一部分是否分页到磁盘?如果是,分页操作是否足够快?此外,如果您想使系统处于良好状态,那么调优分页文件/交换区域是一个重要的步骤。操作系统提供了许多度量和调整内存的工具:点击这里查看。

参考资料

Computer Hope — Memory
Peter J. Denning — Before memory was virtual
Android Authority — What is virtual memory?
Kernel.org — Memory Management
Operating Systems: Three Easy Pieces — Chapter 18: Paging
Philippe’s Oppermann — Introduction to Paging
Computer Science from the Bottom Up — Chapter 6. Virtual Memory
Dr. John T. Bell — Operating systems, Virtual Memory
StackOverflow — Do modern OS’s use paging and segmentation?
StackOverflow — What is thrashing? Why does it occur?
Wikipedia — Memory address
Wikipedia — Paging
Wikipedia — Address space
Wikipedia — Virtual memory
Wikipedia — Virtual address space
Wikipedia — Thrashing
Wikipedia — Segmentation fault
ITPro Today — Paging Performance
Aleph One — Smashing The Stack For Fun And Profit

xindoo CSDN认证博客专家 Linux 分布式 Spring
一个有趣有料的程序猿,9年技术博主,曾在阿里做过3年运维相关工作,现为某厂Java后端开发工程师,拥有丰富的挖坑踩坑填坑背锅经验[狗头],专注于Java,对操作系统、网络、编译原理也有涉猎,正在写一门编程语言和一部科幻小说。
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页