虚拟内存

1️⃣ 什么是虚拟地址?

操作系统 为每个进程都分配了独立的 虚拟地址。并且,操作系统提供一种机制,将不同进程的虚拟地址和不同的物理内存地址映射起来。如果程序要访问虚拟地址,就由操作系统转换成不同的物理内存地址。

之后,进程持有的虚拟内存地址会通过 CPU 芯片中的 MMU 的映射关系,将其转换成物理内存地址。

作用:

  • 虚拟内存可以使得进程对运行内存超过物理内存大小:
    • 因为程序运行符合局部性原理,CPU 访问内存会有很明显的重复访问的倾向性
    • 对于那些没有被经常使用到的内存,我们可以把它换出到物理内存之外,比如硬盘上的 swap 区域。
  • 由于每个进程都有自己的页表,所以每个进程的虚拟内存空间就是相互独立的
  • 页表里的页表项中除了物理地址之外,还有对页的标志位

2️⃣ 操作系统是如何管理虚拟地址与物理地址之间的关系?

主要有两种方式,分别是 内存分段内存分页

3️⃣ 分段机制下,虚拟地址和物理地址是如何映射的?

分段机制下的虚拟地址由两部分组成,段选择因子段内偏移量

段选择因子 最重要的就是 段号,用作 段表 的索引。其中 段表 保存了 段的基地址 以及 段的界限地址。通过 段基地址 加上 段内偏移量 得到 物理内存地址

4️⃣ 分页机制下,虚拟地址和物理地址是如何映射的?

分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。 这样一个连续并且尺寸固定的内存空间,叫做页 Page

分页机制下的虚拟地址分为两部分,页号页内偏移页号 作为 页表 的索引,页表 包含物理页在物理内存的 基地址,这个 基地址 加上 页内偏移 就形成了 物理内存地址

缺点

在 32 位计算机系统下,虚拟地址空间大小共有 4GB。假设一个页的大小位 4KB,那么就需要 (4GB/4KB) 的页的数量 2^20,假设 每个页表项 用 4B 来存储,那么那么 4GB 空间的映射就需要有 4MB 的内存来存储页表。

但是每个进程的都有自己的虚拟地址空间:拥有自己的页表。这就造成了额外的内存开销

5️⃣ 多级页表机制下,虚拟地址和物理地址是如何映射的?

多级页表机制下的虚拟地址分为五部分,全局页目录项 PGD上层页目录项 PUD中间页目录项 PMD页表项页内偏移

多级页表虽然解决了空间上的问题,但是 虚拟地址物理地址 的转换带来了 时间开销

因此,提出了 TLB 的概念:在 CPU 芯片中,加入了一个专门存放程序最常访问的页表项的 Cache

6️⃣ 段页页表机制下,虚拟地址和物理地址是如何映射的?

段页式机制下的虚拟地址分为三部分,段号页号页内偏移

每一个程序一张 段表,每个段又建立一张 页表,段表中的地址是页表的起始地址,而页表中的地址则为某页的物理页号。

7️⃣ 内核态与用户态的区别

  • 进程在用户态,只能访问用户空间内存
  • 进入内核态后,才可以访问内核空间的内存

虽然每个进程都各自有 独立的虚拟内存,但是 每个虚拟内存中的内核地址,其实关联的都是 相同的物理内存

内存分配

1️⃣ malloc 是如何分配内存的?

malloc() 并不是 系统调用,而是 C 库里的函数,用于动态分配内存。

  • 通过 brk() 系统调用从 分配内存
  • 通过 mmap() 系统调用在文件映射区域分配内存

2️⃣ malloc() 会通过 brk() 分配内存?又是什么场景下通过 mmap() 分配内存?

malloc() 源码里定义了一个阈值:

  • 用户分配的内存小于 128 KB,则通过 brk() 申请内存
  • 用户分配的内存大于 128 KB,则通过 mmap() 申请内存

3️⃣ malloc() 分配的是物理内存吗?

malloc() 分配的是虚拟内存。

  • 如果分配后的虚拟内存没有被访问虚拟内存不会映射物理内存 的,这样就不会占用物理内存了。
  • 如果分配后的虚拟内存有被访问操作系统 通过 查找页表,发现 虚拟内存 对应的页没有在物理内存中,就会 触发缺页中断,然后操作系统会建立 虚拟内存物理内存 之间的映射关系。

4️⃣ malloc(1) 会分配多大的虚拟内存?

malloc() 在分配内存的时候,并不是按用户预期申请的字节数来分配内存空间大小,而是会 预分配更大的空间 作为内存池。

5️⃣ free 释放内存,会归还给操作系统吗?

  • malloc 通过 brk() 方式申请的内存的情况,free 释放内存后会给 内存池
  • malloc 通过 mmap() 方式申请的内存的情况,free 释放内存后就会归归还给 操作系统

6️⃣ 为什么不全部使用 mmap 来分配内存?

malloc 的智慧就在于 “看人下菜碟”

  • 小内存、频繁请求: 主要靠 brk 管理的堆区。速度快。
  • 大内存: 直接用 mmap 单独映射一块。这样避免了在堆区造成 外部碎片 ,释放时也能干净利落地立刻归还给系统,不拖累堆区。

7️⃣ free() 函数只传入一个内存地址,为什么能知道要释放多大的内存?

malloc 返回给 用户态的内存起始地址进程的堆空间起始地址 要更多。多出来的字节 表示 该内存块的信息

8️⃣ 内存分配的过程

应用程序通过 malloc 函数申请内存的时候,实际上申请的是 虚拟内存,此时并不会 分配物理内存

当程序 读写了这块虚拟内存CPU 就会去访问这个 虚拟内存。如果 虚拟内存 没有映射到 物理内存CPU 就会产生 缺页中断,此时,进程陷入到 内核态,由内核来对 缺页中断 进行处理,并且检查是否有 空闲内存

  • 如果有,就直接分配物理内存,并建立虚拟内存与物理内存之间的映射关系。
  • 如果没有,内核 就会开始进行 回收内存 的工作。

9️⃣ 回收内存方式

  • 后台内存回收: 唤醒 kswapd 内核线程来回收内存,这个回收内存的过程 异步 的,不会阻塞进程的执行
  • 直接内存回收: 后台异步回收 跟不上 进程内存申请 的速度,会阻塞进程的执行
  • OOM: 如果 直接内存回收 后,空闲的物理内存 仍然无法满足 此次物理内存的申请,就会执行 OOM

    根据算法选择一个占用物理内存较高的进程,然后将其杀死,以便释放内存资源,如果物理内存依然不足,OOM Killer 会继续杀死占用物理内存较高的进程,直到释放足够的内存位置。

1️⃣0️⃣ 申请的虚拟内存超过物理内存会怎么样?

  • 32 位操作系统,因为进程最大只能申请 3 GB 大小的虚拟内存,所以直接申请超过 3GB 的内存时,会直接失败。
  • 64 位操作系统,因为进程最大只能申请 128 TB 大小的虚拟内存,即使物理内存只有 4GB,申请超过 4GB 是可以的,因为申请的是 虚拟内存

程序申请的虚拟内存,如果没有被使用,它是不会占用物理空间的。当访问这块虚拟内存后,操作系统才会进行物理内存分配。

1️⃣1️⃣ 申请物理内存大小超过了空闲物理内存大小会怎么办?

  • 如果没有开启 Swap 机制,程序就会直接 OOM
  • 如果有开启 Swap 机制,程序可以正常运行;

1️⃣2️⃣ 什么是 Swap 机制?

系统的物理内存不够用 的时候,就需要将 物理内存 中的 一部分空间释放 出来,以供当前运行的程序使用。被释放的空间可能来自一些很长时间没有什么操作的程序,这些被释放的空间会被 临时保存到磁盘,等到那些程序要运行时,再从磁盘中恢复保存的数据到内存中。

另外,当 内存使用存在压力 的时候,会开始 触发内存回收 行为,会把这些不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘读入内存就可以了。