为什么这么设计(Why’s THE Design)是一系列关于计算机领域中程序设计决策的文章,我们在这个系列的每一篇文章中都会提出一个具体的问题并从不同的角度讨论这种设计的优缺点、对具体实现造成的影响。如果你有想要了解的问题,可以在文章下面留言。

操作系统中的 CPU 和主内存(Main memory)都是稀缺资源,所有运行在当前操作系统的进程会共享系统中的 CPU 和内存资源,操作系统会使用 CPU 调度器分配 CPU 时间[^1]并引入虚拟内存系统以管理物理内存,本文会分析操作系统为什么需要虚拟内存。

在回答虚拟内存存在的必要性之前,我们需要理解操作系统中的虚拟内存是什么,它在操作系统中起到什么样的作用。正如软件工程中的其他抽象,虚拟内存是操作系统物理内存和进程之间的中间层,它为进程隐藏了物理内存这一概念,为进程提供了更加简洁和易用的接口以及更加复杂的功能。

virtual-memory-layer

图 1 - 进程和操作系统的中间层

如果需要我们从头设计一个操作系统,让系统中的进程直接访问主内存中的物理地址应该是非常自然的决定,早期的操作系统确实也都是这么实现的,进程会使用目标内存的物理地址(Physical Address)直接访问内存中的内容,然而现代的操作系统都引入了虚拟内存,进程持有的虚拟地址(Virtual Address)会经过内存管理单元(Memory Mangament Unit)的转换变成物理地址[^2],然后再通过物理地址访问内存:

virtual-memory-system

图 2 - 虚拟内存系统

主存储是相对比较稀缺的资源,虽然顺序读取只比磁盘快 1 个数量级,但是它能提供极快的随机访问速度,从内存上随机读取数据是磁盘的 100,000 倍[^3],充分利用内存的随机访问速度是改善程序执行效率的有效方式。

操作系统以页为单位管理内存,当进程发现需要访问的数据不在内存时,操作系统可能会将数据以页的方式加载到内存中,这个过程是由上图中的内存管理单元(MMU)完成的。操作系统的虚拟内存作为一个抽象层,起到了以下三个非常关键的作用:

  • 虚拟内存可以利用磁盘起到缓存的作用,提高进程访问指定内存的速度;

  • 虚拟内存可以为进程提供独立的内存空间,简化程序的链接、加载过程并通过动态库共享内存;

  • 虚拟内存可以控制进程对物理内存的访问,隔离不同进程的访问权限,提高系统的安全性;

缓存

我们可以将虚拟内存看作是在磁盘上一片空间,当这片空间中的一部分访问比较频繁时,该部分数据会以页为单位被缓存到主存中以加速 CPU 访问数据的性能,虚拟内存利用空间较大的磁盘存储作为『内存』并使用主存储缓存进行加速,让上层认为操作系统的内存很大而且很快,然而区域很大的磁盘并不快,而很快的内存也并不大

virtual-memory-cache

图 3 - 虚拟内存、主存和磁盘

虚拟内存中的虚拟页(Virtual Page,PP)可能处于以下的三种状态 — 未分配(Unallocated)、未缓存(Uncached)和已缓存(Cached),其中未分配的内存页是没有被进程申请使用的,也就是空闲的虚拟内存,不占用虚拟内存磁盘的任何空间,未缓存和已缓存的内存页分别表示已经加载到主存中的内存页和仅加载到磁盘中的内存页。如上图所示,图中绿色的虚拟内存页由主存中的物理内存页(Physical Page,PP)支撑,所以它是已经缓存过的,而黄色的虚拟内存页仅在磁盘中,所以没有被物理内存缓存。

当用户程序访问未被缓存的虚拟页时,硬件就会触发缺页中断(Page Fault,PF),在部分情况下,被访问的页面已经加载到了物理内存中,但是用户程序的页表(Page Table)并不存在该对应关系,这时我们只需要在页表中建立虚拟内存到物理内存的关系;在其他情况下,操作系统需要将磁盘上未被缓存的虚拟页加载到物理内存中[^4]。

page-fault

图 4 - 虚拟内存的缺页中断

因为主内存的空间是有限的,当主内存中不包含可以使用的空间时,操作系统会从选择合适的物理内存页驱逐回磁盘,为新的内存页让出位置,选择待驱逐页的过程在操作系统中叫做页面替换(Page Replacement)。缺页中断和页面替换技术都是操作系统调页算法(Paging)的一部分,该算法的目的就是充分利用内存资源作为磁盘的缓存以提高程序的运行效率。

内存管理

虚拟内存可以为正在运行的进程提供独立的内存空间,制造一种每个进程的内存都是独立的假象,在 64 位的操作系统上,每个进程都会拥有 256 TiB 的内存空间,内核空间和用户空间分别占 128 TiB[^5],部分操作系统使用 57 位虚拟地址以提供 128 PiB 的寻址空间[^6]。因为每个进程的虚拟内存空间是完全独立的,所以它们都可以完整的使用 0x0000000000000000 到 0x00007FFFFFFFFFFFF 的全部内存。

virtual-memory-space

图 5 - 操作系统的虚拟内存空间

虚拟内存空间只是操作系统中的逻辑结构,就像我们上面说的,应用程序最终还是需要访问物理内存或者磁盘上的内容。因为操作系统加了一个虚拟内存的中间层,所以我们也需要为进程实现地址翻译器,实现从虚拟地址到物理地址的转换,页表是虚拟内存系统中的重要数据结构,每一个进程的页表中都存储了从虚拟内存到物理内存页的映射关系,为了存储 64 位操作系统中 128 TiB 虚拟内存的映射数据,Linux 在 2.6.10 中引入了四层的页表辅助虚拟地址的转换[^7],在 4.11 中引入了五层的页表结构[^8],在未来还可能会引入更多层的页表结构以支持 64 位的虚拟地址。

four-level-page-tables

图 6 - 四层页表结构

在如上图所示的四层页表结构中,操作系统会使用最低的 12 位作为页面的偏移量,剩下的 32 位会分四组分别表示当前层级在上一层中的索引,所有的虚拟地址都可以用上述的多层页表查找到对应的物理地址。

因为有多层的页表结构可以用来转换虚拟地址,所以多个进程可以通过虚拟内存共享物理内存。我们在 为什么 Redis 快照使用子进程 一文中介绍的写时复制就利用了虚拟内存的这个特性,当我们在 Linux 中调用 fork 创建子进程时,实际上只复制了父进程的页表。如下图所示,父子进程会通过不同的页表指向相同的物理内存:

process-shared-memory

图 7 - 进程间共享内存

虚拟内存不仅可以在 fork 时用于共享进程的物理内存,提供写时复制的机制,还能共享一些常见的动态库减少物理内存的占用,所有的进程都可能调用相同的操作系统内核代码,而 C 语言程序也会调用相同的标准库。

除了能够共享内存之外,独立的虚拟内存空间也会简化内存的分配过程,当用户程序向操作系统申请堆内存时,操作系统可以分配几个连续的虚拟页,但是这些虚拟页可以对应到物理内存中不连续的页中。

内存保护

操作系统中的用户程序不应该修改只读的代码段,也不应该读取或者修改内核中的代码和数据结构或者访问私有的以及其他的进程的内存,如果无法对用户进程的内存访问进行限制,攻击者就可以访问和修改其他进程的内存影响系统的安全。

如果每一个进程都持有独立的虚拟内存空间,那么虚拟内存中页表可以理解成进程和物理页的『连接表』,其中可以存储进程和物理页之间的访问关系,包括读权限、写权限和执行权限:

virtual-memory-permission

图 8 - 读权限、写权限和执行权限

内存管理单元可以决定当前进程是否有权限访问目标的物理内存,这样我们就最终将权限管理的功能全部收敛到虚拟内存系统中,减少了可能出现风险的代码路径。

总结

虚拟内存的设计方法可以说是软件工程中的常见手段,通过结合磁盘和内存各自的优势,利用中间层对资源进行更合理地调度充分提高资源的利用率并提供和谐以及统一的抽象,而在实际的业务场景中,类似的缓存逻辑也比较常见。

操作系统的虚拟内存是非常复杂的组件,没有工程师能够了解其中的全部细节,不过了解虚拟内存的整体设计也很有价值,我们能够从中找到很多软件设计的方法。我们重新回到今天的问题 — Linux 操作系统中为什么需要虚拟内存:

  • 虚拟内存可以结合磁盘和物理内存的优势为进程提供看起来速度足够快并且容量足够大的存储;

  • 虚拟内存可以为进程提供独立的内存空间并引入多层的页表结构将虚拟内存翻译成物理内存,进程之间可以共享物理内存减少开销,也能简化程序的链接、装载以及内存分配过程;

  • 虚拟内存可以控制进程对物理内存的访问,隔离不同进程的访问权限,提高系统的安全性;

到最后,我们还是来看一些比较开放的相关问题,有兴趣的读者可以仔细思考一下下面的问题:

  • 为什么每层的页表结构只能够负责 8 位虚拟地址的寻址?

  • 64 位的虚拟内存在操作系统中需要多少层的页表结构才能寻址?

如果对文章中的内容有疑问或者想要了解更多软件工程上一些设计决策背后的原因,可以在博客下面留言,作者会及时回复本文相关的疑问并选择其中合适的主题作为后续的内容。

图 书 推 荐

作者:田宇

定价:139.00元

  • 计算机操作系统原理实践指南

  • 基于 Intel Core i7 处理器的 64 位多核操作系统

  • 引入诸多 Linux 内核的设计精髓

  • 既可在 Bochs 虚拟机中执行,又可通过 U 盘引导运行于笔记本电脑

本书讲述了一个 64 位多核操作系统的自制过程。此操作系统自制过程是先从虚拟平台构筑起一个基础框架,随后再将基础框架移植到物理平台中进行升级、完善与优化。

为了凸显 64 位多核操作系统的特点,物理平台选用搭载着 Intel Core-i7 处理器的笔记本电脑。与此同时,本书还将 Linux 内核的源码精髓、诸多官方白皮书以及多款常用协议浓缩于其中,可使读者在读完本书后能够学以致用,进而达到理论联系实际的目的。

造个操作系统的轮子!

图是怎么画的

  • 技术文章配图指南

推荐阅读

  • 为什么TCP 会被 UDP 取代

  • 为什么0.1+0.2 != 0.300000004

觉得有用就点一下  ????????????

为什么 Linux 需要虚拟内存相关推荐

  1. 理解 Linux 的虚拟内存

    欢迎关注方志朋的博客,回复"666"获面试宝典 来源:https://zhenbianshu.github.io/ 虚拟内存 毋庸置疑,虚拟内存绝对是操作系统中最重要的概念之一.我 ...

  2. linux存储--虚拟内存详解MMU、页表(十)

    内存是程序得以运行的重要物质基础.如何在有限的内存空间运行较大的应用程序,曾是困扰人们的一个难题.为解决这个问题,人们设计了许多的方案,其中最成功的当属虚拟内存技术.Linux作为一个以通用为目的的现 ...

  3. linux虚拟内存当硬盘,linux里面虚拟内存和swap有什么不同?

    为了提高磁盘存取效率, Linux做了一些精心的设计, 除了对dentry进行缓存(用于VFS,加速文件路径名到inode的转换), 还采取了两种主要Cache方式:Buffer Cache和Page ...

  4. linux服务器虚拟内存设置,修改Linux服务器虚拟内存Swap大小

    Linux的Swap相当于Windows的虚拟内存,当物理内存不够的时候,就需要将物理内存中的一部分空间释放出来,以供当前运行的程序使用. 那些被释放出来的空间可能来自一些很长时间没有什么操作的程序, ...

  5. Linux内存管理:为什么 Linux 需要虚拟内存?为什么 Linux 默认页大小是 4KB?

    Table of Contents 为什么 Linux 需要虚拟内存? 缓存 内存管理 内存保护 总结 推荐阅读 为什么 Linux 默认页大小是 4KB? 页表项 碎片化 总结 推荐阅读 为什么 L ...

  6. linux的虚拟内存是4G,而每个进程都有自己独立的4G内存空间,怎么理解?

    问: linux的虚拟内存是4G,而每个进程都有自己独立的4G内存空间,怎么理解? 每个进程所拥有的4G独立的虚拟内存空间是什么意思?linux系统的虚拟4G空间中,高位的1G是用于系统内核运行的,那 ...

  7. Linux为什么虚拟内存大于物理内存?

    对于精通 CURD 的业务同学,内存管理好像离我们很远,但这个知识点虽然冷门(估计很多人学完根本就没机会用上)但绝对是基础中的基础. 这就像武侠小说中的内功修炼,学完之后看不到立竿见影的效果,但对你日 ...

  8. linux的虚拟内存是4G,而每个进程都有自己独立的4G内存空间,怎么理解?进程虚拟地址4G指拥有4G的寻址能力,需要页表转换为实际物理地址,每个进程用到的内核是直接映射,地址的进程地址-3G的关系

    linux的虚拟内存是4G,而每个进程都有自己独立的4G内存空间,怎么理解? 问: linux的虚拟内存是4G,而每个进程都有自己独立的4G内存空间,怎么理解? 每个进程所拥有的4G独立的虚拟内存空间 ...

  9. Linux的虚拟内存

    一.物理地址 在说虚拟地址之前先说一下物理地址的相关概念,物理地址即我们在指令中能够实际操作的物理存储空间,CPU内核设计的时候会有自己的寻址空间,通过控制总线,地址总线和数据总线,访问物理地址空间( ...

  10. 【Linux】Linux的虚拟内存详解(MMU、页表结构)

    内存是程序得以运行的重要物质基础.如何在有限的内存空间运行较大的应用程序,曾是困扰人们的一个难题.为解决这个问题,人们设计了许多的方案,其中最成功的当属虚拟内存技术.Linux作为一个以通用为目的的现 ...

最新文章

  1. node开启子线程_真Node多线程
  2. WINCE屏蔽NK的串口输出信息
  3. Nginx反向代理,负载均衡,redis session共享,keepalived高可用
  4. Apprentissage du français partie 1
  5. Linux下第一次使用MySQL数据库,设置密码
  6. Ubuntu 下安装tomcat和配置eclipse的遇到的问题的一点心得。
  7. 剑指offer面试题[32]:从1到n整数中1出现的次数
  8. python方向是干什么的_Python有哪些应用方向 在数据分析上有什么优势
  9. 【考研英语语法】一般过去式练习题
  10. 【R_绘图】绘图字体设为Times New Roman
  11. 艾司博讯:拼多多一件代发怎么发货
  12. ios状态栏,导航栏,工具栏,tab栏的位置,附图
  13. GDAL API Tutorial中文翻译(只介绍C++部分)
  14. Git --- Git Gui
  15. 【二】gym初次入门一学就会---代码详细解析简明教程----平衡杆案例
  16. 爷青回!经典扫雷再现!
  17. 微信小程序实现答题样式1(显示题目)
  18. 研二非科班研究生如何备战秋招
  19. Apache ShardingSphere 首篇论文被 ICDE 收录,全球数据库发展迎来新局面
  20. Android删除UIM卡联系人

热门文章

  1. python输出命令_Python输出各行命令详解
  2. 3维线程格 gpu_基于CUDA的GPU并行优化重力三维反演
  3. 编写fun函数判断字符串尾部的*号,若多于指定数量,则删除多余的;否则,不做操作
  4. 常见的冒泡排序、顺序查找和对半查找
  5. 新手学习python的方法
  6. 爬虫入门到精通-HTTP协议的讲解
  7. java中浅层克隆和深层克隆
  8. CentOS7 systemctl的使用
  9. 高性能JavaScript笔记三(编程实践)
  10. HDU-3573 Buy Sticks