linux内存管理-页面规整

页面规整

背景

linux长时间运行后,可能会发生页面申请失败,原因有两种情况:

  1. 空闲页不够,触及到回收的阈值
  2. 申请order > 0的连续页时找不到连续的物理内存

对于第一种情况主要通过页面回收的方式来回收到足够的内存页,包括释放干净的文件映射页,回写脏页,匿名页到swap方式回收到可用的物理内存,但是第二种情况就比较复杂。
memory fragmentation
系统长时间运行后,不断的申请和释放页,最终可能是有可用的内存,但是缺少连续的物理内存,就是内存碎片,就和上图一样。虽然buddy以简单的方式能够很好的维护连续空闲页,但是它并对内存碎片问题并没有解决方式。而且内存碎片问题也无法彻底解决,目前有两种方式能够缓解这个问题:

  1. 将zone进一步分为几种类型:UNMOVABLE,RECLAIMABLE,MOVABLE等,通过在申请的时候表明申请页的使用倾向,通过在不同区间中申请页,防止长期使用的UNMOVABLE类型的页破坏空闲物理内存的连续性。
  2. 对页面进行规整,在申请内存失败的时候首先进行内存规整,主要是移动物理页的内容并更新映射关系,释放可回收的页,这样通过积极调整页的位置来使得物理内存连续。

页面规整的原理

reflink:https://lwn.net/Articles/368869/

初始的碎片
页面碎片如上图所示,白色代表空闲页,红色代表使用的页。现在虽然有8个page的空闲内存,但是无法申请连续的4个页面,而且申请连续的2个页面都不行,buddy管理的页面中他们都不符合合并的规则,只有8个order=0的页。

页面规整的整体算法:以zone为规整对象,其中每次扫描又以pageblock为单位进行扫描。它有两个指针:从zone头部开始的migrate和从zone尾部开始的free,首先从头部收集一批可移动的页,然后从尾部收集一批空闲页,将可移动页迁移到尾部的空闲页中,最后当migrage和free在中间相遇时,前半部分已经没有可移动的页,后半部分没有了空闲页,退出整个规整过程。

从头部扫描可移动页:
扫描可移动页
从尾部扫描空闲页:
尾部扫描空闲页
页面规整之后的结果:
页面规整后的内存布局
上面展示了非常理想的工作过程。但是并不是所有的页都可移动的,只有那些可以通过页表访问的页并且没有被lock的页才可以移动,这些页基本上都是应用进程使用的页,而内核的页大部分都是不可移动的,但是其中的一部分是可回收的,在需要的时候直接释放掉。往往一个不可移动的页就能破坏一大片连续内存的连续性,不过内核中的迁移类型机制最大限度的隔离了不可移动的页和可移动页的,内存规整的效果是依赖于迁移类型机制的。

页面规整主要有两种触发方式:

  1. 通过proc节点/proc/sys/vm/compact_node向用户空间提供了主动干预规整的接口
  2. 申请order >0的页失败时,这时候是首先尝试进行页面规整,如果还是不能提供满足连续页面申请的请求则进行直接页面回收。

页面规整的实现

页面规整有两种模式:异步和同步,在页面回收失败的时候首先开启异步的页面规整,如果异步页面规整不出满足要求的内存,接下来使用尝试通过直接内存回收方式回收到足够的内存,如果还是获取不到足够的连续内存,那么再次尝试通过同步页面规整的方式获取连续内存。

页面规整的对象类型有两种迁移类型:MIGRATE_RECLAIMABLE,MIGRATE_MOVABLE,从规整方式来看有两种:匿名映射和文件映射类型的页。

异步页面规整只规整匿名页,而同步页面规整还会处理文件脏页,如果页面正在回写,它还会等待页面完成回写。
异步页面规整相对来说比较保守,匿名页的页面迁移只需要将页的内容迁移到新页中,创建新的映射关系,解除旧的映射关系,全是内存的读写操作,而同步还涉及到IO回写,它的耗时更长。

外部碎片严重程度的计算
系统通过/sys/kernel/debug/extfrag/unusable_index导出了外部碎片的概率,从0-1,这个值越高代表有连续可用内存的概率越小,外部碎片问题越严重,当申请2^order页时越困难。
extfrag
统计0-order中空闲页的总数量和各个阶链表上空闲块的总数,当空闲页数量一定时,如果低阶的空闲页数量越多,则block值越高,则最后的碎片化也越严重;如果高阶的空闲页数量多,则block相对较小,碎片化整理起来难度相对更加容易

static void fill_contig_page_info(struct zone *zone,
                unsigned int suitable_order,
                struct contig_page_info *info)
{
    for (order = 0; order < MAX_ORDER; order++) {
        unsigned long blocks;

        /* 累计各个阶的空闲块数 */
        blocks = zone->free_area[order].nr_free;
        info->free_blocks_total += blocks;

        /* 空闲页的数量 */
        info->free_pages += blocks << order;

        /* 如果在这个阶上有空闲页,肯定是没有碎片的 */
        if (order >= suitable_order)
            info->free_blocks_suitable += blocks <<
                        (order - suitable_order);
    }                                                                                                                                          
}
static int __fragmentation_index(unsigned int order, struct contig_page_info *info)
{
    unsigned long requested = 1UL << order;
    //数值从0-1,保留两位小数,总共就是3位有效数字,转换成0-1000.
    //越靠近1000说明碎片化越严重,0标明缺乏空闲内存
    if (!info->free_blocks_total)
        return 0;
    /* Fragmentation index only makes sense when a request would fail */
    if (info->free_blocks_suitable)
        return -1000;
    return 1000 - div_u64( (1000+(div_u64(info->free_pages * 1000ULL, requested))), info->free_blocks_total);
}

/* Same as __fragmentation index but allocs contig_page_info on stack */
int fragmentation_index(struct zone *zone, unsigned int order)
{
    struct contig_page_info info;

    fill_contig_page_info(zone, order, &info);
    return __fragmentation_index(order, &info);                                                                                                
}

页面规整实现过程

  1. 页面迁移功能中将zone进一步分成pageblock,每个block代表连续的物理内存页,分配一片bitmap的pageblock_flags,每个block占据3个bit,表示当前的迁移类型
enum pageblock_bits {
    PB_migrate,                                                                                                                                
    PB_migrate_end = PB_migrate + 3 - 1,
            /* 3 bits required for migrate types */
    NR_PAGEBLOCK_BITS
};
  1. 隔离页
    开始从zone的头部开始扫描pageblock,如果是异步规整模式,则只隔离MIGRATE_MOVABLE中的匿名页,将它从LRU中隔离到migratepages中,一次最大COMPACT_CLUSTER_MAX个页,扫描完成后记录当前扫描的位置migrate_pfn,这样小块的页面迁移方便在回收失败的时候将隔离页重新返回到LRU链表中。
  2. 从zone的尾部找空闲页,需要满足同样隔离页的迁移性,异步页面规整时只扫描MIGRATE_MOVABLE的block,当free和migrate的指针相遇时或者获取到足够迁移当前隔离的页结束收集空闲页。扫描空闲页一次性扫描一个pageblock区间的页,它得到的空闲页数量可能是大于当前隔离页的数量的,这样这次批量迁移完成下次还有空闲页可用。最后扫描空闲页的时候,migrate_pfn和free_pfn可能相差
  3. 迁移页
    对于文件映射的脏页,如果不是同步模式页面规整,则不进行回写,如果该页面正在回写,也不等待回写完成。如果是同步模式则对于脏页需要回写,并且等待正在回写页的完成。
    迁移总共分三部分,中间比较复杂,涉及到反向映射的实现:
解除需要迁移页的映射关系,即pte
拷贝迁移页内容和page的状态到新的页中page中
建立新的映射关系,即设置新的pte
  1. 对于最终没有迁移的页或者空闲页,最终会恢复原状,migratepages再次加入到LRU链表中,freepage再次还回到buddy系统中
    compact2
    compact3
    compact3

页面规整的结束条件

  1. 满足连续物理内存的申请,不需要继续规整就已经满足本次的页面申请了
  2. 整个zone都已经扫描一遍,没有可以规整的页面了

对于重复进行页面规整的避免

zone中记录了页面规整的状态,compact_defer_shift定义了页面规整的阈值,compact_considered标记当前已经推迟了多少次的页面规整请求。
当页面规整后仍然无法满足页面申请的需求,对zone进行标记compact_defer_shift;如果接下来继续发起了页面规整,则不会再频繁的发起页面规整,它认为短时间内页面规整不会有太大的效果,这时候会记录推迟了多少次的规整请求compact_considered,当发起的请求超过阈值之后才会再次发起规整请求。
当页面规整满足了页面申请后会重置阈值和推迟请求次数。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页