0%

虚拟内存学习笔记

虚拟内存是什么?

虚拟内存是计算机系统内存管理的一种技术。它是的应用程序认为它拥有连续可用的内存,而实际在物理内存层面,可能是不连续的内存碎片,虚拟内存使用映射的方式将物理内存碎片映射起来,使其看起来是连续的内存。

虚拟地址和物理地址之间怎么映射的?

内存分段

内存分段是较早提出的一种分段方式,分段方法也简单,就是段到段的映射。当需要一块大小为n的虚拟内存时,此时就直接在物理内存中寻找一块连续的物理内存使用就好了。

内存分段解决了程序本身不需要关系局的物理内存地址的问题,但也存在以下问题:

  • 内存碎片严重。
  • 内存交换的效率低。

当你不断的申请释放申请释放内存时,就会产生很多不连续的小物理碎片,造成空间浪费。如何解决这个问题呢?就需要把不连续的内存空间重新连续起来,那么就要把内存整理成连续的,就需要利用硬盘来进行swap,把数据写回到硬盘再装载回内存,通过这种方式来将不连续的物理空间整理成连续的,而与硬盘的IO操作必然效率低下。

为了解决碎片问题与内存交换的效率问题,就出现了内存分页。

内存分页(Paging)

分页就是把整个虚拟和物理内存空间切成一段段固定的尺寸大小,我们将切出来的这一段段空间称之为页。

在Linux下,每一页的大小为4KB

虚拟地址与物理地址之间使用页表来映射。

内存分页

页表是存储在内存里的,内存管理单元(MMU)就做将虚拟内存地址转换成物理地址的工作。

而当进程访问的虚拟地址在页表中查不到时,系统就会产生一个缺页异常,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。

  • 由于使用的内存空间都是预先划分成页的,一页页使用,就不会产生内存间隙。
  • 内存交换效率低下问题也被解决掉了,如果遇到内存不足的情况,操作系统就会把其他正在运行的进程中最近不适用的内存页给释放掉,并暂时缓存到硬盘中,由于仅仅缓存一个或几个页面,就不会花很多时间,内存交换的效率自然就被提高了。
  • 更进一步的,在加载程序时,我们也不需要直接把所有程序都加载到物理内存中。只有当使用到所需要的数据时,再用预先创建好的映射关系去物理内存找,物理内存此时才会从硬盘里加载数据。(直接实现了数据的按需加载。)

多级页表

前面我们聊到了页表,用来负责虚拟内存到物理内存的映射。这部分映射关系保存在内存中,在32位环境中,虚拟地址空间共有4GB,如果一个页的大小是4KB的话,那么可以分出大约100万个页,每个页表项需要4个字节来保存,那么4GB空间则需要4MB,每个进程都拥有自己的虚拟内存地址,当进程很多时,对内存的占用就很大了。

为了解决上述映射问题,就出现了多级页表的解决方案。

局部性原理:指CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中。

通常为一个进程分配的4GB的虚拟内存都是用不完的,那么,那么能否做到按需加载呢?那么多级页面便可利用局部性原理做到按需加载。

一级页表覆盖所有虚拟地址(先粗粒度的划分),一级页表下有1024个二级页表,二级页表下有1024个页表项,当进程需要访问某个页时,若该页表没有分配,此时再将其分配出来,其他的页表等需要的时候再分配,以此减少内存。

多级页表减少了页表对内存的占用,但同时也降低访问速度,以空间换时间,那么有没有一种做法,可以把牺牲的时间再换回来呢?答案依旧是利用局部性原理做缓存,对常用的页表项缓存。

TLB

TLB:Translation Lookaside Buffer,利用局部性原理,把最常访问的几个页表项存储到访问速度更快的硬件中,这个硬件在CPU中,用来对页表项做Cache,这个Cache即TLB。

TLB

CPU请求MMU,MMU再查询TLB是否有缓存命中,没找到则查询常规页表。

MMU是啥

MMU:memory management unit

MMU是CPU的一部分,每个CPU核心都有一个MMU,包含:

  • TLB:页表项高速缓存。
  • Table Walk Unit:负责从页表中读取虚拟地址对应的物理地址。

更进一步,分析Linux.011的虚拟内存管理源码

请注意:我们下面会明确的区分虚拟地址(线性地址)物理地址,请注意表述。

接下来我们以Linux0.11为例,看下当时Linux中,是怎么对内存进行管理的,Linux0.11的内存管理有三个文件

  • page.s文件较短,仅包含内存页异常的中断处理int 14,主要实现了缺点和页写保护的处理。
  • memory.c是内存管理的核心文件,用于内存的初始化过程、页目录和页表的管理和内核其他部分对内存的申请处理过程。
  • Makefile不做介绍

Linux0.11多页页表结构

Linux0.11采用页目录表->页表->页表项的页表结构,每个页表的寻址范围为4MB。

页目录表和页表结构示意图

为了使用内存分页机制,一个32位的线性地址被分成了三个部分分别用来指定页目录表项、页表项、物理内存页的偏移地址。

页目录表的基地址被存放在CR3寄存器中,一个系统可以存在多个页目录表,而某个时刻仅有一个页目录表可使用,对于Linux0.11,仅有一个页目录表,在head.s中初始化,放置在物理内存0处。

线性地址变换示意图

每个表项由页框地址、访问标志位、脏标志位、存在标志位等构成。

表项结构

Linux0.11物理内存的分配和管理

物理内存示意图

Linux0.11对线性空间的使用与分配

Linux0.11对线性空间的使用与分配

memeory.c

本程序可以进行内存分页的管理,实现了对主内存区的动态分配和回收操作。

更多资料:

-------- 本文结束 感谢阅读 --------