您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 
 订阅
CPU如何与内存交互:虚拟内存
 
 
  491  次浏览      19 次
 2023-12-28
 
编辑推荐:
本文讲解了CPU如何与内存交互:虚拟内存相关内容。希望对您的学习有所帮助。
本文来自于微信公众号芯生代,由火龙果软件Linda编辑、推荐。

计算机领域,有一句神圣的哲言:"计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决",从内存管理、网络模型、并发调度甚至是硬件架构,都能看到这句哲言在闪烁着光芒,而虚拟内存则是完美实践之一。

虚拟内存是计算机中的一个非常重要的存储器抽象,主要是用来解决应用程序日益增长的内存使用需求:现代物理内存(是指计算上主板上插着的内存条)的容量增长已经非常快速了,然而还是跟不上应用程序对主存需求的增长速度,对于应用程序来还是可能出现内存不足,因此便需要一种方法来解决这两者之间的容量差矛盾。

为了更高效地管理内存并尽可能消除程序错误,现代计算机系统对物理主存 RAM 进行抽象,实现了虚拟内存 (Virtual Memory, VM)技术。具体是:为每个程序设置一段"连续"的虚拟地址空间,把这个地址空间分割成多个具有连续地址范围的页 (Page),并把这些页和物理内存做映射,在程序运行期间动态映射到物理内存。

虚拟地址空间按照固定大小划分成被称为页(Page)的若干单元,物理内存中对应的则是页框(Page Frame)。这两者一般来说是一样的大小,如上图中的是 4KB,不过实际上计算机系统中一般是 512 字节到 1 GB,这就是虚拟内存的分页技术。因为是虚拟内存空间,每个进程分配的大小是 4GB (32 位架构),而实际上当然不可能给所有在运行中的进程都分配 4GB 的物理内存,所以虚拟内存技术还需要利用到一种技术,也就是通常所说的页面置换算法,在进程运行期间只分配映射当前使用到的内存,暂时不使用的数据则写回磁盘作为副本保存,需要用的时候再读入内存,动态地在磁盘和内存之间交换数据。

虚拟内存映射

在常用的 Linux 或者 Windows 操作系统下,程序并不能直接访问物理内存。程序都是通过虚拟地址 VA(virtual address)用地址转换翻译成 PA 物理地址(physical address)才能获取到数据。也就是说 CPU 操作的实际上是一个虚拟地址 VA。

如上图,CPU 访问主存的时候会将一个虚拟地址(virtual address)被内存管理单元(Memory Management Unint, MMU)进行翻译成物理地址 PA(physical address) 才能访问。

想要把虚拟内存地址,映射到物理内存地址,最直观的办法,就是来建一张映射表。这个映射表在计算机中叫页表(Page Table)。

在查找页表的时候,会将虚拟地址分成页号(Directory)和偏移量(Offset)两个部分。前面的高位,表示内存地址的页号。后面的低位,表示内存地址里面的偏移量。

查找方式和上文说的组相联类似,首先使用虚拟页号作为索引去页表中找到对应的物理页号,页表中还会有 1 位表示有效位,如果该位无效就不在主存中,发生一次缺页;如果有效,那么就可以拿到对应的物理页号获取到对应的物理页位置,再根据偏移量得到物理内存地址。

如果有效位关闭,那么该页就只存在磁盘上的某个指定的磁盘地址。缺页会触发缺页异常,然后在闪存或磁盘中找到该页,将其放入到主存 DRAM 中。

如果主存满了,那么会选择一个牺牲页,大多数操作系统会使用 LRU 替换策略来进行页的替换。操作系统会查找最少使用的页,被替换的页会写入磁盘的交换区(swap 分区)。swap 分区通常被称为交换分区,这是一块特殊的硬盘空间,即当实际内存不够用的时候,操作系统会从内存中取出一部分暂时不用的数据,放在交换分区中,从而为当前运行的程序腾出足够的内存空间。

在下图中假设选择将存放在主存中的 VP6 进行替换,将 VP6 替换为 VP3。如果被替代的 VP6 已经被修改了,那么内核会将它复制回磁盘。

由于局部性(locality)的存在,程序一般而言会在一个较小的活动页面集合上工作,页的切换开销只存在于程序启动时将页面调度到内存中,接下来的程序都会页命中。但是如果代码的工作集太大,超过了物理内存大小,那么页面就会不停地换进换出,产生抖动。

多级页表

假设我们现在是一个 32 位的地址空间、4KB 的页面和一个 4 字节的 PTE,那么需要一个 4MB 的页表常驻在内存中,并且这个页表是每个进程都独占一份,所以会造成很大的内存浪费,我们需要一种方式来优化我们的页表空间存储。

想一下虚拟内存空间结构,整个 4 GB 的空间,操作系统用了 1 GB,从地址 0XC0000000 到 0XFFFFFFFF, 剩余 3 GB 留给用户空间,其实很多程序根本用不到这么大的空间,对于 64 位系统,每个进程都会拥有 256 TiB 的内存空间,那就更加用不上了。

那么对于用不上的空间,我们可以不可以不把它加载到页表里面,等到用这块空间的时候才在页表里面给它分配一个页表项,是不是就可以节省大量空间。

在程序运行的时候,内存地址从顶部往下,不断分配占用的栈的空间。而堆的空间,内存地址则是从底部往上,是不断分配占用的。所以,在一个实际的程序进程里面,虚拟内存占用的地址空间,通常是两段连续的空间。而多级页表,就特别适合这样的内存地址分布。

假设 32 位虚拟地址空间被划分位 4KB 每页,每个条目都是 4 字节,那么我们可以让第一级页表中的每个 PTE (页表项 page table entry)负责映射虚拟地址空间中一个 4MB 的片,这个片由 1024 个连续页面组成,表示二级页表。如果地址空间是 4GB,那么 1024 个一级页表项就可以覆盖整个空间。

如下图所示,内存前 2K 个页面给代码和数据,接下来 6K 个页面未分配,在接下来 1023 个页面也未分配,接下来一个页面分配给用户栈。

这种方法从两个方面减少了内存占用。第一,如果一级页表中的一个 PTE 是空的,那么相应的二级页表就根本不会存在。由于很多程序占用内存实际远小于页表所能表示的大小,所以可以节约很大空间的页表项资源;第二,只有一级页表才需要总是在主存中,二级页表会在需要的时候创建或销毁,只有最经常使用的二级页表才需要缓存在主存中,这就减少了主存的压力。

Linux 在 2.6.10 中引入了四层的页表辅助虚拟地址的转换:

首先会找到 4 级页表里面对应的条目(Entry)。这个条目里存放的是一张 3 级页表所在的位置。4 级页面里面的每一个条目,都对应着一张 3 级页表,所以我们可能有多张 3 级页表。

找到对应这张 3 级页表之后,我们用 3 级索引去找到对应的 3 级索引的条目。3 级索引的条目再会指向一个 2 级页表。依次拿到 1 级页表里面存储的物理页号,我们同样可以用“页号 + 偏移量”的方式,来获取最终的物理内存地址。

TLB 加速地址转换

对于一个页命中的数据获取过程通常来说,如果没有 TLB 加速是这样的:

第 1 步:CPU 生成一个虚拟地址,并把它传给 MMU;

第 2 步:MMU 生成页表项地址 PTEA,并从高速缓存/主存请求获取页表项 PTE;

第 3 步:高速缓存/主存向 MMU 返回 PTE;

第 4 步:MMU 构造物理地址 PA,并把它传给高速缓存/主存;

第 5 步:高速缓存/主存返回所请求的数据给 CPU。

一次简单的数据获取需要多次经过多次与内存的交互,如果是 4 级页表,那么就需要访问 4 次内存才能获取到对应的物理页号。如果是缺页,还需要有一个 PTE 的置换或加载过程。在开头也讲了,访问内存的性能其实很低的,实际上这严重影响了 CPU 处理性能。

程序所需要使用的指令,都顺序存放在虚拟内存里面。我们执行的指令,也是一条条顺序执行下去的。也就是说,我们对于指令地址的访问,存在前面几讲所说的“空间局部性”和“时间局部性”,而需要访问的数据也是一样的。我们连续执行了 5 条指令。因为内存地址都是连续的,所以我们可以通过加缓存的方法,把之前内存转换的地址缓存下来,减少与内存的交互。

加的这一层就是缓存芯片 TLB (Translation Lookaside Buffer),它里面每一行保存着一个由单个 PTE 组成的块。

那么查询数据的过程就变成了:

第 1 步:CPU 产生一个虚拟地址;

第 2 步:MMU 从 TLB 中取出相应的 PTE;

第 3 步:MMU 将这个虚拟地址翻译成一个物理地址,并且将它发送到高速缓存/主存;

第 4 步:高速缓存/主存将所请求的数据字返回给 CPU;

为何需要虚拟内存?

讲完了什么是虚拟内存,我们最后讲讲虚拟内存的必要性。

由于操作虚拟内存实际上就是操作页表,从上面讲解我们知道,页表的大小其实和物理内存没有关系,当物理内存不够用时可以通过页缺失来将需要的数据置换到内存中,内存中只需要存放众多程序中活跃的那部分,不需要将整个程序加载到内存里面,这可以让小内存的机器也可以运行程序。

虚拟内存可以为正在运行的进程提供独立的内存空间,制造一种每个进程的内存都是独立的假象。虚拟内存空间只是操作系统中的逻辑结构,通过多层的页表结构来转换虚拟地址,可以让多个进程可以通过虚拟内存共享物理内存。

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

最后就是提供了内存保护机制。任何现代计算机系统必须为操作系统提供手段来控制对内存系统的访问。虚拟内存中页表中页存放了读权限、写权限和执行权限。内存管理单元可以决定当前进程是否有权限访问目标的物理内存,这样我们就最终将权限管理的功能全部收敛到虚拟内存系统中,减少了可能出现风险的代码路径。

总结

页表:从数学角度来说页表就是一个函数,入参是虚拟页号 VPN,输出是物理页框号 PPN,也就是物理地址的基址。页表由页表项组成,页表项中保存了所有用来进行地址翻译所需的信息,页表是虚拟内存得以正常运作的基础,每一个虚拟地址要翻译成物理地址都需要借助它来完成。

TLB:计算机硬件,主要用来解决引入虚拟内存之后寻址的性能问题,加速地址翻译。如果没有 TLB 来解决虚拟内存的性能问题,那么虚拟内存将只可能是一个学术上的理论而无法真正广泛地应用在计算机中。

多级页表和倒排页表:用来解决虚拟地址空间爆炸性膨胀而导致的大页表问题,多级页表通过将单页表进行分拆并按需分配虚拟内存页而倒排页表则是通过反转映射关系来实现节省内存的效果。

为了访问数据安全,便捷,迅速所以加了一层虚拟内存,每个程序在启动的时候都会维护一个页表,这个页表维护了一套映射关系。CPU 操作的实际上是虚拟地址,每次需要 MMU 将虚拟地址在页表上映射成物理地址后查找数据。并且为了节省内存所以设计了多级页表,为了从页表中查找数据更快加了一个缓存芯片 TLB。

 

   
491 次浏览       19
相关文章

一文了解汽车嵌入式AUTOSAR架构
嵌入式Linux系统移植的四大步骤
嵌入式中设计模式的艺术
嵌入式软件架构设计 模块化 & 分层设计
相关文档

企点嵌入式PHP的探索实践
ARM与STM简介
ARM架构详解
华为鸿蒙深度研究
相关课程

嵌入式C高质量编程
嵌入式操作系统组件及BSP裁剪与测试
基于VxWorks的嵌入式开发、调试与测试
嵌入式单元测试最佳实践

最新活动计划
面向对象业务分析与系统设计 10-16[线上]
嵌入式软件架构设计-高级实践 10-17[线上]
Qlik Sense数据分析技术 10-17线上]
基于 UML 和EA进行分析设计 10-22[北京]
用户研究与用户建模 10-24[北京]
QT应用开发 10-24[北京]
 
 
最新文章
基于FPGA的异构计算在多媒体中的应用
深入Linux内核架构——简介与概述
Linux内核系统架构介绍
浅析嵌入式C优化技巧
进程间通信(IPC)介绍
最新课程
嵌入式Linux驱动开发
代码整洁之道-态度、技艺与习惯
嵌入式软件测试
嵌入式C高质量编程
嵌入式软件可靠性设计
成功案例
某军工所 嵌入式软件架构
中航工业某研究所 嵌入式软件开发指南
某轨道交通 嵌入式软件高级设计实践
深圳 嵌入式软件架构设计—高级实践
某企业 基于IPD的嵌入式软件开发
更多...