前言

在操作系统还没有出现之前,程序存放在纸带上,计算机读取一张纸带就运行一条指令,这种从外部存储介质上直接运行指令的方法效率很低。

后来出现了内存存储器,也就是说,程序要运行,首先要加载,然后执行,这就是所谓的 “存储的程序”。这一概念开启了操作系统快速发展的通道,直至后来出现了分页机制。

在这个演变的历史过程中,出现了不少内存管理的机制。

内存

简单地说,内 存就是一个数据货架。内存有一个最小的存储单位,大多数都是一个字节。内存用内存地址(memory address)来为每个字节的数据顺序编号。因此,内存地址说明了数据在内存中的位置。内存地址从0开始,每次增加1。这种线性增加的存储器地址称为线性地址(linear address)。为了方便,我们用十六进制数来表示内存地址,比如0x00000003、0x1A010CB0。这里的“0x”用来表示十六进制。“0x”后面跟着的,就是作为内存地址的十六进制数。

内存地址的编号有上限。地址空间的范围和地址总线(address bus)的位数直接相关。CPU 通过地址总线来向内存说明想要存取数据的地址。以英特尔32位的80386型CPU为例,这款CPU有32个针脚可以传输地址信息。每个针脚对应了一位。如果针脚上是高电压,那么这一位是 1;如果是低电压,那么这一位是0。32 位的电压高低信息通过地址总线传到内存的 32 个针脚,内存就能把电压高低信息转换成 32 位的二进制数,从而知道CPU想要的是哪个位置的数据。用十六进制表示,32位地址空间就是从 0x00000000 到0xFFFFFFFF。

内存的存储单元采用了 随机读取存储器(RAM, Random Access Memory)。 所谓的“随机读取”,是指存储器的读取时间和数据所在位置无关。 与之相对,很多存储器的读取时间和数据所在位置有关。就拿磁带来说,我们想听其中的一首歌,必须转动带子。如果那首歌是第一首,那么立即就可以播放。如果那首歌恰巧是最后一首,我们快进到可以播放的位置就需要花很长时间。我们已经知道,进程需要调用内存中不同位置的数据。如果数据读取时间和位置相关的话,计算机就很难把控进程的运行时间。因此,随机读取的特性是内存成为主存储器的关键因素。

内存提供的存储空间,除了能满足内核的运行需求,还通常能支持运行中的进程。即使进程所需空间超过内存空间,内存空间也可以通过少量拓展来弥补。换句话说,内存的存储能力,和计算机运行状态的数据总量相当。 内存的缺点是不能持久地保存数据,一旦断电,内存中的数据就会消失。 因此,计算机即使有了内存这样一个主存储器,还是需要硬盘这样的外部存储器来提供持久的储存空间。

总结一下内存的特点:

{% tip success %}

  • 数据加载
    • 当程序运行时,CPU会将程序代码和相关数据从硬盘加载到内存中。
  • 快速访问
    • 内存以极高速度向CPU提供所需的数据。
  • 临时性
    • 数据只在设备通电时保留,一旦断电数据就会丢失(易失性特性)。

{% endtip %}

内存的类型

  1. 随机存取存储器(RAM)
    • 动态随机存取存储器(DRAM)
      • 需要不断刷新数据,主要用于计算机的主内存。
    • 静态随机存取存储器(SRAM)
      • 不需要刷新,速度更快,但成本更高,通常用于缓存(Cache)。
  2. 只读存储器(ROM)
    • 存储永久性数据,内容不可修改或只能有限修改。
    • 用于固件,如启动程序(BIOS/UEFI)。
  3. 闪存(Flash Memory)
    • 非易失性内存,常用于USB闪存盘、SSD、手机存储等。
  4. 虚拟内存
    • 当物理内存不足时,操作系统将硬盘的一部分用作虚拟内存(如Windows的页面文件)。

本系列文章主要围绕着RAM为主线,少部分会提到其余类型的内存。

SRAM (Static RAM)

所谓的 “静态”,是指这种存储器只要保持通电,里面储存的数据就可以恒常保持。相比较于 DRAM,其里面所储存的数据就需要周期性地更新。当然,SRAM 在供电停止时,SRAM 储存的数据还是会消失(被称为 volatile memory),这与断电后还能储存数据的 ROM 或闪存是不一样的。

SRAM 主要用于 CPU 内的高速缓存 L1 / L2 / L3 Cache。

优点是访问速度快(1~30 个时钟周期,L1:1~3,L2:4~10,L3:10~30),缺点是容量小价格高。

DRAM (Dynamic RAM)

DRAM 主要用于内存,也就是常说的内存条。DRAM 只能将数据保持很短的时间,为了保持数据,DRAM 使用电容存储,所以必须每个一段时间刷新一次,如果存储单元没有被刷新,存储的信息就会丢失。

DDR SDRAM 是 DRAM 中的一种,全称为 Double Data Rate Synchronous Dynamic Random Access Memory,为具有双倍数据传输率的 SDRAM,其数据传输速度为系统时钟频率的两倍,其性能优于传统的 SDRAM (在一个时钟周期只传输一次数据)。

一个 DDR5 内存条正反面上一共有 16 个 DRAM 芯片,双通道,每个通道 8 个 DRAM。

内存条结构

每个 DRAM 内部是一个二维矩阵,通过行地址(RAS,row access strobe)和列地址(CAS,column access strobe)定位一个 8 bits 数据,也就是每个 DRAM 一次读写 8 bits 数据,一个通道一次就可以读写 64 bits 数据。

DRAM和SRAM的对比

特性SRAMDRAM
全称Static Random Access MemoryDynamic Random Access Memory
存储单元结构使用 六个晶体管(6T) 组成一个存储单元使用 一个晶体管和一个电容(1T+1C) 组成
刷新需求无需刷新,数据能保持直至断电需要定期刷新(通过电容充放电保留数据)
速度更快(几纳秒级别)较慢(几十纳秒级别)
功耗较低(因为无刷新操作)较高(由于频繁刷新)
密度较低(单个单元需要更多晶体管)较高(单元结构简单,单位面积存储更多数据)
成本较高(制造工艺复杂,占用面积大)较低(制造工艺简单,占用面积小)
应用场景用于高速缓存(Cache),如 CPU 的 L1、L2 缓存用于主内存(RAM),如 PC、手机中的 DRAM
数据存储时长断电后数据丢失(易失性)断电后数据丢失(易失性)
访问延迟低延迟高延迟
能耗特性静态功耗低,动态功耗高(适合小容量高速应用)刷新功耗较高(适合大容量存储)

细节解释

  1. 结构差异
    • SRAM 使用多个晶体管构成稳定的触发器(Flip-Flop)来存储一个位的数据,无需电容充放电,因此不需要刷新。
    • DRAM 使用电容存储电荷来表示数据,需要不断地刷新以防止电容泄漏导致数据丢失。
  2. 性能差异
    • SRAM 更快,因为其数据是通过晶体管的稳定电路存储的,不受刷新操作影响。
    • DRAM 刷新会增加延迟,同时其电容存储机制本身访问速度较慢。
  3. 应用场景
    • SRAM 因其高速度和低延迟,常用于对性能要求极高的缓存,如 CPU 的 L1/L2/L3 缓存。
    • DRAM 因其高密度和低成本,适用于系统主内存,提供更大的容量以满足现代计算需求。
  4. 能耗
    • 虽然 SRAM 的静态功耗较低,但其动态功耗随着频繁访问可能增大,尤其是在高速工作时。
    • DRAM 刷新的功耗是其主要消耗来源,尤其是在大容量内存中表现更显著。

{% tip success %}

  • SRAM 更快,但更贵,适合小容量的高速缓存。
  • DRAM 性能稍逊,但容量大且成本低,适合用作主内存。

{% endtip %}

内存的动态分区

动态分区法

  • 假设现在有个 32MB 大小的内存,操作系统使用很小的一块 4MB 大小。剩余的内存会留个其他进程使用。如上图(1) 所示。
  • 进程 A 使用了操作系统往上的10MB 内存,进程 B 使用进程A往上的 6MB 内存,进程 C 使用了进程 B 往上的 8MB 内存。剩余的 4MB 内存不足以装载进程D(若进程D内存大小为5MB)。如图(2) 所示。
  • 如果想装载进程 D,而剩余内存又不足以,这里就形成了内存的第一个空洞 (内存碎片)。当系统需要运行进程 D 时,为了腾出足够的空间,需要选择一个进程来换出,假设操作系统选择了进程 B 来换出,这样进程 D 就装载到了进程B 所在的内存空间。于是,这里就产生了第二个空洞,如图(3) 所示。
  • 假如某一时刻,操作系统需要运行进程B,也需要选择一个进程来换出,假如此次选择了进程A 换出,那么操作系统又产生了第三个空洞,如图(4) 所示。

这种动态分区法,在最开始的时候是好的,但随着时间的推移会出现很多内存空洞,内存的利用率会随之下降,这些内存空洞就是我们常说的内存碎片。为了解决碎片化的问题,操作系统需要动态地移动进程,使得进程占用的内存空间是连续的,并且所有的空闲内存空间也是连续的。而整个进程的迁移是一个非常耗时的过程。

总之,动态分区法存在一下问题:

{% tip success %}

  • 进程地址空间保护问题。 所有的进程都可以访问全部的物理内存,所以恶意的程序可以修改其他程序的内存数据。即使操作系统中的程序都不是恶意的,但是进程A 依然可能不小心修改了进程B 的数据,进而导致进程B 的崩溃。
  • 内存使用效率低。 当运行的进程所需的内存空间不足,就需要选择一个进程换出,这种机制导致大量的数据需要换出和换入,效率非常低下。
  • 程序运行地址重定位问题。 如上图所示,进程在每次换出、换入时使用的地址都是不固定的,这给程序的编写带来了一定的麻烦,因为访问数据和指令跳转时的目标地址通常是固定的,这就需要重定位技术。

{% endtip %}

内存分段机制

内存分段(Segmentation) 是一种内存管理机制,将程序的内存空间划分为多个逻辑分段(Segment),每个段都有一个名称和一组属性。这种机制基于逻辑内存划分,不同的段可以存储不同类型的数据,如代码段、数据段、堆栈段等。

分段( segmentation) 机制基本思想是把程序所需的内存空间的虚拟地址映射到某个物理地址空间中。

分段机制可以解决进程地址空间保护问题,进程 A 和进程 B 会被映射到不同的物理地址空间,他们的物理地址是不会重叠的。因为存在映射关系,当一个进程访问了没有映射的虚拟地址空间,或者访问了不属于该进程的虚拟地址空间,那么 CPU 会捕捉这个越界访问,并拒绝该次访问。同时CPU 会发送一个异常给操作系统,有操作系统处理这些异常,这就是我们常说的 缺页异常

另外,对于进程来说,不需要关心物理地址空间的布局,进程访问的都是虚拟地址,只需要按照原来的地址编写程序和访问地址,这样程序就可以无缝地迁移到不同的操作系统中。

分段机制会将进程 虚拟地址 分成很多段,如 代码段、数据段、堆段和栈段 等。这些段的物理地址可以不连续,这样可以解决内存碎片问题,但是会产生外部碎片。程序员需要为每个进程的各个段合理分配物理地址空间,否则容易发生问题。

分段机制是一个比较明显的改善, 但是 它的内存使用效率依然很低。分段机制对虚拟内存到物理内存的映射依然 以进程为单位,当内存不足时,通常做法是把这个进程的所有段都换出到磁盘,因此导致大量的磁盘访问,从而影响系统性能。

关于内存分段的细节,将在后续的内存分段篇详细介绍

内存分页机制

在分段机制中,在以进程为单位的换入、换出时确实会影响系统性能,而对于进程,根据局部性原理,只有一部分数据是一直使用的,若把那些不常用的数据交换出磁盘,就可以节省很多系统带宽,而那些常用的数据驻留在物理内存中,因此可以得到很好的性能,于是人们在分段机制的实践之后发明了新的机制——分页(paging)机制

分页机制就是把分段机制的单位细分到页面,进程的虚拟地址空间也按照页面来分割,这样常用的数据和代码就可以以页面为单位驻留在内存中。而那些不常用的页面即可以交换到磁盘中,从而节省物理内存,这比分段机制要高效很多。

进程以页面为单位的虚拟地址通过 CPU 映射到物理内存中,物理内存也以页面为单位来管理,这些物理内存称为物理页面(physical page) 或页帧(page frame)。操作系统为了管理这些页帧,给每个页帧进行了编号,这个编号称为页帧号(Page Frame NumberPFN)。

进程中的虚拟地址空间中的页面称为虚拟页面(virtual page)。

常用的操作系统中默认支持的页面大小为 4KB,但是 CPU 通常可以支持多种大小的页面,如4KB、16KB及64KB。另外,现在的计算机系统 内存已经变得很大,特别是服务器上使用以 TB 为单位的内存,所以使用 4KB 的内存页面会产生很多性能上的缺陷。现代的处理器都支持大页面(Huge page),如Intel 的至强处理器支持 2MB 和 1GB 为单位的大页面,以提高应用程序的效率。

关于内存分页的细节,将在后续的内存分段篇详细介绍

内存分段和内存分页的对比

特性分段(Segmentation)分页(Paging)
划分依据按程序逻辑划分,如代码段、数据段按固定大小的页划分
地址结构段号 + 段内偏移量页号 + 页内偏移量
段/页大小不固定固定大小
碎片类型外部碎片内部碎片
硬件支持需要段表和段寄存器需要页表和页框
实现复杂性相对较高较低
虚拟内存支持不适合非常适合

MMU

分页机制的实现离不开硬件的支持,在CPU 内部有一个专门的硬件单元来负责这个虚拟页面到物理页面的转换,这就是 MMU。ARM 处理器的 MMU 包括TLB 和页表遍历单元两个部件。

{% tip success %}

关于MMU将虚拟地址转成物理地址中arm core如何处理的?可以查看

ARMv8地址翻译

{% endtip %}