什么是内存碎片
内存碎片分为内部碎片和外部碎片:内部碎片指内存页里面的碎片;外部碎片指空闲的内存也是分散的,很难找到一组物理页连续的空闲内存页,无法满足超过一页的内存分配请求;
内核有时需要分配超过一页的物理内存,因为内核使用线性映射区的虚拟地址,需要分配连续的物理页;使用巨型页,也需要分配连续的物理页;
为可决外部碎片问题,内核引入反碎片技术:
1)虚拟可移动区域;
2)内存碎片整理;
虚拟可移动区域是预防外部碎片的技术;内存碎片整理是在出现外部碎片以后消除外部碎片的技术;
虚拟可移动区域
可移动区域(ZONE_MOVABLE)是一个伪内存区域,基本思想是:把物理内存分为两个区域,一个区域用于分配不可移动的页,另一个区域用于分配可移动的页,防止不可移动页向可移动页引入碎片;
技术原理
可移动区域(ZONE_MOVABLE)没有包含任何物理内存,所以说它是伪内存区域,或者说是虚拟的内存区域;可移动区域借用最高内存区域的内存,在32位系统上最高的内存区域通常是高端内存区域(ZONE_HIGHMEM),在64位系统上最高的内存区域通常是普通区域(ZONE_NORMAL);
在分配物理页时,根据标志(__GFP_MOVABLE)从可移动区域分配物理页;
内存碎片整理
什么是内存碎片整理
内存碎片整理(memory compaction)的基本思想是:从内存区域的底部扫描已分配的可移动页,从内存区域的顶部扫描空闲页,把底部的可移动页移到顶部的空闲页,在底部形成连续的空闲页;
技术原理
内存碎片整理流程
假设有一个很小的碎片化的内存区域,包含16个页,如下图所示:
白色表示页是空闲的,这个内存区域已经碎片话;最大的连续空闲页是两页,从这个区域分配四页会失败,甚至分配两页也会失败,因为连续的两个空闲页的起始地址没有对齐到两页的整数倍(伙伴分配器的规则);
该碎片化内存区域内存碎片整理流程如下:
1)内存碎片整理算法从内存区域的底部向顶部扫描,把可以移动的已分配的页组成一条链表,这个扫描成为迁移扫描器,如下图所示:
2)内存碎片整理算法从内存区域的顶部向底部扫描,把空闲的页组成一条链表,称为空闲扫描器,如下图所示:
3)迁移扫描器和空闲扫描器在内存区域的中间相遇,把可以移动的已分配页移到顶部的空闲页,形成连续8个空闲页,可以满足申请连续8个页的需求,如下图所示:
在真实的系统中,内存区域大得多,内存碎片整理以内存区域为单位执行,在内存区域内部以分组页块为单位执行;
内存碎片整理的算法流程如下(与上述很小的碎片化的内存区域的内存碎片整理流程一致):
1)首先从内存区域的底部向顶部以页块为单位扫描,在页块内部从起始页向结束页扫描,把这个页块里面的可移动页组成一个链表;
2)然后从内存区域的顶部向底部以页块为单位扫描,在页块内部也是从起始页向结束页扫描,把空闲页组成一条链表;
3)最后把底部的可移动页的数据复制到顶部的空闲页,修改进程的页表,把虚拟页映射到新的物理页;
内存碎片整理优先级
1)COMPACT_PRIO_SYNC_FULL:完全同步模式,允许阻塞,允许把脏的文件页回写到存储设备上,并且等待回写完成;
2)COMPACT_PRIO_SYNC_LIGHT:轻量级同步模式,允许大多数操作阻塞,但是不允许把脏的文件页回写到存储设备上(因为可能需要等待很长的时间);
3)COMPACT_PRIO_ASYNC:异步模式,不允许阻塞;
完全同步模式的成本最高,轻量级同步模式的成本次之,异步模式的成本最低;
内存碎片整理结束条件
1)如果迁移扫描器和空闲扫描器相遇,那么内存碎片整理结束;
2)如果迁移扫描器和空闲扫描器没有相遇,但是申请或备用的迁移类型至少有一个足够大的空闲页块,那么内存碎片整理结束;
代码分析
函数__alloc_pages_nodemask
是页分配器的核心函数,函数__alloc_pages_slowpath
是页分配器得慢速路径;内存碎片整理在慢速路径中执行,代码如下: