虚拟内存

操作系统为每个进程都分配独属于进程自己的 虚拟内存,人人有份,互不干涉

操作系统会提供一种映射机制,将虚拟内存地址 映射成 硬件上的实际物理内存地址

当程序要访问虚拟内存地址时,由操作系统转成物理内存地址这样就可以防止说你直接使用物理内存地址,导

致多个进程之间物理地址冲突。

于是我们就引申出两个概念

1.程序使用的内存地址叫虚拟内存地址

2.实际硬件里的空间地址叫物理内存地址

操作系统引入了虚拟地址,进程持有的虚拟内存地址会通过cpu里的MMU(内存管理单元)来转化成物理地址,然后进程再通过物理地址来访问内存

PS:其实就相当于引入了一中间层来协调防冲突,类似中介

32位操作系统内核态虚拟内存的前896内存是直接映射到物理内存的前896m区域的,是一对一映射

且这种映射是不会改变的

操作系统提供两种方式来管理虚拟内存和物理内存的关系,内存分段和内存分页

内存分段

:::color4
程序是由若干个逻辑分段构成的,比如由代码分段,数据分段,栈段,堆段组成。不同段是有不同的属性的,所以就用分段的形式把这些段分离出来

你可以把内存分段想象成一本书:

  • 书的目录分为 序言、正文、附录(这就是分段)。
  • 每一段(章节)都有一个起始页码(段基址)和页数范围(段长度)。
  • 你要找“正文第 5 页”,其实就是 正文起始页码 + 5

分段的缺点:

  1. 内存碎片的问题
  2. 第二个就是内存交换的效率低的问题

内存碎片主要分成外部碎片和内部碎片

分段内存管理可以做到根据段的大小分配合适的内存大小,所以不会出现内部内存碎片

但是由于每个段的内存不一样,有大有小,所以多个段未必能恰好使用到所有的内存空间,会产生多个不连续的物理内存空间,导致新的程序无法转载,所以会出现外部内存碎片的问题

总结就是随着段的申请和释放,会导致你的内存空间东一块西一块,新的程序来的时候需要一整块连续的,无法满足

解决外部内存碎片的方案就是 内存交换

内存交换就是指当你新进程来的时候,会把暂时没有用的内存数据搬到硬盘,腾空间给新进程用,然后等到那些搬到硬盘的数据需要用的时候再搬回来,搬回来的时候不一定得放到原始的位置上,而是会先进行内存紧缩,让内存空间变得连续

这个内存交换空间,在Linux中,就是我们常说的 swap空间,Linux专门在硬盘上划出这么一块空间,来进行内存与硬盘交换

:::

内存分页

:::color4
分页是指把虚拟和物理内存分为一段段固定内存的大小。这样一个大小固定且连续的内存空间我们就称之为页(好像是这样的哦,比如你书本的页,就是连续且大小固定的。但段就不一定了)。在Linux下,每个页的大小为 4kb

页表是存储在内存中的,而MMU就做这么一个工作,将虚拟地址转成物理地址

问题1:当进程访问的虚拟地址在表中查不到会怎么样

这时候系统会产生一个缺页的表现,会从用户态切成内核态,然后进行物理内存的分配,页表的更新。最后在返回用户态,恢复进程的运行

分页可以让我们程序在加载时不需要一次性把程序加载到物理内存,而是可以在处理好物理内存和虚拟内存之间的映射关系后,并不真的把页加载进去,而是在程序运行时,需要用到虚拟页中的指令和数据时,在把它加载到物理内存里去。

在分页机制下,虚拟内存里包含 虚拟页号和业内偏移。虚拟页号作为页表的索引,页表里又包含虚拟页号

和物理页号,这个物理页号和页内偏移量就构成了物理地址

在 32 位的环境下,虚拟地址空间共有 4GB,假设一个页的大小是 4KB(2^12),那么就需要大约 100 万 (2^20) 个页,每个「页表项」需要 4 个字节大小来存储,那么整个 4GB 空间的映射就需要有 4MB 的内存来存储页表。

这 4MB 大小的页表,看起来也不是很大。但是要知道每个进程都是有自己的虚拟地址空间的,也就说都有自己的页表

那么,100 个进程的话,就需要 400MB 的内存来存储页表,这是非常大的内存了,更别说 64 位的环境了。

多级页表

我们把这100多个页再分页,将一级页表分为1024个二级页表,每一个二级页表里包含1024个页表项

且如果某个一级页表的页表项没有被用到时就不去创建二级页表,大大节省了内存空间,当然这是建议在内存使用是稀疏的情况下

即一级页表有1024个页表项,每个页表项对应了4MB的内存空间,总共是4GB,如果一个程序只需要用400MB内存空间,只需要4KB(1024 * 4字节)(一级页表) + 100 * 4KB(二级页表) = 0.395MB的页表去存储,这比之前的4MB好多了

为什么不分级的页表就做不到内存节约呢?

我们从页表的性质来看,保存在内存中的页表承担的职责是将虚拟地址翻译成物理地址。假如虚拟地址在页表中找不到对应的页表项,计算机系统就不能工作了(中断了,cpu切到内核态,程序不能运行)。

原因是页表需要覆盖所有的虚拟内存空间,而如果不分级,那么所使用到的内存空间是非常大。那么这时分级的话,只需要1024个页表项(此时页表已经覆盖所有虚拟内存地址了,二级页表需要时再创建)

:::

段页管理

:::color4
内存分页和内存分段并不是对立的,我们可以吸取两者的优点

:::

为什么要使用虚拟内存

  • 虚拟内存可以使得运行时内存超过实际内存的大小,因为程序的运行符合局部性原理,cpu访问内存会有明显的重复性访问,那么就可以把那些暂时没用的内存数据都放到硬盘上
  • 由于每个进程都有自己的页表,所以每个进程的虚拟内存空间就是相对独立的。进程也没法访问其他页表,这解决了多进程下的地址冲突问题
  • 页表里的页表项了除了物理地址外,还会有写比特位用来标识一些属性,比如控制页表的读写权限,标记该页是否存在。在内存访问方便,操作系统提供了更好的安全性