内存管理
一. 为什么要有虚拟内存?
1. 为什么要有虚拟内存?
为应用程序开发提供统一的虚拟内存地址空间, 避免让应用程序处理直接操作物理内存可能存在的地址冲突问题。
虚拟地址: 程序使用的内存地址叫做虚拟内存地址 物理地址: 物理内存的地址叫做物理内存地址
单片机开发中程序是直接操作物理内存 linux中程序是操作虚拟内存
2. 虚拟内存工作的硬件依赖
虚拟地址通过cpu中的MMU转换为物理地址 MMU: 内存管理单元
3. 操作系统如何管理虚拟地址和物理地址之间的关系?
内存分段、内存分页
4. 什么是内存分段? (https://xiaolincoding.com/os/3_memory/vmem.html#%E5%86%85%E5%AD%98%E5%88%86%E6%AE%B5)
虚拟内存地址=段选择子+段内偏移,根据段选择子中的段号在段表项中找到对应段描述符(含有段基址和段界限),段内偏移加上段基址就是物理地址,同时能够判断段内偏移是否越界
优点: 不存在内部内存碎片,分配连续的内存空间 缺点: 存在外部内存碎片,内存交换效率低(比如段可能很大,一次换入/换出耗时较长)
5. 什么是内存分页? 什么是简单分页? 为什么要有多级页表?
分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小。 简单分页是指只用1级页表来管理虚拟地址和物理地址之间的映射关系。 多级页表是为了节省内存空间而设计的。因为简单分页需要为每个虚拟页分配一个物理页框,如果虚拟地址空间很大,可能会浪费大量内存。多级页表用比较小的1级页表来覆盖所有虚拟页,后面的n级(n>=2)页表只在需要的时候才分配,节省了内存空间。
关键词: 页表、页号、页内偏移、多级页表
6. TLB(Translation Lookaside Buffer) 作用是什么?
TLB 是一个硬件缓存,用于存储最近使用的虚拟地址到物理地址的映射,以加速地址转换过程。
7. 什么是段页式内存管理?
先分段再分页,虚拟地址=段号+页号+页内偏移
8. 为什么linux要把所有段的基地址设为0?
关键词: 逻辑地址、线性地址(虚拟地址)、物理地址、Intel处理器发展史
9. linux的虚拟地址空间是如何分布的?
每个程序的内存空间中,从高到低分别是: 内核地址、栈地址、文件映射地址、堆地址、BSS地址、数据段、代码段
每个程序虚拟内存中的内核地址,关联的都是相同的物理内存。
10. 虚拟内存有什么作用?
- 降低应用程序管理内存复杂性: 每个程序有独立的虚拟内存空间,避免开发者处理物理内存地址冲突问题
- 提供更大的内存地址空间: 支持进程使用大于物理内存的内存空间
- 提供更好的内存访问安全性: 比如通过页表项中的权限位来限制进程对内存的访问
11. 什么是内存交换?
内存交换是指把不常用的内存页换出到磁盘上,以释放内存空间。linux中使用swap分区来实现内存交换。
12. 什么是缺页中断? 缺页中断是内存访问的哪一步触发的?
在程序试图访问一个没有物理内存映射的虚拟内存页时,会触发缺页中断。缺页中断发生在虚拟地址转换为物理地址的过程中。 CPU尝试访问内存,MMU查询页表发现该虚拟页没有对应物理页映射,于是触发一个缺页中断(一个硬件中断),操作系统捕获到这个中断, 为这个中断处理程序分配物理页,并更新页表,使得虚拟页映射到新分配的物理页。之后,CPU可以继续执行原来的指令,访问到正确的物理内存。
二. malloc是如何分配内存的?
1. 内核空间和用户空间比例
32位系统中, 总空间4G, 内核空间1G, 用户空间3G 64位系统中, 内核空间和用户空间都是128T
2. malloc是系统调用吗? 底层是什么?
malloc是c库函数,底层调用brk()或mmap()系统调用来分配内存。
- brk(): 调整堆顶指针的位置,用于分配小块内存
- mmap(): 映射文件或匿名内存到进程的虚拟地址空间,用于分配大块内存
这里内存的大块小块的划分根据不同glibc的实现而不同, 一般阈值是128kB
3. malloc分配的是物理内存吗?
不,是虚拟内存
4. malloc(1)会分配多大内存?
不是1字节,而是预分配更大的空间做内存池待后面使用。
5. 如何查看进程的内存分配情况?
cat /proc/$pid/maps
查看进程的内存映射情况
6. free释放内存,会归还给操作系统吗?
使用brk()分配的内存,free后不会归还,而是缓存待下次使用; 使用mmap()分配的内存,free后会归还。
最终进程结束的时候,不管之前归没归还的资源都会回收
7. 为什么不全部使用mmap来分配内存?
使用brk()分配内存的内存会放到malloc的内存池中缓存使用,后续再分配的时候可能就不需要再走系统调用,减少系统调用次数。另外,之前分配的虚拟内存地址在页表中的物理地址映射可能还存在,复用之前分配的虚拟内存地址的话,还可能减少缺页中断重新映射物理地址的次数
8. 为什么不全部使用brk()分配内存?
brk分配的内存free的时候不会被释放而是被malloc的内存池管理等待复用,比较容易造成内存碎片,如果要分配大块内存的话很可能无法复用内存碎片的空间,而是需要继续增长brk分配的内存,所以对于大块的内存,使用mmap分配比较好,
9. free只传入一个内存地址,如何知道要释放多大内存?
把分配的内存块大小存放在了内存块地址前面的16字节中
10. malloc内存分配器是如何实现的?
https://mp.weixin.qq.com/s/Flt85kKbDEn_XD83mtYxUA?token=1646973705&lang=zh_CN
三. 内存满了,会发生什么?
1. 内核分配的过程是怎么样的?
malloc分配虚拟内存,虚拟内存实际访问的时候如果发现未映射到物理内存,则触发缺页中断,开始分配物理内存。 如果物理内存充足,则直接分配。如果物理内存不足,则唤醒后台内存回收线程kswapd进行后台内存回收(异步),如果回收速度跟不上进程分配内存的速度,则启动直接内存回收(同步), 如果还是没有足够物理内存,则启动OOM,OOM会持续杀死物理内存占用较高的进程,直到释放足够的内存。
2. 哪些内存可以回收?
文件页:
- 干净页: 直接回收
- 脏页: 先写回磁盘再回收 匿名页: 通过Swap机制回收
3. 回收内存会带来什么性能影响? 如何优化?
回收内存会造成一些磁盘I/O操作,降低系统性能,其中文件页回收比匿名页回收往往性能影响更大。
优化方法:
- 设置
/proc/sys/vm/swappiness
选项,调整文件页和匿名页的回收倾向 - 尽早触发kswapd线程开启异步回收:通过设置min_free_kbytes
4. SMP架构是什么?SMP架构有什么问题?
SMP: 对称多处理器架构。多CPU平等共享系统资源。也称为UMA(Uniform Memory Access)架构
缺点: 总线带宽压力随着核数增大。
5. NUMA架构如何解决SMP架构总线带宽压力过大的问题?
CPU分组,每组CPU用一个Node表示。
"每个 Node 有自己独立的资源,包括内存、IO 等"
当Node的内存不足的时候,有多种回收模式可以选择, 通过/proc/sys/vm/zone_reclaim_mode
控制:
- 0(默认): 先去其他Node找空闲内存
- 1: 只回收本地
- 2: 只回收本地, 可写回脏页
- 4:
注意: 不同的模式是可以组合使用的
6. 如何保护一个进程不被OOM杀掉?
设置/proc/[pid]/oom_score_adj
参数,
取值范围:-1000~1000,越小越不容易被杀掉
四. 4G物理内存上,申请8G内存会怎么样?
32位操作系统上: 8G操过了理论上用户态虚拟内存空间上限3G,会申请失败
64位操作系统上: 8G小于物理内存上限128T,可以申请成功。 但是访问虚拟内存的时候,根据是否开启swap机制会有不同结果:
- 不开启swap机制: 使用的时候,会因物理空间不足,触发OOM,导致进程被杀掉
- 开启swap机制: 使用的时候,可以通过swap来回收内存,该书中测试是可以正常访问;书中把分配的虚拟内存增大到64G的时候,使用过程中进程还是被OOM杀掉了,因为系统多次回收内存还是不能够获取足够的内存。
总结:
- 如果分配的虚拟内存空间操作系统的用户态虚拟内存空间上限(32位操作系统3G)会申请失败
- 分配成功后,使用时如果没有开启swap机制,会因为物理空间不足触发OOM,导致进程被杀掉
- 如果开启了swap机制,使用超过物理内存的虚拟内存空间时,会通过swap机制回收内存,如果回收能够获取足够的内存,那么就能够正常使用;如果多次回收内存还是不能够获取足够的内存,那么就会触发OOM,导致进程被杀掉
五. 如何避免预读失效和缓存污染的问题?
普通的LRU缓存淘汰算法存在两个问题: 预读失效: 预先读取的页后续没有被使用,却被缓存了且淘汰了热点页 缓存污染: 某些一次性读取的页面后续不再使用,却占用了缓存空间,导致热点页被淘汰
解决预读失效: 通过改进LRU算法,给予访问的页和预读的页不同的优先级。 linux的LRU实现使用两个链表:active list和inactive list,预读的页先加入inactive list头部,只有访问过的页才能加入active list中; MySQL Innodb使用一个链表,链表划分为两个部分young和old,预读的页先插入old头部,只有访问过的页才加入young中。
解决缓存污染: 提高优先级改变的阈值,避免仅仅因为访问一次就改变优先级。 linux的LRU实现中在预读内存页被访问第二次的时候,才从inactive list移到active list中; MySQL Innodb,不仅预读的内存页要访问到第二次,还要判断两次时间间隔是否超过1s, 超过了才会从old移到young中。
什么是预读? 利用空间局部性原理,为了提高性能,操作系统在缓存某个要访问的页的时候,会预先读取并缓存后续的若干页。
六. 深入理解Linux虚拟内存管理
1. 什么是虚拟内存地址?
一个用于关联到物理地址的数据。
32位虚拟内存地址=页目录项(10)+页表项(10)+页内偏移(12)
每个虚拟内存地址用于定位一个虚拟内存空间中一个特定的字节。
2. 为什么要使用虚拟内存地址访问内存?
- 提供进程隔离: 每个进程都有独立的虚拟内存空间,避免了不同进程直接使用物理地址需要处理的地址冲突的问题。
- 统一开发者视图,简化开发难度: 每个进程都有独立的虚拟内存空间,避免了开发者需要处理物理内存地址冲突的问题,而且使用连续的大块的虚拟内存空间的时候,不需要考虑底层的物理内存空间是否是连续的问题。把内存管理的复杂性交给操作系统的内存管理模块处理。
- 支持内存超售: 虚拟内存允许系统分配比物理内存更多的内存
- 提高安全性: 通过页表项中的权限位来限制进程对内存的访问
3. 进程虚拟内存空间布局
- https://xiaolincoding.com/os/3_memory/linux_mem.html#_3-%E8%BF%9B%E7%A8%8B%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98%E7%A9%BA%E9%97%B4