暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

内存管理单元mmu知识分享思考

原创 yBmZlQzJ 2023-01-12
1202

今天又是粉丝问题回答,有好几个粉丝让我分享一下mmu的原理以及作用,我才疏学浅,不会分享到mmu的具体实现细节,毕竟这个是IC设计的事情。并且不同的厂商不同的架构实现方式肯定是有区别的。我们今天主要从软件看到的角度来分析mmu的作用。我觉得这对于os以及软件这样的理解足够了。

我更多的是传递一种思想,目的是大家学习到一种新东西的时候能够举一反三。思想比工具更加重要。

那么什么是mmu呢,英文是Memory Management Unit,翻译过来就是内存管理单元。为什么要引入mmu呢?我们先从历史角度解释这个问题。当我们带着问题来思考这个问题的时候,那么一切一切思想就有了因果关系。新事物的出现就是为了解决遇到的问题。不是吗

早期的处理器不存在mmu,我们软件中的一个指令访问的地址直接就是代码中是什么地址,那么总线上就是什么地址,也就是代码中的地址就是物理地址。这样在软件规模还比较小的时候,似乎没有什么,但是对于多应用程序,系统中有十个程序,那么这十个程序都访问物理地址,程序员就要统一规划这些程序各自占用哪些物理地址空间。但是如果是100个程序呢?理论上每一个程序都可以访问系统所有的物理地址,那么这100个怎么避免冲突,怎么统一规划地址空间。这整个都是一个问题。为了解决这个问题。虚拟内存的概念就提出来了,伴随着硬件上的mmu也就出现了。当开启虚拟地址访问的时候,这个时候代码中同样的一段代码,这个时候代码中访问的地址就不再是物理地址了,而是虚拟地址。他需要经过mmu的转换变为物理地址。

假设代码中都执行下面这两句代码:我们是以x86的指令来说明的,arm也是类似

//将0x1000000赋值给eax寄存器mov 0x1000000  %eax//将100写入eax寄存器所指向的内存地址,也就是将100存入内存0x1000000mov 100  (%eax)
//上面这两句可以类比为c语言如下更容易理解int * p=(int*)(0x1000000);*p=100;//更简化的类比如下:(*(int*)(0x1000000))=100;

大家可以看到上面这段代码我说的都是内存地址,并没有说是物理地址或者虚拟地址。接下来我们看看就这段代码假设在有mmu和没有mmu下的不同,我用图来说明

在没有mmu的时候或者没有开启mmu的时候这个形象的拓扑图如下


可以看到cpu指令的0x1000000直接当成物理地址发送到物理内存寻址

我们再看看如果开启了mmu的时候是怎样的。


可以看到同样的一段代码在开启mmu的时候,代码中写的地址0x1000000被当成虚拟地址,然后经过mmu映射,得到物理地址xxxx,然后访问内存物理地址的xxxx地址。这就是有mmu和没有mmu的区别。

我们上图为了方便将cpu和mmu独立画出来,目的是为了描述问题,实际通常是soc,拓扑过程比这个复杂,但是从os和软件角度这样的类比足够了。

上面是从宏观角度说明了mmu的作用,以及对软件的影响。那么这样中间多一个环节有什么好处呢?上图中的这个0x1000000是怎么转换为xxxx的呢

带着这几个问题,我们继续。

第一个问题,mmu的优缺点

优点:首先有了mmu之后我们的程序地址空间就可以统一起来了,尤其是对于应用程序。我们的系统有1000个程序,那么我们写代码的时候不用再考虑这1000个程序的地址空间冲突重叠,我们不用做这样的划分。这1000个程序可以访问的虚拟地址可以完全是一样的。经过mmu之后,他们得到的物理地址可是不一样的,当然也可以一样。这样的好处显而易见。各个程序相互不影响。极大的扩展性。各个程序相互不影响,提高了安全性

缺点:首先说明一下,这个缺点是我的猜测,也许是错的。我们能看到多了中间转换环节。那么是不是意味着速度会降低。影响了效率。当然我不知道,这个仅仅是我的一个猜测

第二个问题,上图中的0x1000000是怎么转换为物理地址xxxx的?

mmu有有各种各样,32bit的转换,64bit的转换,并且这个虚拟地址分段的方式不同。我们以最简单的32bit的方式来说明,因为原理都是一样的。


这幅图就是32bit两级映射情况下将虚拟地址转换为物理地址的拓扑。我们依次解释

上图中的绿色部分和浅蓝色部分都是在物理内存里面,只不过绿色部分比较特殊,它里面放的是映射关系也就是映射表,也是我后面称为的字典。蓝色部分是通用的内存

CR3是x86处理器的一个寄存器,以32bit为例,0-11bit有几个是控制缓存映射体系那个之类的,我们忽略。我们重点关注的是映射。12-31bit这20个bit位存储的是上图绿色部分的page directory的物理地址的的高位部分,低12位是0,也就是说CR3的12-31bit表示的数值扩大4096倍就是page directory的物理地址。

同样page directory里面的每一项PDE也是32bit。低12bit位存储一些权限,缓存控制之类的位,从12-31位是下一级page table的高位部分,同样也是PDE的12-31bit代表的数值扩大4096倍得到的是一个page table的物理地址。整个如下图所示


也就是CR3和PDE,PTE的蓝色高位都是地址,红色低12位都是用来控制缓存,脏页等等结构的一些bit位定义,当然这三个的属性控制位定义都不一样。我们不关注。要得到这三个项代表的物理地址那么就是将这红色部分全部当成0所对应的数值就是对应表的物理地址。

这个CR3在x86下是CR3,在其他的架构有类似的其他的寄存器,并且属性控制位的定义也各有不同。我们重点关注软件角度的原理。

比如我们的0x1000000这个虚拟地址是怎么找到物理地址的呢,将0x1000000按照10,10,12bit分为三段,我们可以知道0x1000000的二进制是:


1 0000 0000 00000000 0000 0000

绿色部分是directory:4,红色部分是table:0,蓝色部分是offset:0

可以看到这三段对应的值分别是7,0,0。转换过程就是CR3找到page directory,然后找到这个page directory里面的第4项,每一项是32bit。这个第4项的32bit位又按照上图我们PDE画的这个忽略低12位为0得到一个地址是page table。然后0x100000000的二进制红色部分是0,那么找到page table里面的第0项的32bit。这32bit继续按照这个方式得到一个物理地址。然后这个物理地址加上0x1000000里面的offset为0这样就得到该虚拟地址对应的物理地址


可以看到无论这个虚拟地址的低12位怎么变化,寻找的pde和pte不会变化,也就是说一个映射项可以映射4K的物理地址。

这个是映射转换的过程。那么问题又来了,这CR3里面的值以及这些映射表page directory和page table是谁填写的呢?并且除了CR3是寄存器,这个page directory和page table也在内存里面。他是怎么访问的呢?这个就是我前面文章说的鸡生蛋蛋生鸡的逻辑?

我们解释了这个转换过程,结果又引出了新的问题哈哈。我们接着解释:

原因就是mmu是有开关的通过寄存器控制,无论是arm还是x86都有类似的寄存器,让系统开启mmu。在x86下就是保护模式。也就是说在开启mmu前,我们要先规划好我们的os的地址空间,然后找一块物理内存作为page directory和page table,然后在里面预先填写一些项,这样就为一些物理内存建立了虚拟映射。然后我们开启mmu(也是通过设置某个寄存器)。这个时候我们代码中同样的一条指令,指令中的地址在开启了mmu之后就被当成虚拟地址,经过上面这幅图转化。当然假设我们随便访问一个地址0xabcxxx之类的,也许这个地址在映射表中并没有映射关系,那么就会触发异常中断,page fault之类的。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论