操作系统常问问题
虚拟内存
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 机制?
系统的物理内存不够用 的时候,就需要将 物理内存 中的 一部分空间释放 出来,以供当前运行的程序使用。被释放的空间可能来自一些很长时间没有什么操作的程序,这些被释放的空间会被 临时保存到磁盘,等到那些程序要运行时,再从磁盘中恢复保存的数据到内存中。
另外,当 内存使用存在压力 的时候,会开始 触发内存回收 行为,会把这些不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘读入内存就可以了。
