内存管理基础概念总述


  1. 背景
  2. 内存共享
  3. 分段
    1. 误解一:内存分段 vs 链接器分段
    2. 误解二:物理地址分段 vs 虚拟地址分段
      1. 物理地址分段
      2. 虚拟地址分段
    3. 分页
      1. 页 vs 页框
  4. 地址的产生
  5. 地址映射
  6. 总结

背景

本文主要思考的问题如下:

  1. 内存管理的复杂的根源是什么?
    • 分段
  2. 地址是怎么产生的?
    • 逻辑地址 vs 虚拟地址 vs 物理地址
    • 页 vs 页框
  3. 为什么使用映射表做地址转换?

内存共享

操作系统允许多个程序并发(或并行)的访问内存的前提下,有以下几种使用内存的方案

  • 内存分割:

    将内存进行分割成若干块,每个进程占用一部分内存。
    缺点:

    1. 需要事先确定分割块数
    2. 内存使用率不高,资源浪费
  • 内存独占:

    运行中的进程独占内存,等待运行的进程换出到磁盘
    缺点:

    1. 内存换入换出,性能差
    2. 内存使用率不高
    3. 无法支持多核并行
  • 内存共享:

    多个进程共享内
    缺点:

    1. 复杂度高:需要解决好以下问题
      1. 如何管理内存空间(尤其是应用程序)
      2. 如何保证内存隔离,避免进程间互相破坏,进程破坏操作系统
      3. 如何保证内存共享不会大幅影响CPU和内存的性能

在1940 ~ 1950年代,所有较大的程序都必须包含管理内存和二级存储的逻辑,例如overlaying。为了允许多程序和多任务,许多早期系统在多个程序之间划分内存,例如:早期模型 PDP-10

有一种经不起推敲的说法,虚拟内存的概念的来源是德国物理学家弗里茨·鲁道夫·居特施于1956年在柏林理工大学发表的博士论文《Logical Design of a Digital Computer with Multiple Asynchronous Rotating Drums and Automatic High Speed Memory Operation》。后续虚拟内存的概念不断发展壮大。

分段

如果要分割内存,总要寻找一种内存分割的方式。与分页相比,分段存在太多太多的误解。

误解一:内存分段 vs 链接器分段

“内存分段”表明这是一个内存寻址问题。了解ELF格式的人都知道,段寄存器对像“代码段”(在对象文件中)这样的东西并没有任何用处。 那么计算机是如何利用代码段/文本段呢?

  • 一方面,对于 链接器段 和 CPU 段 具有同样的模糊性
  • 另一方面,段寄存器的信息通常来自 “.text” ,因此 “代码段”(CPU)通常意味着“文本段”(linker)

p2-overview.jpg

在 objdump 的文档中,类似的部分 被称为节(section),而不是段(segment)

误解二:物理地址分段 vs 虚拟地址分段

虽然分段倍定位为“将计算机的内存分割成若干段”,然而物理内存的分段和虚拟内存的分段并非同一回事,因此不应混为一谈。

物理地址分段

物理地址分段实际上是在讨论如何将“计算机的内存分成若干段”,实际上说的段是指:Intel 8086机器的偏移寻址方案,使用20位物理(线性)地址,将16位段寄存器向左移4位并添加16位偏移寄存器。这是一种扩展可寻址内存空间(尽管只有20位)的方法,而不必为获得32位地址空间组合两个寄存器。

虚拟地址分段

物理地址分段实际上是在讨论如何将进程的虚拟地址空间划分为逻辑段,因为它的主要优点是允许在时间域和空间域中分别加载不同的段,从而允许灵活地加载进程,还允许进程之间共享段。

前者被称为 实模式,以兼容x86处理器;后者被称为保护模式,或许是因为虚拟空间下已经完整实现了内存保护和隔离。前者已经被历史所淘汰,后者在虚拟内存得到应用。

分页

对于虚拟内存管理,仅仅有分段还不够,因为段不够标准(大小不同)。要把虚拟内存自由映射到物理内存,就需要引入页的概念。

页 vs 页框

然而,页并不真实存在,它只是一块标准大小的数据,实际存放数据的页框才是真正标准大小的物理内存。

地址的产生

程序如何生成指令和数据地址?

  • 编译:源代码经编译、链接后得到程序。由于无法预先知道程序装入内存的具体位置,因此不可能直接使用内存地址。因此编译器需要一种特定的地址来生成程序。
  • 加载:程序在加载时,操作系统需要确定进程的地址,由于虚拟内存的原因,此时操作系统分配地址也不能是实际的物理地址。因此操作系统需要一种特定的地址用来加载程序。
  • 执行:无论是编译时产生的地址,还是操作系统加载程序时的地址,到最后都需要实际落地到物理内存中特定位置。因此,访问物理内存需要第三种地址。

三种依次被称作:逻辑地址、虚拟地址、物理地址。

  • 逻辑地址:每个逻辑地址都由一个段(segment)和段内偏移量(offset)组成。编译器将程序数据分为若干段。
  • 虚拟地址:操作系统抽象出来的地址为虚拟地址,由两部分组成:页和页内偏移量。
  • 物理地址:内存中实际存储单元的地址称为物理地址由于每个内存单元都有唯一的内存地址编号,因此物理地址空间是一个一维的线性空间。

要使装入内存的程序后能够正常运行,就必须将逻辑地址转换为虚拟地址,最后再转换为物理地址,如下图所示:

20117114nSfPg83s0J.png

地址映射

通俗来看,用两种方式可以实现地址映射:

  • 固定映射:即按照固定的偏移量,将地址直接映射到特定的位置。

    缺点:僵硬

    1. 需要修改程序代码,多份代码拷贝无法共享
    2. 加载完成,无法移动修改
  • 动态映射:即建立映射表,在每次访问时根据映射表确定映射的位置。

    缺点:复杂

    1. 有一定空间和性能消耗
      优点:灵活

地址映射在操作系统层面有特定的称呼:地址重定位。前者称为静态重定位,后者称为动态重定位。看起来前者明明更简单好用,现在操作系统却没有使用前者作为地址重定位的方案。

如果考虑到内存共享的两大根本:

  1. 换入换出
  2. 局部性原理

内存换入换出使得静态重定位不再简单好用。如果再加上内存移动,那么静态重定位将直接无法使用。

总结

很多人觉得内存管理就是完整的一块,然而事实并非如此。内存管理包括:物理内存管理、虚拟内存管理(内核管理)、进程内存管理、内存映射等。理解这些的基础是,了解清楚很多模糊的概念,从设计的角度出发理解内存管理要实现的能力。

本文作者:cyningsun
本文地址https://www.cyningsun.com/12-02-2020/memory-management-summary.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!

# Linux