1. 虚拟内存

单片机是没有操作系统的,所以每次写完代码后 都需要借助工具将程序烧录进去,这样程序才能跑起来。另外,单片机的CPU 直接操作内存的 "物理地址"

在这种情况下,要想在内存中 同时运行两个程序 是不可能的。如果程序1 在存放程序2的某个位置上 写入⼀个新的值,将会覆盖 程序2在该位置上的内容,从而造成错误。

问题:操作系统是如何解决这个问题的?
这个问题的关键在于 两个程序都访问了物理地址,所以首先就需要 避免程序直接访问物理地址。
思路:可以将进程所使用的地址 "隔离"开来,即 让操作系统为每个进程分配 独立的一套"虚拟地址",从而每个进程都能 访问自己的虚拟地址,而不是 直接访问物理地址。虚拟地址对应的内存物理地址 对进程而言是透明的

操作系统提供了一种机制,将不同进程的虚拟地址 与其对应的 内存的物理地址 映射起来。程序访问虚拟地址时,由操作系统转换成 对应的物理地址,这样 不同的进程运行时,访问的是 不同的物理地址,因此就解决了 访问地址冲突问题。
  • 程序所使用的内存地址 叫做虚拟内存地址
  • 实际存在内存中的地址 叫做物理内存地址
操作系统引⼊了虚拟内存后,进程的虚拟地址 通过CPU芯片中的 内存管理单元(MMU) 的映射关系,转换成相应的物理地址,然后再通过物理地址 访问内存。过程如下图:

问题:操作系统如何管理 虚拟地址与物理地址之间的关系?
主要有两种方式,分别是 内存分段内存分页

2. 内存分段

程序由若干个 逻辑分段 组成 (如:代码段、数据段、栈段、堆段)。不同的段 有不同的属性,所以就用分段的形式 将这些段分离出来。
问题:分段机制下,虚拟地址与物理地址之间 是如何映射的?
分段机制下的虚拟地址 由两部分组成:段选择因子段内偏移量

  • 段选择因子 保存在段寄存器中。段选择因子中 最重要的是段号,⽤作段表的索引。段表中保存了 段基地址段界限特权级 等。
  • 如果段内偏移量是合法的 (最终得到的物理地址 落在段内),就将 段基地址+段内偏移量 从而得到物理内存地址
从上可知,虚拟地址是通过 段表和物理地址 进行映射的分段机制将程序的虚拟地址 分成了4个段在段表中可以找到 段的基地址,再加上偏移量,就得到了 物理内存地址。如下图:

例如:如果要访问 段3中偏移量为500 的虚拟地址,可以计算出物理地址为:段3基地址7000 + 偏移量500= 7500。

分段带来的两个问题:
  • 内存碎片
  • 内存交换的效率低
问题:分段为什么会产生 内存碎片?
举例:假设有1G的物理内存,用户执行了多个程序,其中:游戏占用了512MB内存、浏览器占用了128MB内存、音乐占用了256MB内存。如果关闭浏览器,则空闲内存大小为 1024 - 512 - 256 = 256MB。
如果这256MB的空闲内存 不是连续的,而是被分成了 两段128MB的空闲内存,这将导致 没有足够的空间支持 再打开⼀个200MB的程序。

上图中产生了 两种内存碎片:
  • 外部内存碎片:产生了多个 不连续的小物理内存,导致新的程序⽆法被装载。
  • 内部内存碎片:程序所有的内存 都被装载到了物理内存,但该程序的部分内存 可能很少被使用,从而导致内存的浪费。
针对这两种内存碎片的问题,解决的方式有所不同。解决外部内存碎片的问题 就是内存交换
例如:可以先将 音乐程序占用的256MB内存 写到硬盘上,然后再将其 从硬盘读回到内存中。读回时,不能将其 装载回原来的位置,而是紧紧跟在 "游戏占用的512MB内存" 的后⾯。如此将空缺出 连续的256MB空间,因此 200MB的程序就可以被装载进来。
在Linux系统中,内存交换空间 就是Swap空间,这块空间是从硬盘划分出来的,用于内存与硬盘的空间交换。
问题:分段为什么会导致 内存的交换效率低?
在多进程的系统中,使用分段的方式 将很容易产生内存碎片。如果产生了内存碎片,则需要重新交换(Swap) 内存区域,这个过程中 将会产生性能瓶颈。因为硬盘的访问速度 比内存慢很多,而每⼀次内存交换 都需要将⼀大段连续的内存数据 写到硬盘上。因此,如果内存交换时,交换的是 ⼀个占内存空间很大的程序,这将导致 整个机器都会显得卡顿
解决:为了解决内存分段时的 内存碎片和内存交换效率低 的问题,就出现了内存分页

3. 内存分页

分段的好处就是 能够产生连续的内存空间,但也会出现 内存碎片和内存交换的空间太大 的问题。要解决这些问题,首先,需要尽量减小 产生的内存碎片;另外,当进行内存交换时,需要尽量减少 交换写入或者从磁盘装载 的数据。解决这些问题的方法就是 内存分页
分页是将整个虚拟和物理内存空间 切成一段段固定尺存的大小的块。这样⼀个 连续并且尺存固定的内存空间,叫做 页(Page)。在Linux系统中,页的大小为 4KB。
虚拟地址与物理地址之间 通过页表来映射,如下图:

页表是存储在内存中的,内存管理单元(MMU) 的作用就是 将虚拟内存地址转换成物理内存地址
当进程访问的虚拟地址 在页表中查不到时,系统会产生⼀个缺页异常,进⼊系统内核空间 分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。

问题:分页是如何解决 分段方式带来的内存碎片、内存交换效率低的问题 的?
由于内存空间都是预先划分好的 (以页为单位),所以不会像分段一样 产生段与段之间的间隙。采用分页,释放的内存 是以页为单位释放的,所以不会产生 无法给进程使用的小内
如果内存空间不够,操作系统就会 将其它正在运行的进程中的 "最近没被使用"的内存页面 给释放掉 (即写回硬盘上),这个过程称为 换出 (swap out)。⼀旦再需要时,就再加载进来,这个过程称为 换入 (swap in)因为交换时 写入磁盘的的数据 只有一页或少数的几页,花费的时间少,因此 内存交换的效率高

分页的方式使得 在加载程序时,不需要 ⼀次性将程序都加载到物理内存中。在进行 虚拟内存页和物理内存页之间 的映射后,并不真的将页加载到 物理内存中,而是在程序运行中 需要用到对应虚拟内存页中的指令和数据时 再真正加载到物理内存中。

问题:分页机制下,虚拟地址和物理地址之间 是如何映射的?