Linux 操作系统原理 — 内存 — 基于 MMU 硬件单元的虚/实地址映射技术

目录

文章目录

  • 目录
  • 前文列表
  • 物理地址与虚拟地址
  • 内存空间的组织方式
  • 虚拟地址空间的编址
    • 内核态地址空间
    • 用户态地址空间
  • 内-外存空间的交换与虚拟存储空间之间的映射关系
    • 缺页异常

前文列表

《Linux 操作系统原理 — 内存 — 物理存储器与虚拟存储器》

物理地址与虚拟地址

物理地址:即物理主存的地址空间。主存被组织成一个由 M 个连续的、字节大小相同的单元组成的数组,每字节都有一个唯一的物理地址(Physical Address,PA)。第一个字节的地址为 0,接下来的字节的地址为 1,依此类推。给定这种简单的结构,CPU 访问存储器的最自然的方式就是使用物理地址,即物理寻址。

虚拟地址:即虚拟存储地址空间,它能够让用户态应用程序以为自己拥有一块连续可用的 “物理” 地址,但实际上从程序视角所见的都是虚拟地址,而且这些虚拟地址对应的物理主存空间通常可能是碎片的,甚至有部分数据还可能会被暂时储存在外部磁盘设备上,在需要时才进行数据交换。

虚拟存储器为了实现虚拟地址空间的 隔离性连续性,就必须满足以下条件:

  1. 新的 内存组织 方式。
  2. 新的 虚拟地址空间编址 方式。
  3. 新的 内存分配 方式。
  4. 新的 物理地址和虚拟地址的转换 方式。

内存空间的组织方式

为了让数据得以在内存和外存之前高速交换,也为了让虚拟地址和物理地址之间可以进行高效转换。若使用传统的字节流方式进行内-外存是数据传输,总线的传输速率和磁盘的读写速率必然会成为瓶颈。因此,虚拟存储器需要实现特殊的内存组织方式来满足以上两点需求。

虚拟内存管理方式可以简单分为连续分配管理方式和非连续分配管理方式这两种:

  • 连续分配管理方式:为一个用户程序分配一个连续的内存空间,常见的如块式管理。
  • 非连续分配管理方式:允许一个用户程序使用的内存,分布在离散的内存空间中,常见的如页式管理、段式管理,以及综合了两者优点的段页式管理。

从物理地址和虚拟地址的特征可以看出,虚拟存储器地址映射的实现是建立在离散分配的内存管理方式的基础之上的

  • 块式管理 (远古时代的计算机操系统的内存管理方式,暂且不暂开):将内存分为几个固定大小的块,每个块中只包含一个进程。如果程序运行需要内存的话,操作系统就分配给它一块,如果程序运行只需要很小的空间的话,分配的这块内存很大一部分几乎被浪费了。这些在每个块中未被利用的空间,称之为碎片。

  • 页式管理 :把内存分为大小相等且固定的一页一页的形式,页较小,相对相比于块式管理的划分力度更大,提高了内存利用率,减少了碎片。页式管理通过页表对应逻辑地址和物理地址

  • 段式管理:页式管理虽然提高了内存利用率,但是页式管理其中的页对于用户程序而言并无实际意义。段式管理则是把内存分为多个段,每段的空间要比一页的空间大些。但最重要的是段对于用户程序而言是有实际意义的,每个段定义了一组逻辑信息,例如:有主程序段 main、子程序段 X、数据段 D 及栈段 S 等。段式管理通过段表对应逻辑地址和物理地址

  • 段页式管理:结合了段式管理和页式管理的优点。先将用户程序分成若干个段,再把每个段分成若干个页,并为每一个段赋予一个段名。如此的,段页式管理机制中段与段之间以及段的内部的都是离散的。

详见《Linux 操作系统原理 — 内存 — 页式管理、段式管理与段页式管理》

虚拟地址空间的编址

在 32 位 CPU 中,Linux 上的虚拟地址空间为 4G。在 64 位 CPU 中的虚拟地址空间为 16G。在 64 位的很多应用场景中,实际的物理内存可能远远小于虚拟内存的大小。所以这里以 32 位举例,Linux 会为每个进程维护一个单独的虚拟地址空间。

  • 0~3G 用户态地址空间:是某个进程独有的,进程之间互相隔离。
  • 3~4G 内核态地址空间:所有的进程,以及内核态线程都会共享这部分地址空间。

所以,我们习惯的将 Linux 虚拟存储器系统分为 内核虚拟存储器进程虚拟存储器。则虚拟地址空间又可以分为 内核地址空间用户地址空间

Linux 操作系统原理 — 内存 — 基于 MMU 硬件单元的虚/实地址映射技术_第1张图片

Linux 操作系统原理 — 内存 — 基于 MMU 硬件单元的虚/实地址映射技术_第2张图片

内核态地址空间

内核态地址空间包含了内核的代码和数据结构,其中的某些区域被映射到给所有进程共享的物理内存页面存储所有进程共享的内核代码和全局数据结构;其他区域则包含每个进程都不相同的数据。例如:用户进程页表、内核在进程的上下文中执行代码时使用的栈(Stack),以及记录虚拟地址空间当前组织的各种数据结构。

在这里插入图片描述

  • 直接映射区:线性空间中从 3G 开始最大 896M 的区间,为直接内存映射区。
  • 动态内存映射区:该区域由内核函数 vmalloc 来分配。
  • 持久映射区:该区域和 4G 的顶端只有 4k 的隔离带,其每个地址项都服务于特定的用途,如:ACPI_BASE 等。
  • 永久内核映射区:该区域可访问高端内存。

Linux 操作系统原理 — 内存 — 基于 MMU 硬件单元的虚/实地址映射技术_第3张图片

用户态地址空间

Linux 操作系统原理 — 内存 — 基于 MMU 硬件单元的虚/实地址映射技术_第4张图片

在这里插入图片描述

  • stack:用户进程栈。
  • mmap 共享库映射区:共享库及匿名文件的映射区域。
  • heap 运行时堆:在程序运行过程中使用 malloc() 申请的内存区域。
  • .bss 段:存放程序中未初始化的全局变量。
  • .data 数据段:存放程序中已经初始化的全局变量。
  • .text 代码段:存放可执行代码、字符串字面值、只读变量。

C 语言程序中的变量在用户地址空间中有四个区域可以存放:
Linux 操作系统原理 — 内存 — 基于 MMU 硬件单元的虚/实地址映射技术_第5张图片

·执行用户进程时,需要先从内存中读取该进程的指令,然后执行,获取指令时用到的就是虚拟地址。这个虚拟地址是程序链接时确定的(内核加载并初始化进程时会调整动态库的地址范围)。为了获取到实际的数据,CPU 需要将虚拟地址转换成物理地址。

由于每个进程都有 3G 的用户态地址空间,所以操作系统的物理内存无法对这些地址空间进行一一映射。因此 Linux kernel 需要一种机制,把进程的用户态地址空间映射到物理内存上。当一个用户进程请求访问物理内存时,内核通过存储在内核态地址空间中的进程页表(Page Table),把这个虚拟地址映射到物理地址。最基本的映射单位是页(Page),对应为页面上的是页表项(PTE)。

页表是由内核维护的,里面的每个内存映射(Memory Mapping)都将一块虚拟地址映射到一个特定的物理地址空间(物理内存或者磁盘存储空间)。每个进程拥有自己的页表,和其他进程的页表没有关系,以此实现了用户程序间虚拟空间的隔离。

Linux 操作系统原理 — 内存 — 基于 MMU 硬件单元的虚/实地址映射技术_第6张图片

Linux 操作系统原理 — 内存 — 基于 MMU 硬件单元的虚/实地址映射技术_第7张图片

Linux 操作系统原理 — 内存 — 基于 MMU 硬件单元的虚/实地址映射技术_第8张图片

另外,值得注意的是,在每个进程创建加载时,内核只是为进程 “创建” 了虚拟内存的布局(e.g. 初始化进程控制表中内存相关的链表),实际上并不立即就把虚拟内存对应位置的程序数据和代码(e.g. .text、.data 段)拷贝到物理内存中,只是建立好虚拟内存和磁盘文件之间的映射(叫做存储器映射)。等到运行到对应的程序时,才会通过缺页异常,来拷贝数据到内存空间。还有进程运行过程中,要动态分配内存,比如:malloc 时也只是分配了虚拟内存,即为这块虚拟内存对应的页表项做相应设置,当进程真正访问到此数据时,才引发缺页异常,继而写入数据。

用户进程申请并访问物理内存(或磁盘存储空间)的过程如下

  1. 用户进程向操作系统发出内存申请请求。
  2. 系统会检查进程的虚拟地址空间是否被用完,如果有剩余,给进程分配虚拟地址。
  3. 系统为这块虚拟地址创建内存映射(Memory Mapping),并将它放进该进程的页表(Page Table)。
  4. 系统返回虚拟地址给用户进程,用户进程开始访问该虚拟地址。
  5. CPU 根据虚拟地址在此进程的页表(Page Table)中找到了相应的内存映射(Memory Mapping),但是这个内存映射(Memory Mapping)没有和物理内存关联,于是产生缺页中断。
  6. 操作系统收到缺页中断后,分配真正的物理内存并将它关联到页表相应的内存映射(Memory Mapping)。中断处理完成后,CPU 就可以访问内存了
  7. 当然缺页中断不是每次都会发生,只有系统觉得有必要延迟分配内存的时候才用的着,也即很多时候在上面的第 3 步系统会分配真正的物理内存并和内存映射(Memory Mapping)进行关联。

内-外存空间的交换与虚拟存储空间之间的映射关系

Linux 操作系统原理 — 内存 — 基于 MMU 硬件单元的虚/实地址映射技术_第9张图片

我们可以简易的认为虚拟空间都被映射到了磁盘空间中(事实上这种映射是按需的,通过 mmap 函数),并且由页表记录映射位置,当访问到某个地址的时候,通过页表中的有效位,可以得知此数据是否在内存中,如果不是,则通过缺页异常,将磁盘对应的数据拷贝到内存中,如果没有空闲内存,则选择牺牲页面,替换其他页面。

mmap 是用来建立从虚拟空间到磁盘空间的映射的,可以将一个虚拟空间地址映射到一个磁盘文件上,当不设置这个地址时,则由系统自动设置,函数返回对应的虚拟内存地址,当访问这个地址的时候,就需要把磁盘上的内容拷贝到内存了,然后就可以读或者写,最后通过 man_map 可以将内存上的数据换回到磁盘,也就是解除虚拟空间和内存空间的映射,这也是一种读写磁盘文件的方法,也是一种进程共享数据的方法,即共享内存。

缺页异常

  • 通过 get_free_pages 申请一个或多个物理页面。
  • 换算 addr 在进程 pdg 映射中所在的 pte 地址。
  • 将 addr 对应的 pte 设置为物理页面的首地址。
  • 系统调用:Brk—申请内存小于等于 128kb,do_map—申请内存大于 128kb。

Linux 操作系统原理 — 内存 — 基于 MMU 硬件单元的虚/实地址映射技术_第10张图片

你可能感兴趣的:(Linux,操作系统原理)