内存管理
最近在测试MySQL的性能时,遇到了有趣的问题
docker stats里面明明显示MySQL已经占用了16G的内存,但是top显示仅仅占用了1G的内存
最后找到了问题的原因:Buffer Pool的内存被分配在了Cache/Buff中,top显示的内存并不包括这一区域
使用free -h可以看到Cache/Buff的使用 于是就想着来梳理一下计算机的内存体系
1. 虚拟内存
1.1 介绍
对于单片机这类没有操作系统的硬件来说,运行的程序是直接跑在内存的物理地址上的,也就是说同时运行两个程序是不可能的,因为两个程序的地址很有可能冲突,进而导致相互覆盖
为此,操作系统引入了虚拟内存,来为程序屏蔽掉物理内存,将不同进程的虚拟地址通过CPU的内存管理单元(MMU)映射到内存的物理地址,主要包括内存分段和内存分页两种方式。
1.2 内存分段
程序由代码分段、数据分段、栈段、堆段组成
分段机制下,虚拟地址由段选择因子和段内偏移量两部分组成:
- 段选择因子位于段寄存器中,存储了段号等信息,段号用作段表的索引。段表保存了整个段的基地址、界限和特权等级等信息
- 段偏移量用于物理内存地址相对段基地址的偏移量,
物理地址 = 段基地址 + 段内偏移量
注意
经过映射后,虚拟段在物理地址上的顺序和间隔可能会改变
分段由于是按照需求去分配的空间,所以不会产生内部碎片,但缺点是容易产生外部碎片,比如1000MB连续分配了程序:
- 500MB QQ
- 125MB 微信
- 250MB 浏览器
当微信关闭之后,就会产生两段不连续的内存,导致无法分配到125MB < 内存 < 250MB
的内存
为了解决这个问题,操作系统还有内存交换机制,即首先将两段空闲内存间的内存(音乐)存储到磁盘中,然后再重新接着之前的内存(QQ)分配内存。这块磁盘中的空间也就是Swap空间。当然,如果需要Swap的内存过大,就会导致服务器性能下降
1.3 内存分页
内存分页提升了大内存的内存交换效率。分页把虚拟和物理内存分成固定尺寸的页,Linux中每页的大小为4KB。和分段类似,分页的每一个虚拟页也通过页表映射到物理地址。
当进程的虚拟地址在页表中找不到时,系统会产生一个缺页异常,进入系统内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复运行
分页虽然不会带来外部碎片问题,但由于页的尺寸是固定的4KB,所以会产生内部碎片。当内存不足时,操作系统会将最近未被使用的页面写入磁盘,称之为换出(Swap Out)。等到需要的时候再加载进来,即换入(Swap In)。
分页机制下,虚拟地址分为两部分:
- 页号:页表的索引,页表包含页所在物理地址的基地址
- 页内偏移:表示当前地址与物理基地址的偏移量,
物理地址 = 页基地址 + 页内偏移量
在32位操作系统中,每个页的大小是4KB(2 ^12),如果内存是4GB的话,那么就需要约100万(2 ^20)个页,每个页表项需要4B的空间存储,所以共需要4MB的内存来存储页表。此时,如果启动了100个进程就需要400MB的内存,这将对系统造成较大的资源消耗。
1.4 多级页表
多级页表的出现解决了上述的问题。
二级分页技术对页表进行了再次分页。这样不需要使用的二级页表就不会被创建,当一级页表的某项被使用时再创建对应的二级页表。
在64位系统上,二级分页又变成了四级目录,分别是:
- 全剧页目录项PGD(Page Global Directory)
- 上层页目录项PUD(Page Upper Directory)
- 中间页目录项PMD(Page Middle Directory)
- 页表项PTE(Page Table Entry)
1.5 TLB
多级页表虽然解决了空间的问题,但虚拟地址的转换也多了转换的步骤。由于程序的局部性原理,某段时间内执行的程序都局限于某个内存区域。
因此,可利用该特性,将最常访问的几个页表项存储到访问速度最快的硬件,即CPU中的TLB(Translation Lookaside Buffer),称之为页表缓存、转址旁路缓存、快表等。CPU在寻址时,会通过MMU首先查找TLB,如果不存在则继续查找页表。