面试不懂 Linux 内存管理?我用 20 张图给你讲明白( 三 )


vm_area_struct是描述进程地址空间的基本管理单元,一个进程往往需要多个vm_area_struct来描述它的用户空间虚拟地址,需要使用「链表」和「红黑树」来组织各个vm_area_struct 。
链表用于需要遍历全部节点的时候用,而红黑树适用于在地址空间中定位特定内存区域 。内核为了内存区域上的各种不同操作都能获得高性能,所以同时使用了这两种数据结构 。
用户空间进程的地址管理模型:

面试不懂 Linux 内存管理?我用 20 张图给你讲明白

文章插图
 
wm_arem_struct
内核空间动态分配内存数据结构在内核空间章节我们提到过「动态内存映射区」,该区域由内核函数vmalloc来分配,特点是:线性空间连续,但是对应的物理地址空间不一定连续 。vmalloc 分配的线性地址所对应的物理页可能处于低端内存,也可能处于高端内存 。
vmalloc 分配的地址则限于vmalloc_start与vmalloc_end之间 。每一块vmalloc分配的内核虚拟内存都对应一个vm_struct结构体,不同的内核空间虚拟地址之间有4k大小的防越界空闲区间隔区 。
与用户空间的虚拟地址特性一样,这些虚拟地址与物理内存没有简单的映射关系,必须通过内核页表才可转换为物理地址或物理页,它们有可能尚未被映射,当发生缺页时才真正分配物理页面 。
面试不懂 Linux 内存管理?我用 20 张图给你讲明白

文章插图
 
动态内存映射
 
前面分析了 Linux 内存管理机制,下面深入学习物理内存管理和虚拟内存分配 。
通过前面的学习我们知道,程序可没这么好骗,任你内存管理把虚拟地址空间玩出花来,到最后还是要给程序实实在在的物理内存,不然程序就要罢工了 。
所以物理内存这么重要的资源一定要好好管理起来使用(物理内存,就是你实实在在的内存条),那么内核是如何管理物理内存的呢?
物理内存管理在Linux系统中通过分段和分页机制,把物理内存划分 4K 大小的内存页 Page(也称作页框Page Frame),物理内存的分配和回收都是基于内存页进行,把物理内存分页管理的好处大大的 。
假如系统请求小块内存,可以预先分配一页给它,避免了反复的申请和释放小块内存带来频繁的系统开销 。
假如系统需要大块内存,则可以用多页内存拼凑,而不必要求大块连续内存 。你看不管内存大小都能收放自如,分页机制多么完美的解决方案!
But,理想很丰满,现实很骨感 。如果就直接这样把内存分页使用,不再加额外的管理还是存在一些问题,下面我们来看下,系统在多次分配和释放物理页的时候会遇到哪些问题 。
物理页管理面临问题物理内存页分配会出现外部碎片和内部碎片问题,所谓的「内部」和「外部」是针对「页框内外」而言,一个页框内的内存碎片是内部碎片,多个页框间的碎片是外部碎片 。
外部碎片当需要分配大块内存的时候,要用好几页组合起来才够,而系统分配物理内存页的时候会尽量分配连续的内存页面,频繁的分配与回收物理页导致大量的小块内存夹杂在已分配页面中间,形成外部碎片,举个例子:
面试不懂 Linux 内存管理?我用 20 张图给你讲明白

文章插图
 
外部碎片
内部碎片物理内存是按页来分配的,这样当实际只需要很小内存的时候,也会分配至少是 4K 大小的页面,而内核中有很多需要以字节为单位分配内存的场景,这样本来只想要几个字节而已却不得不分配一页内存,除去用掉的字节剩下的就形成了内部碎片 。
面试不懂 Linux 内存管理?我用 20 张图给你讲明白

文章插图
 
内部碎片
页面管理算法方法总比困难多,因为存在上面的这些问题,聪明的程序员灵机一动,引入了页面管理算法来解决上述的碎片问题 。
Buddy(伙伴)分配算法Linux 内核引入了伙伴系统算法(Buddy system),什么意思呢?就是把相同大小的页框块用链表串起来,页框块就像手拉手的好伙伴,也是这个算法名字的由来 。
具体的,所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块 。最大可以申请1024个连续页框,对应4MB大小的连续内存 。
面试不懂 Linux 内存管理?我用 20 张图给你讲明白

文章插图
 
伙伴系统
因为任何正整数都可以由 2^n 的和组成,所以总能找到合适大小的内存块分配出去,减少了外部碎片产生。


推荐阅读