【软件开发底层知识修炼】三 深入浅出处理器之三 内存管理与内存管理单元(MMU)
发布日期:2021-07-01 00:05:12 浏览次数:3 分类:技术文章

本文共 6381 字,大约阅读时间需要 21 分钟。

学习交流加

  • 个人qq:
    1126137994
  • 个人微信:
    liu1126137994
  • 学习交流资源分享qq群:
    962535112

上一篇文章学习了中断的概念与意义,以及中断的应用-断点调试原理。点击链接复习上一篇文章:

本片文章继续学习处理器相关的知识-内存管理。包括:内存管理单元MMU的作用,虚拟内存与物理内存之间的映射方式,页表的概念,高速缓存(Cache)的作用,物理内存与高速缓存之间的映射关系等。当然,想要深入了解,本文并不适合,本文只是从原理上,讲述以上几者之间的关系。

文章目录

1、内存管理单元MMU

这里假设大家了解虚拟内存的由来。参考《深入理解计算机系统》讲虚拟内存的章节

实际上我们写的程序,都是面向虚拟内存的。我们在程序中写的变量的地址,实际上是虚拟内存中的地址,当CPU想要访问该地址的时候,内存管理单元MMU会将该虚拟地址翻译成真实的物理地址,然后CPU就去真实的物理地址处取得数据。

这里说的虚拟地址,是指虚拟地址空间中地址。这里我们说的虚拟地址空间,实际上是在磁盘上的一块空间(常见的是4G的进程虚拟地址空间)。具体这4G的虚拟地址空间的来龙去脉,参考《深入理解计算机系统》第九章。

MMU:内存管理单元。它是一个硬件,不是软件。它用于将虚拟地址翻译成实际的物理内存地址。同时它还可以将特定的内存块设置成不同的读写属性,进而实现内存保护。注意,MMU是硬件管理,不是软件实现内存管理。

总结来说,MMU能实现以下功能:

  • 虚拟内存。有了虚拟内存,可以在处理器上运行比实际物理内存大的应用程序。为了使用虚拟内存,操作系统通常要设置一个交换区(通常在硬盘上),通过将内存中不活跃的数据与指令放到交换区,以腾出物理内存来为其他程序服务。
  • 内存保护。通过这一功能,可以将特定的内存块设置为读、写或者可执行的属性。比如将不可变的数据或者代码设为只读的,这样可以防止被恶意串改。

1.1、虚拟内存

进程的概念大家都知道。

每一个进程都独立的运行在自己的虚拟地址空间。为了理解这一个概念。我们可以看一个而简单的例子:

看一下下面的代码:

main.c

#include 
#include
int g_int = 1;int main() {
printf("g_int = %d\n",g_int); printf("&g_int = %d\n",&g_int); system("pause");//此处程序会停止执行,不会执行到return 0 return 0;}

如果我同时运行该程序两次。打印结果会是一样么?答案是结果肯定一样,运行结果都为:

在这里插入图片描述

当然,这是在我的计算机上,在你的计算机上g_int地址可能不一样,但是同时运行该程序两次,结果肯定是一样的。其实这个答案很多人都知道是一样的,初学者都知道。但是初学者说不清楚是为什么。

  • 分析

这个进程运行两份实例的时候。在物理内存中,实际上是以下分布情况:

进程1和进程2 位于不同的地址。但是我们程序打印的g_int全局变量的地址值,是一样的。

这里就引入了虚拟内存的概念。我们写程序,面向的是虚拟地址空间。写的程序的内容,都可以看成是在虚拟地址空间中运行(实际上最终是将虚拟地址空间映射到了物理地址空间)。如下图:

在这里插入图片描述

以上只是简图。

我们可以看到。main.o可执行程序,运行两份实例时,相当于两个进程。这两个进程都有自己独立的虚拟地址空间。然后将虚拟地址空间里的代码数据映射到内存中,从而被CPU执行与处理。在物理内存中,g_int这个全局变量的物理地址确实不同。但是在虚拟内存中,由于进程1与进程2的虚拟地址空间完全一样(同一个可执行程序代码),那么g_int地址,实际上就是一样的。

CPU在执行指令与数据时,获得的是虚拟内存的地址。但是CPU只能去物理内存寻址。此时,MMU就派上用场了。MMU负责,将虚拟地址,翻译成,真正运行时的物理地址。

MMU是如何将虚拟地址翻译成物理地址的,这个后面讲。现在先要了解一下交换区的概念。

  • 交换区: 实际上就是一块磁盘空间(硬盘空间)。虚拟内存与物理内存映射的时候,是将虚拟内存的代码放到交换区中,以后在CPU想要执行相关的指令或者数据时,如果内存中没有,先去交换区将需要的指令与数据映射到物理内存,然后CPU再执行。

虚拟内存与交换取的这种概念,实现了大内存需求量的(多个)进程,能够(同时)运行在较小的物理内存中。如下图所示:

在这里插入图片描述

上图中,说的是进程的局部代码在物理内存中运行。是因为程序具有局部性原则,所以在某一段很小的时间段内,只有很少一部分代码会被CPU执行。具体可以参考下一篇文章。

到这里,我们应该大致明白了虚拟内存的作用与简单机制。还剩下MMU如何翻译虚拟地址为物理地址的,这放到最后讲解。现在先总结一下虚拟内存机制:

  • 虚拟内存需要重新映射到物理内存
  • 虚拟地址映射到物理地址中的实际地址
  • 每次只有进程的少部分代码会在物理内存中运行
  • 大部分代码依然位于磁盘中(存储器硬盘)

1.2、 页式内存管理

上一节笼统的介绍了虚拟内存的概念。接下来学习内存管理中的一种方式:页式内存管理。

页式内存管理中我们需要了解:

  • 页的概念
  • 页表的概念
  • 缺页的概念与页命中的概念
  • 分配页面
  • 程序的局部性原则

1.21 页的概念

由1.1的内容,我们知道了交换区。我们知道交换区里面存放的是大部分的可执行代码与数据。而物理内存中,执行的是少部分的可执行代码与数据。那么当物理内存中的代码与数据执行完需要执行接下来的代码,而刚好接下来的代码还在交换区中没有映射到物理内存(这称为缺页,后面会讲),那么此时就需要从交换区获取程序的代码,将它拿到物理内存执行。那么一次拿多少代码过来呢?这是一个问题!

为了CPU的高效执行以及方便的内存管理(详细原因见以后的文章),每次需要拿一个页的代码。这个页,指的是一段连续的存储空间(常见的是4Kb),也叫作块。假设页的大小为P。在虚拟内存中,叫做虚拟页(VP)。从虚拟内存拿了一个页的代码要放到物理内存,那么自然物理内存也得有一个刚好一般大小的页才能存放虚拟页的代码。物理内存中的页叫做物理页(PP)

在任何时刻,虚拟页都是以下三种状态中的一种:

  • 未分配的:VM系统还未分配的页(或者未创建)。未分配的页还没有任何数据与代码与他们相关联,因此也就不占用任何磁盘。
  • 缓存的: 当前已缓存在物理内存中的已分配页
  • 未缓存的:未缓存在物理内存中的已分配页

下图展示了一个8个虚拟页的小虚拟内存。其中:虚拟页0和3还没有被分配,因此在磁盘上还不存在。虚拟页1、4、 6被缓存在物理内存中。虚拟页2、 5、 7已经被分配,但是还没有缓存到物理内存中去执行。

在这里插入图片描述

1.22 页表的概念

1.21节用到了缓存这个词。这里假设大家都理解缓存的概念。

虚拟内存中的一些虚拟页是缓存在物理内存中被执行的。理所应当,应该有一种机制,来判断虚拟页,是否被缓存在了物理内存中的某个物理页上。如果不命中(需要一个页的代码,但是这个页未缓存在物理内存中),系统还必须知道这个虚拟页存放在磁盘上的哪个位置,从而在物理内存中选择一个空闲页或者替换一个牺牲页,并将需要的虚拟页从磁盘复制到物理内存中。

这些功能,是由软硬件结合完成的。 包括操作系统软件,MMU中的地址翻译硬件,和一个存放在物理内存中的页表的数据结构。

上一节说将虚拟页映射到物理页,实际上就是MMU地址翻译硬件将一个虚拟地址翻译成物理地址时,都会去读取页表的内容。操作系统负责维护页表的内容,以及在磁盘与物理内存之间来回传送页。

下图是一个页表的基本组织结构(实际上不止那些内容):

如上图所示:

页表实际上就是一个数组。这个数组存放的是一个称为页表条目(PTE)的结构。虚拟地址空间的每一个页在页表中,都有一个对应的页表条目(PTE)。虚拟页地址(首地址)翻译的时候就是查询的各个虚拟页在页表中的PTE,从而进行地址翻译的。

现在假设每一个PTE都有一个有效位和一个n位字段的地址。其中

  • 有效位:表示对应的虚拟页是否缓存在了物理内存中。0表示未缓存。1表示已缓存。
  • n位地址字段:如果未缓存(有效字段为0),n位地址字段不为空的话,这个n位地址字段就表示该虚拟页在磁盘上的起始的位置。如果这个n位字段为空,那么就说明该虚拟页未分配。如果已缓存(有效字段为1),n位地址字段肯定不为空,它表示该虚拟页在物理内存中的起始地址。

综上分析,就得知,上图中:四个虚拟页VP1 , VP2, VP4 , VP7 是被缓存在物理内存中。 两个虚拟页VP0, VP5还未被分配。但是剩下的虚拟页VP3 ,VP6已经被分配了,但是还没有缓存到物理内存中去执行。

注意:任意的物理页,都可以缓存任意的虚拟页。(因为物理内存是全相联的)

1.23 页命中

考虑下图的情形:

假设现在CPU想读取VP2页面中的某一个字节的内容。会发生什么呢?

当CPU得到一个地址vaddr想要访问它(这个addr就是上面想要访问的某一个字节的地址),通过后面会学习的MMU地址翻译硬件,将虚拟地址addr作为索引定位到页表的PTE条目中的PTE2(这里假设是PTE2),从内存中去读到PTE2的有效位为1,说明该虚拟页面已经被缓存了,所以CPU使用该PTE2条目中的物理内存地址(这个物理内存地址是PP1中的起始地址)构造出vaddr的物理地址paddr(这个地址是PP1页面起始地址或后面的某一个地址)。然后CPU就会去paddr这个物理内存地址去取数据。这种情况,就是也命中。

实际上,上面的VP2的起始地址与paddr地址,很类似于内存的分段机制(X86以前就是分段机制),CPU访问内存的地址是“段地址偏移地址”或者叫做“CS:IP”。而我们现在学习的是分页机制,他们都是一种内存管理机制。

1.24 缺页

什么是缺页?

考虑以下图示情形:

当CPU想访问VP3页面中的某一个字节。会发生什么情况?

由1.23小节的分析知,当地址翻译硬件MMU找到了PTE3后,发现有效位为0,则说明VP3并未缓存在物理内存中,并且触发一个缺页异常。缺页异常调用内核中的缺页异常处理程序,该程序会在物理内存中查询是否有空闲页面。如果物理内存中有空闲页面,则将VP3页面的内容从磁盘中复制到(映射)物理内存中的空闲页面。如果物理内存中没有空闲页面,则缺页异常处理程序就选择一个牺牲页,在此例中就是存放在PP3中的VP4。如果VP4已经被修改了,那么内核就会将它复制回磁盘。

然后此时因为VP3已经在物理内存中被缓存了,就需要将页表更新,也就是更新PTE3。

随后缺页异常处理程序返回。它会重新启动导致缺页的指令,该指令会重新将刚刚导致缺页的虚拟地址发送到MMU硬件翻译,但是此时,因为VP3已经被缓存,所以会页命中。

下图是在经过了缺页后,我们的示例页表的状态:

  • 以上有一个过程是替换页面的过程,其中包含一个页面调度算法。这个以后会学习。

1.25 分配页面

当你在程序中调用malloc或者new分配内存时,发生了什么?调用malloc后,会在虚拟内存中分配页面。(注意malloc分配的内存时虚拟内存,当CPU访问的时候,首先肯定会发生缺页,然后再将该页缓存到物理内存中)

如下图所示:

本身没有VP5这个虚拟页面,现在malloc后,新分配了一个虚拟页面VP5。

分配好VP5这个虚拟页面后,还需要更新PTE条目,使得PTE5指向VP5。

1.26 程序的局部性原则

虚拟内存这种机制会有什么问题?经常缺页会不会导致程序的执行效率低下?

实际上,虽然会产生不命中现象,但是虚拟内存机制工作的很好。这主要与程序局部性原则有关!!!什么是程序的局部性?

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

上面看似很完美,但是也有可能会出现这样一种情况:工作集的大小超过了物理内存的大小!! 此时,页面会不停的换入换出。这种状态叫做抖动!!!

当然,现在的计算机的物理内存的大小都非常大,一般不会出现抖动的现象!!!

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

虚拟内存为什么说是一种内存管理工具?

虚拟内存大大地简化了内存管理,并提供了一种自然的保护内存的方法。

到目前为止,我们都假设有一个单独的页表,将一个虚拟地址空间映射到物理地址空间。实际上,操作系统为每一个进程提供了一个独立的页表,因而也就是一个独立的虚拟地址空间。如下图:

注意:多个虚拟页面,可以映射到同一个共享物理页面上。

按需页面调度和独立的虚拟地址空间的结合,对系统中内存的使用和管理产生了深远的影响!!!如下:

  • 简化链接。
  • 简化加载
  • 简化共享
  • 简化内存分配

具体参考CSAPP:9.4节内容。

1.4、虚拟内存作为内存保护工具

上一节学习了虚拟内存作为内存管理工具。

其实虚拟内存还可以作为内存保护工具。如何做到?

想一想,CPU在访问一个虚拟内存页面时,需要读取页表条目中的PTE条目。如果在PTE条目中加一些额外的许可位来控制对虚拟内存的访问,当CPU读到相应的许可位,就可以知道该虚拟内存是否可读或者可写,或者可执行? 这样看来我们的页表就要变化一下,就如下图所示:

上图中:

  • SUP表示进程是否必须运行在内核模式(超级用户)下才能访问该页。
  • READ表示是否可读
  • WRITE表示是否可写

如果一条指令违反了这些许可条件,那么CPU就会触发一个一般保护故障,将控制传递给一个内核中的异常处理程序。Linux shell 一般将这种异常报告为“段错误(segmentation fault)”

2、地址翻译

上面一直在说MMU通过读取页表的PTE将虚拟地址翻译成物理地址。到底是如何翻译的?

如下图,展示了MMU是如何翻译地址的:

看到这么复杂的图,不要害怕!!! 下面讲解很容易懂!

  • CPU中有一个控制寄存器,页表基址寄存器(PTBR)指向当前页表。
  • n位的虚拟地址,包含两个部分:虚拟页面偏移VPO(p位)与虚拟页号VPN(n-p位)
  • MMU利用虚拟内存的高n-p位VPN作为索引找到页表的对应的PTE条目,然后获取PTE条目对应的物理页号PPN
  • 然后将PPN与VPO串联连接起来,就得到了实际的物理地址。(实际上就是PPN左移p位然后加上VPO,VPO=PPO)

到这里实际上我们已经更加的将这种地址串联与X86处理器中的分段机制很像。X86-16位的分段机制 也是将段地址CS左移4位然后与偏移地址IP相加,得到最终的物理地址。这是不是与上面的分页机制的地址翻译过程很像? 实际上它们一个是实模式,一个是保护模式而已!

MMU的地址翻译过程是不是很简单?如果不理解,就反复看,就理解了!!!

3、总结

下面来总结一下,分页机制中,CPU获得一个虚拟地址后,有哪些步骤需要做:

本片文章,我学会了:

  • 虚拟内存的概念与交换区的概念

  • MMU的作用

  • 虚拟内存机制的意义

    • 虚拟内存作为内存管理工具
    • 虚拟内存作为内存保护工具
  • 页表的概念

  • 页命中与缺页

  • 程序的局部性在虚拟内存中的作用

  • MMU的地址翻译过程

本人积累了无数的技术电子书籍与各类技术的视频教程,可以加好友共同探讨学习交流。

学习探讨加个人:
qq:1126137994
微信:liu1126137994

转载地址:https://lyy-0217.blog.csdn.net/article/details/84206495 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:推荐学习-Linux性能优化实战
下一篇:【软件开发底层知识修炼】二 深入浅出处理器之二 中断的概念与意义

发表评论

最新留言

很好
[***.229.124.182]2024年05月01日 05时02分11秒