查看原文
其他

想和你聊聊操作系统的内存管理

脚本之家 2022-04-23

The following article is from java小杰要加油 Author 好懂事一男的

 关注
“脚本之家
”,与百万开发者在一起

来源 | java小杰要加油(ID:xhJaver)

物理地址 VS 虚拟地址

  • 物理地址:逻辑上,我们可以把物理内存看成一个大数组,其中每个字节都可以通过与之对应的地址进行访问,这个地址就叫做物理地址

  • 虚拟地址 :应用程序在运行时使用的地址

CPU翻译虚拟地址的过程大概如图所示

他们的包含关系如下:cpu包含MMU,MMU包含TLB

  • CPU
    • TLB(转址旁路缓存 Translation Lookaside Buffer):加速地址翻译的过程
    • MMU(内存管理单元 Memory Management Unit): 负责虚拟地址到物理地址的转换

平常加载程序的顺序是

  1. 操作系统把程序从磁盘加载到内存中(程序一开始是在磁盘中存放的)
  2. CPU去执行程序的第一条指令但是这个指令现在在物理内存
  3. cpu取指令取的是该指令的虚拟地址,由MMU翻译为物理地址
  4. 这个读物理地址的请求将通过总线,传送到相应的物理内存中,然后物理内存把该指令发送给CPU

分段

MMU虚拟地址翻译为物理地址主要有种机制 :分段分页

分段机制

  • 操作系统以“段”(一段连续的物理内存)的形式管理/分配物理内存
  • 应用程序的虚拟地址空间由若干个大小不同的段组成:代码段、数据段等等
  • CPU访问虚拟地址中的某一个段的时候,MMU会通过查询段表来得到该段对应的物理地址

虚拟地址:

  • 段号: 标志着该虚拟地址属于整个虚拟地址空间中的哪一段
  • 段内地址(段内偏移): 相对于该段起始地址的偏移量

当 cpu 读取指令时,发现指令的地址是虚拟地址,那么CPU中的MMU 首先判断这个段号是否合法,如果合法, 则通过 段表基址寄存器  找到段表的位置,通过虚拟地址中的段号,找到该段的起始地址,再加上段内地址(段内偏移),就可以得到最终的物理地址

  • 在分段机制下,虚拟内存物理内存都划分成了不同的段

分段缺点

  • 在虚拟地址空间中,相邻的段所对应的物理内存空间可以不相邻,操作系统能够实现物理内存资源的离散分配,但是这种段式分配方式容易导致在物理内存上出现外部碎片

图中装载不进来的就是外部碎片

分页机制

  • 基本思想:
    • 将应用程序的虚拟地址空间划分为连续的等长的虚拟页(4K)
    • 物理地址也是划分为连续的等长的的物理页
    • 物理页虚拟页页长固定且相等

之所以这样构造是因为会使操作系统很方便的为每个应用程序构造页表,即虚拟页和物理页映射关系表

  • 在分页机制下,应用程序虚拟地址空间中的任意虚拟页可以被映射到物理内存中的任意物理页上,可以避免外部碎片的问题

  • 分页机制下的虚拟地址也由两部分组成:虚拟页号: 页内偏移量:

翻译的具体流程就是:

  1. MMU首先解析虚拟地址中的虚拟页号,检查这个虚拟页号是否合法,通过这个虚拟页号取该应用程序的虚拟页表中找到对应条目(页表起始地址放在页表基地址寄存器
  2. 然后取出该条目中的物理页号
  3. 最后用该物理页号对应的物理起始地址加上虚拟地址中的页内偏移得到最终的物理地址

TLB

首先要说一下局部性原理

  • 时间局部性: 如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行,如果某个数据被访问过,不久后该数据很可能再次被访问(因为程序中存在大量的循环)
  • 空间局部性: 一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能会被访问(因为很多数据在内存中都是连续存放的)

所以,能不能弄一个缓存,缓存这些有可能会被经常被访问的数据呢,从而减少访问页表的次数呢?

为了减少地址翻译的访问次数,MMU引入TLB(转址旁路缓存 Translation Lookaside Buffer)

  • TLB 硬件采用分层架构,分为L1、L2两层
    • LI又分为数据TLB和指令TLB,分别缓存数据和指令的地址翻译
    • L2不区分数据和指令
  • TLB缓存了虚拟页号和物理页号的映射关系,类似map,key是虚拟页号,value是物理页号。
    • 如果在TLB中找到则称为TLB命中
    • 没有找到则称之为TLB未命中

有了TLB之后,查询就变成了

  • 1.  MMU首先解析虚拟地址中的虚拟页号,检查这个虚拟页号是否合法,如果合法
    • 查TLB,如果命中则 直接取出物理初始地址,再加上页内偏移量得到最终物理地址,否则继续查询页表
    • 如果页表中存在物理初始地址,则将此物理初始地址缓存到TLB中 通过这个虚拟页号取该应用程序的虚拟页表中找到对应条目(页表起始地址放在页表基地址寄存器
  1. 然后取出该条目中的物理页号
  2. 最后用该物理页号对应的物理起始地址加上虚拟地址中的页内偏移得到最终的物理地址

多级页表

  • 如果页表太大时怎么办,页表必须连续存放,会占用很多内存,所以就把一个大表拆成很多小表

拆分后的访问顺序如图所示

  • 根据一级页号查找到物理页号,这个物理页号里面装的是二级页表的地址,找到此地址后,在根据二级页号 找到物理地址,此物理地址在加上页内偏移量则为最终的物理地址

换页与缺页异常

换页

虚拟内存中的换页:当物理内存容量不够的时候,操作系统应当把若干物理页的内容写到磁盘这种大容量的地方,然后回收物理页并继续使用

举例:有个应用程序A,A的虚拟页K对应物理页V,这个时候,操作系统想回收物理页V,要怎么做呢?

  • 操作系统把V写到磁盘上
  • 并且在A的页表中除去虚拟页K和物理页V的映射,同时记录物理页V被换到磁盘上的对应的位置

以上这两部被称为物理页V的换出

缺页异常

缺页异常是换页机制能够工作的前提,当应用程序访问已经分配但是未映射至物理内存的虚拟页时,就会触发缺页异常

  • 如何解决:通过换入
    • cpu会运行操作系统预先设置的缺页异常处理函数,该函数会找到一个空闲的物理页,
    • 将以前写入到磁盘上的内容重新加载到该空闲的物理页
    • 然后将虚拟地址和此物理地址映射起来

处理完这一切后,cpu回到发生缺页异常的地方继续运行

段页式内存管理

  • 分段管理

    • 优点: 很方便的按照逻辑模块实现信息的共享和保护
    • 缺点: 容易产生外部碎片
  • 分页管理

    • 优点 内存空间利用率高,不会产生外部碎片,只会有少量页内碎片
    • 缺点: 不方便按照逻辑模块实现信息的共享和保护
  • 段页式内存管理

    • 将地址空间按照程序自身的逻辑关系分为若干层,将各段分为大小相等的页面
    • 将物理内存与虚拟内存划分为大小相等的一个个的内存块,系统以块为单位为进程分配内存
    • 逻辑地址/虚拟地址(段号,页号,页内偏移量)

虚拟地址翻译为物理地址的步骤变为

  • 根据逻辑地址取出其中的段号,判断这个段号是否正常
  • 如果正常,则找到该段号对应的页表初始地址
  • 根据页号是否正常,若正常则根据页号找到物理初始地址,在加上页内偏移量则找到真正的物理地址

关注脚本之家视频号,观看视频互动留言

↓↓↓

参与视频号留言互动
每天送出 5个 8.88 元红包

  推荐阅读:

主板上这家伙,要当CPU和内存的中间商!

10 个内存引发的大坑,你能躲开几个?

18张图揭秘高性能Linux服务器内存池技术是如何实现的

内存分页不就够了?为什么还要分段?

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存