计算机虚拟存储器的一点琐碎记录

本篇是关于计算机虚拟存储器的一点琐碎记录。

概述

为了更加有效地管理存储器并且少出错,现代系统提供了一种对主存的抽象概念,叫做虚拟存储器(VM)。

目的

  1. 有效地管理存储器。为不同进程合理的分配存储器资源,为每个进程提供一致的、私有的地址空间。
  2. 少出错。避免进程间相互写彼此的储存器。

三个重要的能力

  1. 将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并且根据需要在磁盘和主存之间来回传送数据,通过这种方式,高效地使用了主存。
  2. 为每个进程提供了一致的地址空间,从而简化了存储器管理。
  3. 它保护了每个进程的地址空间不被其他进程破坏。

物理寻址

计算机系统的主存被组织成一个由M个连续的字节大小的单元组成的数组。每个字节都有一个唯一的物理地址(Physical Address,PA)。
CPU访问存储器最自然的方式就是使用物理地址。这种方式称为物理寻址(physical addressing)。

虚拟寻址

现代处理器使用的是一种称为虚拟寻址(virtual addressing)的寻址方式。
将一个虚拟地址转换为物理地址的任务叫做地址翻译(address translation)。

物理内存、虚拟内存

物理内存也称为主存,大多数计算机用的主存都是动态随机访问内存(DRAM)。只有内核才可以直接访问物理内存。
操作系统给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。这样,进程就可以很方便地访问内存,更确切地说是访问虚拟内存。
虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同字长(单个 CPU 指令可以处理数据的最大长度,如32位或64位)的处理器,地址空间的范围也不同。
物理内存只有进程真正去访问虚拟地址,发生缺页中断时,才分配实际的物理页面,建立物理内存和虚拟内存的映射关系。
应用程序操作的是虚拟内存;而处理器直接操作的却是物理内存。
当应用程序访问虚拟地址,必须将虚拟地址转化为物理地址,处理器才能解析地址访问请求。
主存中的每个字节有两个地址:
一个选自虚拟地址空间的虚拟地址,一个选自物理地址空间的物理地址。
(image:geekbang.org)

Linux虚拟内存空间

用户空间内存,从低到高分别是五种不同的内存段。
  1. 只读段,包括代码和常量等。
  2. 数据段,包括全局变量等。
  3. 堆,包括动态分配的内存,从低地址开始向上增长。
  4. 文件映射段,包括动态库、共享内存等,从高地址开始向下增长。
  5. 栈,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。
其中,堆和文件映射段的内存是动态分配的。比如说,使用 C 标准库的 malloc() 或者 mmap() ,就可以分别在堆和文件映射段动态分配内存。
malloc() 是 C 标准库提供的内存分配函数,对应到系统调用上,有两种实现方式,即 brk() 和 mmap()。
分配小块内存(小于128K),C标准库brk(),通过移动堆顶位置来分配。内存释放后,缓存起来以供复用。
分配大块内存(大于128K),使用内存映射mmap()来分配,在文件映射段找一块空闲内存分配。
Linux 使用伙伴系统来管理内存分配,以页为单位来管理内存,并且会通过相邻页的合并,减少内存碎片化(比如 brk 方式造成的内存碎片)。
在用户空间,malloc 通过 brk() 分配的内存,在释放时并不立即归还系统,而是缓存起来重复利用。
在内核空间,Linux 则通过 slab 分配器来管理小内存。可以把 slab 看成构建在伙伴系统上的一个缓存,主要作用就是分配并释放内核中的小对象。
(本段:geekbang.org)

内存映射、页表

只有那些实际使用的虚拟内存才分配物理内存,并且分配后的物理内存,是通过内存映射来管理的。
内存映射,就是将虚拟内存地址映射到物理内存地址。
页表,用于将虚拟页映射到物理页,记录虚拟地址与物理地址的映射关系。内核为每个进程都维护了一张页表。
页表实际上存储在 CPU 的内存管理单元 MMU(Memory Management Unit)中。
磁盘上的数据被分割成块,作为磁盘和主存之间的传输单元。
  • 虚拟页(Virtual Page,VP):将虚拟存储器分割为大小固定的块。
  • 物理页(Physical Page,PP):将物理存储器分割为大小固定的块。也称为页帧(page frame)

地址翻译

地址翻译(address translation),将一个虚拟地址转换为物理地址。
地址翻译硬件,位于MMU(存储器管理单元)中。
每次地址翻译硬件将一个虚拟地址转换为物理地址时都会读取页表。
翻译后备缓冲区TLB(Translation Lookaside Buffer),是 MMU 中页表的高速缓存。
(白板画的,有点丑啊)
操作系统为每个进程提供了一个独立的页表,因而也就是一个独立的虚拟地址空间。
多个虚拟页面可以映射到同一个共享物理页面上。
(ProcessOn画的好看,比较花时间)

页表项过多问题

为了解决页表项过多的问题,Linux 提供了两种机制,也就是多级页表和大页(HugePage)。

多级页表

多级页表就是把内存分成区块来管理,将原来的映射关系改成区块索引和区块内的偏移。
多级页表就只保存使用中的区块,这样就可以大大地减少页表的项数。

大页

大页,就是比普通页更大的内存块,常见的大小有 2MB 和 1GB。
大页通常用在使用大量内存的进程上,比如 Oracle、DPDK 等。

页命中、缺页

页命中,DRAM缓存命中。虚拟地址所对应的字被缓存在物理存储器中。PTE对应的标志位为有效。
缺页,DRAM缓存不命中,称为缺页(page fault)。

页面调度

磁盘和存储器之间传送页的活动叫做交换(swapping)或者页面调度(paging)。
页从磁盘换入(或者页面调入)DRAM和从DRAM换出(或者页面调出)磁盘。
一直等待,直到最后时刻,也就是当有不命中发生时,才换入页面的这种策略称为按需页面调度(demand paging)。

按需换页

支持按需换页的操作系统将虚拟内存按需映射到物理内存。
虚拟内存和按需换页的结果,是任何虚拟内存页可能处于如下的一个状态:
A. 未分配
B. 已分配,未映射(未填充并且未缺页)
C. 已分配,已映射到主存(RAM)
D. 已分配,已映射到物理交换空间(磁盘)
  • 常驻集合大小(RSS):已分配的主存页(C)大小。
  • 虚拟内存大小:所有已分配的区域(B+C+D)。
如果因为系统内存压力而换出页就会到达D状态。状态B到C的转变就是缺页。
如果需要磁盘读写,就是严重缺页,否则就是轻微缺页(内存映射可以由内存中的其他页满足)。

最后

非常精美的一张图,借用一下哈(from https://my.oschina.net/fileoptions/blog/968320)

参考

《深入理解计算机原理》
《Linux性能优化实战》
《性能之颠》
https://my.oschina.net/fileoptions/blog/968320

---转载本站文章请注明作者和出处 二进制之路(binarylife.icu),请勿用于任何商业用途---

留下评论