《深入理解计算机系统》虚拟内存感悟

虚拟内存

向用户显示该资源的一些不同类型的视图,你可以通过介入对该资源的访问过程来实现这一点。你会在malloc创建包装函数的时候看到这一点

例如,你知道磁盘在物理上由柱面,磁道,扇区,磁盘组成。访问这些磁盘上一个特定扇区,你必须指定柱面,磁道和盘面,但是我们看到磁盘控制器的显示的视图实际不是这样的,是磁盘的虚拟化视图。磁盘控制器则将磁盘抽象成一系列逻辑块的形式提供给内核,它通过拦截内核的读写请求来呈现该视图,并将内核发送的逻辑块好转为实际物理地址

将主存视为存储在磁盘上地址空间的高速缓存

虚拟内存存储在磁盘是一系列连续的字节

在这里插入图片描述

任何虚拟页都可以放置在任何的物理页中,所以我们需要一个映射(页表,存于内存,由内核维护,是每个进程上下文的一部分,所以每个进程有自己的页表)
虚拟地址空间中的每个页在页表中一个固定偏移量处都有一个 PTE

在这里插入图片描述

虚拟内存作为内存管理的工具

在这里插入图片描述

这张图不是完全正确,实际上在用户栈和内核代码开始有 空白的地址空间
原因在intel体系结构中,虚拟地址是48位的,所以如果48位的最高位是0的话,那么剩下的比特也应该是0,也就是未使用的16bit需要设置为0 ,这有点像符号扩展 ,如果48位虚拟地址最高位为1,那么扩展出来的高位也是1
所以最高位和48位地址保持一致,内核所在的虚拟地址最高16位全部为1,所以你可以认为64位虚拟地址空间最顶部内核地址

每个进程都拥有自己的专属的虚拟地址空间,内核通过为每个进程提供自己独立的页表来实现这一点。每个进程的页表都映射该进程的虚拟地址空间。有趣的是,虚拟内存连续在实际中可以不连续。这样,我们可以为每个程序员和工具提供一个视图,每个进程都有一个非常相似的虚拟地址空间,有相同大小的地址空间,代码和数据分别从同一个地方开始,但实际上页面可能分散在内存中。

如果没有,如果有100个进程,如何跟踪这些进程的使用的所有数据的位置。选用内存分区,就会有很多问题,比如加入一个程序会很困难,并且你不能提前链接你的程序,因为他必须在加载时重定位,一个进程不知道他会加载到内存的什么位置,只知道他会使用内存的某些块,所以必须重定位所有的引用。

所以虚拟内存可以

  • 简化链接:独立的地址空间允许每个进程的内存映像使用相同的基本格式(代码段总是从虚拟地址 0x400000 开始,数据段跟在代码段之后,中间有一段符合要求的对齐空白,栈占据用户进程地址空间最高的部分,并向下生长),而不管代码和数据实际存放在物理内存的何处,这样的一致性极大地简化了链接器的设计和实现,允许链接器生成完全链接的可执行文件,这些可执行文件是独立于物理内存中代码和数据的最终位置的

  • 简化加载:虚拟内存还使得容易向内存中加载可执行文件和共享对象文件。要把目标文件中 .text 和 .data 节加载到一个新创建的进程中,Linux 加载器为代码和数据段分配虚拟页,把它们标记为无效的(即未被缓存的),加载器从不从磁盘到内存实际复制任何数据。在每个页初次被引用时,要么是 CPU 取指令时引用的,要么是一条正在执行的指令引用一个内存位置时引用的,虚拟内存系统会按照需要自动地调入数据页

  • 简化共享: 一般而言,每个进程都有自己私有的代码、数据、堆以及栈区域,是不和其他进程共享的在一些情况中,还是需要进程来共享代码和数据。例如,每个进程必须调用相同的操作系统内核代码,而每个 C 程序都会调用 C 标准库中的程序,比如 printf操作系统通过将不同进程中适当的虚拟页面映射到相同的物理页面,从而安排多个进程共享这部分代码的一个副本,而不是在每个进程中都包括单独的内核和 C 标准库的副本

  • 简化内存分配:虚拟内存为向用户进程提供一个简单的分配额外内存的机制。当一个运行在用户进程中的程序要求额外的堆空间时(如调用 malloc 的结果), 操作系统分配一个适当数字(例如k)个连续的虚拟内存页面,并且将它们映射到物理内存中任意位置的k个任意的物理页面。由于页表工作的方式,操作系统没有必要分配是个连续的物理内存页面。页面可以随机地分散在物理内存中。

虚拟内存作为内存保护的工具

在这里插入图片描述
SUP位表示进程是否必须运行在内核(超级用户)模式下才能访问该页
READ 位和 WRITE 位控制对页面的读和写访问
如果一条指令违反了这些许可条件,那么 CPU 就触发一个一般保护故障,将控制传递给一个内核中的异常处理程序。Linux shell —般将这种异常报告为“段错误"(segmentation fault)

地址翻译

在这里插入图片描述

缺陷解决

页表,每次都要访问内存?TLB

正如我们看到的,每次 CPU 产生一个虚拟地址,MMU 就必须查阅一个 PTE, 以便将虚拟地址翻译为物理地址。在最糟糕的情况下,这会要求从内存多取一次数据,代价是几十到几百个周期。如果 PTE 碰巧缓存在 L1 中,那么开销就下降到 1 个或 2 个周期。然而,许多系统都试图消除即使是这样的开销,它们在 MMU 中包括了一个关于 PTE 的小的缓存,称为翻译后备缓冲器(Translation Lookaside Buffer, TLB)

页表,常驻内存,空间太大?多级页表

到目前为止,我们一直假设系统只用一个单独的页表来进行地址翻译。但是4KB页大小,48位地址空间,8字节PTE,需要 512GB的页表
改为多级页表
一级页表,常驻内存,如果一级页表中的一个 PTE 是空的,那么相应的二级页表就根本不会存在,下面同理
在这里插入图片描述

缺页代价?局部性

局部性拯救了这个代价
尽管在整个运行过程中程序引用的不同页面的总数可能超出物理内存总的大小,但是局部性原则保证了在任意时刻,程序将趋向于在一个较小的活动雨面(active page)集合上工作,这个集合叫做工作集(working set)或者常驻集合(resident set)在初始开销,也就是将工作集页面调度到内存中之后,接下来对这个工作集的引用将导致命中,而不会产生额外的磁盘流量。

在这里插入图片描述

应用:Intel Core i7

在这里插入图片描述

在这里插入图片描述

问题:虚拟地址如何映射到硬盘数据块地址

Linux加载进程时(exec系列系统调用)会为该地址空间分配一个 VMA,vma数据结构会描述虚拟空间的开始地址,以及空间大小,同时会描述该vma背后映射的文件名(或路径)、映射空间所在文件的偏移量和大小。但是Linux内核不给该空间分配物理内存,所以此时的页表项是空的。当vma设置好之后,就算加载完事了,跳到"main"函数开始执行
引用一张图 https://zhuanlan.zhihu.com/p/67936075

在这里插入图片描述


在这里插入图片描述

虚拟内存区域初始化的时候和磁盘对象关联起来,这个过程叫做内存映射

第一次引用来自于文件,初始值来自于文件,也可以是个匿名文件,内核创建,创建一个全为0的页,这样页面和匿名文件官关联

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yilyil

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值