内存管理
虚拟内存
单片机的CPU是直接操作物理内存的,因此想要同时运行两个程序是做不到的,因为如果第一个程序在地址x的位置写入数据,第二个程序同样需要用到地址x,则会发生冲突。
因此我们需要用到虚拟地址来分割不同进程之间的内存地址,即给每个进程分配一个虚拟地址,虚拟地址与物理地址之间的映射关系对于进程而言是透明的,进程只需要直接对虚拟地址操作即可。
操作系统提供了一种机制,将虚拟地址和物理地址联系起来。
虚拟地址:程序使用的内存地址
物理地址:硬件实际的内存空间
我们上文提到了操作系统将虚拟地址和物理地址映射的机制,接下来我们将介绍这种机制
内存分段
程序是由多个逻辑段构成:
- 代码段
- 数据段
- 堆段
- 栈段
因此我们可以通过内存分段的形式进行虚拟地址与物理地址的映射
在内存分段的情况下
虚拟地址 = 段选择因子 + 段偏移量
段偏移量我们很好理解就是内存段中的offset,当我们访问一块内存区域时,需要知道该内存在段中的哪个部分
段选择因子中包括了段号,通过段号我们可以在段表上找到对应的段描述符,段描述符中包括了段基址和段界限还有特权级
段界限用来判断访问的段地址是否合法,如果地址合法那么访问的地址就是段基址 + 段偏移量
缺陷
- 内存碎片问题
在分段的情况下,容易产生内存外碎片,导致大内存程序即使在剩余内存空间够的情况下,但是由于内存分散导致无法载入,因此继而产生了接下来的内存交换问题 - 内存交换问题
解决内存碎片问题的一个方式就是内存交换,但内存碎片多的时候将已载入内存的部分放入硬盘重新载入(swap),使得与其他段紧挨着,减少碎片,以此解决内存碎片问题,但是频繁的内存交换导致性能瓶颈。
内存分页
内存分页就是在上述情况下出现
内存分页就是将虚拟内存和物理内存划分成大小一致的页,在Linux中为4KB
虚拟地址和物理地址通过页表进行地址映射。内存管理单元MMU负责将虚拟地址转化为物理地址。当进程访问的虚拟地址在页表上查询不到时会产生一个缺页中断,然后进入系统内核空间分配物理内存,更新进程页表,最后返回用户空间。
虚拟地址分为页号 + 偏移量两个部分,页表中存储了虚拟页号和物理页号的映射关系,找到对应的物理页号后根据偏移量找到实际的地址
分页是如何解决内存碎片问题?
因为分页将内存划分成大小相同的页,因此程序只需要取数量的页即可,但是这里存在内存内碎片问题,因为页的大小是相同的,因此页内存在碎片问题
多级页表
多级页表是为了解决简单分页下出现的内存占用问题
简单分页
在简单分页下,我们知道在32位系统下,虚拟内存有4G,一个页的大小是4KB,因此分成2的20次方个页,每个页表项需要4B大小存储,因此这里总共是4MB,对于每个进程而言都有一个页表,因此产生的开销是巨大的,在这种情况下我们考虑采用多级页表的形式
事实上,我们考虑一下复杂分页并不能减少内存的时候,比如对于2的20次方个页,我们分成二级页表而言需要4KB(1024)大小的一级页表,以及4MB(1024 * 1024)大小的二级页表,这样一想并没有减少内存的使用呀?但实际上这里涉及了懒惰分配的原则,由于程序具有局部性原理,对于没有用到的地址我们展示不分配二级页表因此减少了内存的使用
对于64位系统而言页表一般是四级:
- 全局页
- 上层页
- 中间页
- 页表项
TLB
但是由于页表结构变得复杂,那么虚拟地址的转化也会变得复杂,因此出现了TLB,即一种缓存结构,放置了最常访问的页表项和对应的物理页,CPU在寻址时首先会查询TLB,然后进行地址转换
通常情况下TLB的命中率比较高,因为程序具有局部性原理