跳至主要內容

内存管理

pptg2024年9月12日大约 5 分钟

最近在测试MySQL的性能时,遇到了有趣的问题

docker stats里面明明显示MySQL已经占用了16G的内存,但是top显示仅仅占用了1G的内存

最后找到了问题的原因:Buffer Pool的内存被分配在了Cache/Buff中,top显示的内存并不包括这一区域

使用free -h可以看到Cache/Buff的使用 于是就想着来梳理一下计算机的内存体系

1. 虚拟内存

1.1 介绍

对于单片机这类没有操作系统的硬件来说,运行的程序是直接跑在内存的物理地址上的,也就是说同时运行两个程序是不可能的,因为两个程序的地址很有可能冲突,进而导致相互覆盖

为此,操作系统引入了虚拟内存,来为程序屏蔽掉物理内存,将不同进程的虚拟地址通过CPU的内存管理单元(MMU)映射到内存的物理地址,主要包括内存分段内存分页两种方式。

1.2 内存分段

程序由代码分段、数据分段、栈段、堆段组成

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

内存分段
内存分段

注意

经过映射后,虚拟段在物理地址上的顺序和间隔可能会改变

分段由于是按照需求去分配的空间,所以不会产生内部碎片,但缺点是容易产生外部碎片,比如1000MB连续分配了程序:

内存碎片
内存碎片

当微信关闭之后,就会产生两段不连续的内存,导致无法分配到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位系统上,二级分页又变成了四级目录,分别是:

1.5 TLB

多级页表虽然解决了空间的问题,但虚拟地址的转换也多了转换的步骤。由于程序的局部性原理,某段时间内执行的程序都局限于某个内存区域。

因此,可利用该特性,将最常访问的几个页表项存储到访问速度最快的硬件,即CPU中的TLB(Translation Lookaside Buffer),称之为页表缓存、转址旁路缓存、快表等。CPU在寻址时,会通过MMU首先查找TLB,如果不存在则继续查找页表。

1.6 段页式内存管理