操作系统基础-内存虚拟化

Posted 云服务与SRE架构师社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了操作系统基础-内存虚拟化相关的知识,希望对你有一定的参考价值。

原文发布于微信公众号 - 云服务与SRE架构师社区(ai-cloud-ops),作者李勇。

前言

进程地址空间

  1. 代码段(图中0-1KB的部分),程序的二进制指令保存在这里
  2. 用来存放动态数据的堆(Heap,图中1-2KB的部分), malloc的内存就从这里申请,当现有堆大小不够的时候往高地址扩展
  3. 用来追踪函数调用的栈(Stack,途中16-15KB的部分),里面保存了每个函数调用中的局部变量,参数等信息,每层函数调用都会导致堆往低地址扩展

Base and Bounds

操作系统基础-内存虚拟化

图2 - Base and Bounds

具体实现上,CPU上有两个寄存器用来记录这些信息:

  • Base 寄存器用来记录当前进程在物理内存的起始位置(32K)
  • Bounds 寄存器用来记录该进程地址空间的边界(16K)

毫无疑问,这两个寄存器的值在上下切换的时候需要保存到PCB中。

  1. 这个地址空间太小,虽然我们可以放大这个地址空间(比如说640K……),但无论如何不能超过物理内存的大小
  2. 更严重的是,堆和栈之间有一大片分配了却没有使用的地址,地址空间越大,这里的浪费越明显

段式寻址

操作系统基础-内存虚拟化

图3 - Segmentation

地址前两位
00 代码
01
10
11 内核

段式寻址带来的另一个好处是,如果运行同一个程序的多个副本时,因为代码段是只读的,这些进程的代码段可以映射到同一个物理内存区域。

但是段式寻址没有解决根本问题,假如一个进程申请了一个巨大的堆,比如说1GB,然后释放了这1GB里面大部分的空间,只留下开头和结尾各1KB的空间,这同样导致的浪费。我们需要更精细的内存分配手段。

Pagetable

操作系统基础-内存虚拟化

图4-页表

操作系统基础-内存虚拟化

图5-物理内存

上图可以看到,进程的逻辑页0、1、3分别映射到物理页3、7、2,而逻辑页2没有使用,因此处于没有分配的状态。这里需要一个叫做valid bit的标记位来确定该页是否已经映射到物理内存。

地址翻译

  1. 把16383的二进表示(11111111111111,一共14个1)拆成两部分,后面12位(对应4K页大小)作为页内偏移量(offset),前2位为作为页表索引(Virtual Page Number,或VPN),转换成10进制标的话:
    1. offset = 4095
    2. VPN = 3
  2. CPU从PTBR中读取出进程的页表
  3. 从页表中读出第3项(即VPN指向的PTE),从图4中可以看到,它的内容是2,表示这个逻辑页对应第2个物理页。
  4. 最后可以计算出逻辑地址16383对应的物理地址 2(逻辑页编号) × 4K(页大小) + 4095 (offset) = 12287

Swapping

有些时候,物理内存实在放不下所有进程需要的页,这时候可以在硬盘中划分一个swap分区,把不常用的页换出(Swap out) 到swap分区中,这样物理内存能空出一部分放置别的内容。当需要访问swap分区中的内容时,再用类似的方式淘汰其他不常用的内容,在把swap分区的内容换入(Swap in)到物理内存中。

因此,PTE中其实需要一个叫做present bit的标记位,用来标记这个页对应的内容是否在物理内存中。如果preset bit为1,说明对应的页在物理内存中,PTE的内容表示对应的物理页(PFN);如果为0,说明这个页不在内存中,操作系统可以使用PTE来保存这个页在swap分区中的位置。

Page Table Entry

读者们会注意到,PTE不像图4中展示的那么简单,它至少应该包含两个标记位:

  • Valid bit: 标记该页是否已经映射到物理内存
  • Present bit:标记该页是否在物理内存中。一个页可以是Valid,但是not present的,因为它被换出去了。

PTE上通常还会有些别的标记为,来看看X86的PTE:

操作系统基础-内存虚拟化
图6 - x86 PTE

  • present bit(P):表示页是否在内存中
  • read/write bit(P):页是否可写
  • user/supervisor(P):用户是否可访问这些页
  • PTW/PCD/PAT/G:跟硬件缓存相关
  • access bit(A):该页最近是否访问过,操作系统可以依赖这个位来制定swap的策略
  • dirty bit(D):是否有脏数据需要写回到硬盘的。为什么会有这个位?因为物理页还可以用来缓存文件或者块设备的内容,设置了direct bit的内容需要定期写回到硬盘中。
  • Page Frame Number(PFN):该页对应的物理页号。

我们可以发现:

  1. 这里缺少了一个valid bit,linux用别的方式实现了valid bit,如果整个PTE的内容全为0,那么这个页是未映射的。
  2. 跟文件系统相比,这里缺少了一个可执行权限的判断,攻击者这可以通过缓冲区溢出攻击漏洞在栈中注入可恶意代码,参考《CS:APP Attack Lab: 缓冲区溢出攻击》(https://cloud.tencent.com/developer/article/1590156)。后来x86_64中添加了禁止执行位(No-Execute bit,或NX)来解决这个问题。

有些硬件采用了讨厌的段页式的混合寻址,现代操作系统已经不用这种模式了。

Translation Lookaside Buffer

Pagetable 目前看起来很美好,但是它太慢了,每一次访问内存(包括读取代码段的指令)都额外的计算以及多一次的内存操作:

  1. 根据地址计算出这个地址所在页以及offset
  2. 根据PTBR,从物理内存中读取PTE
  3. 根据PTE和offset计算出物理地址
  4. 从物理地址读取实际内容

多级页表

这个问题的解决方案是使用多级页表,以一个二级页表为例,一级页表的每一项不再指向一个PTE,而是一个叫Page Directory的页;Page Directory包含多个PTE,如下图右边所示:

图7 - 线性页表(左边)和多级页表(右边)

那么多级页表是如何节省空间的呢?如果某个Page Directory中所有的PTE都没有映射,那么直接不分配这个Page Directory,并且在父页表对应的项中把present bit设置为0。

来看一个现实的例子,x86_64中采用了4级页表,每级页表包含512个PTE,每个PTE的大小是8字节,512*8正好是4K,即一个页的大小:

图8 - x86_64 四级页表

关于作者

不怎么务正业的程序员,BUG制造者、CPU0杀手。从事过开发、运维、SRE、技术支持等多个岗位。原Oracle系统架构和性能服务团队成员,目前在腾讯从事运营系统开发。


以上是关于操作系统基础-内存虚拟化的主要内容,如果未能解决你的问题,请参考以下文章

小林coding阅读笔记:操作系统篇之内存管理基础,虚拟内存分段分页

redis使用基础 ——Redis虚拟内存

鸿蒙轻内核虚拟内存基础知识:虚拟内存进程空间编号

(转)虚拟内存与内存映射文件区别与联系

2017-2018-1 20155235 《信息安全系统设计基础》第十一周学习总结

性能测试基础