Zoned Allocator -4- (Direct Compact)

 

Compaction 판단

zone_balanced()

mm/vmscan.c

static bool zone_balanced(struct zone *zone, int order,
                          unsigned long balance_gap, int classzone_idx)
{
        if (!zone_watermark_ok_safe(zone, order, high_wmark_pages(zone) +
                                    balance_gap, classzone_idx, 0))
                return false;

        if (IS_ENABLED(CONFIG_COMPACTION) && order && compaction_suitable(zone,
                                order, 0, classzone_idx) == COMPACT_SKIPPED)
                return false;

        return true;
}

zone의 free page가 high 워터마크를 초과하였거나 compaction 필요성이 없는 경우 페이지 회수가 필요한 zone이 아니므로 처리를 하지 않고 빠져나간다.

  • if (!zone_watermark_ok_safe(zone, order, high_wmark_pages(zone) + balance_gap, classzone_idx, 0)) return false;
    • 요청 zone에서 order 페이지를 high 워터마크 + 밸런스 gap의 영역으로 처리할 수 없으면 false로 함수를 빠져나간다.
  • if (IS_ENABLED(CONFIG_COMPACTION) && order && compaction_suitable(zone, order, 0, classzone_idx) == COMPACT_SKIPPED) return false;
    • 요청 zone에서 과 order 페이지를 compaction 할 필요 없는 상태인 경우 false로 함수를 빠져나간다.

 

compaction_suitable()

mm/compaction.c

unsigned long compaction_suitable(struct zone *zone, int order,
                                        int alloc_flags, int classzone_idx)
{
        unsigned long ret;

        ret = __compaction_suitable(zone, order, alloc_flags, classzone_idx);
        trace_mm_compaction_suitable(zone, order, ret);
        if (ret == COMPACT_NOT_SUITABLE_ZONE)
                ret = COMPACT_SKIPPED;

        return ret;
}

요청 zone과 order를 사용하여 compaction을 진행할지 여부에 대한 상태를 다음과 같이 알아온다.

  • COMPACT_SKIPPED
    • compaction 하지 않도록 한다.
  • COMPACT_PARTIAL
    • compaction 없이 페이지 할당이 거의 성공될 것이다.
  • COMPACT_CONTINUE
    • compaction이 진행중이거나 지금 하는 것이 좋다.

 

__compaction_suitable()

mm/compaction.c

/*
 * compaction_suitable: Is this suitable to run compaction on this zone now?
 * Returns
 *   COMPACT_SKIPPED  - If there are too few free pages for compaction
 *   COMPACT_PARTIAL  - If the allocation would succeed without compaction
 *   COMPACT_CONTINUE - If compaction should run now
 */
static unsigned long __compaction_suitable(struct zone *zone, int order,
                                        int alloc_flags, int classzone_idx)
{
        int fragindex;
        unsigned long watermark;

        /*
         * order == -1 is expected when compacting via
         * /proc/sys/vm/compact_memory
         */
        if (order == -1)
                return COMPACT_CONTINUE;

        watermark = low_wmark_pages(zone);
        /*
         * If watermarks for high-order allocation are already met, there
         * should be no need for compaction at all.
         */
        if (zone_watermark_ok(zone, order, watermark, classzone_idx,
                                                                alloc_flags))
                return COMPACT_PARTIAL;

        /*
         * Watermarks for order-0 must be met for compaction. Note the 2UL.
         * This is because during migration, copies of pages need to be
         * allocated and for a short time, the footprint is higher
         */
        watermark += (2UL << order);
        if (!zone_watermark_ok(zone, 0, watermark, classzone_idx, alloc_flags))
                return COMPACT_SKIPPED;

        /*
         * fragmentation index determines if allocation failures are due to
         * low memory or external fragmentation
         *
         * index of -1000 would imply allocations might succeed depending on
         * watermarks, but we already failed the high-order watermark check
         * index towards 0 implies failure is due to lack of memory
         * index towards 1000 implies failure is due to fragmentation
         *
         * Only compact if a failure would be due to fragmentation.
         */
        fragindex = fragmentation_index(zone, order);
        if (fragindex >= 0 && fragindex <= sysctl_extfrag_threshold)
                return COMPACT_NOT_SUITABLE_ZONE;

        return COMPACT_CONTINUE;
}

요청 zone과 order를 사용하여  compaction을 진행할지 여부에 대한 상태를 다음과 같이 알아온다.

 

  • if (order == -1) return COMPACT_CONTINUE;
    • /proc/sys/vm/compact_memory 에 1이 기록된 경우 모든 zone이 compact가 진행되고 있는 중이다. 이러한 경우 COMPACT_CONTINUE 값으로 리턴한다.
  • watermark = low_wmark_pages(zone);
    • 지정된 zone의 low 워터마크 값을 알아온다.
  • if (zone_watermark_ok(zone, order, watermark, classzone_idx, alloc_flags)) return COMPACT_PARTIAL;
    • 지정된 zone의 free page가 low 워터마크를 초과한 경우 COMPACT_PARTIAL 값으로 리턴한다.
  • watermark += (2UL << order); if (!zone_watermark_ok(zone, 0, watermark, classzone_idx, alloc_flags)) return COMPACT_SKIPPED;
    • 워터마크 값에 요청 order 페이지 수의 두 배를 더한 워터마크 값으로 order 0에서의 워터마크 기준을 넘기지 못한 경우 compaction을 하지 못하게 COMPACT_SKIPPED 값으로 리턴한다.
      • 두 배를 하는 이유는 compaction을 진행하는 잠시 동안 페이지들을 복사하여 할당을 하므로 그 할당 용량만큼이 더 필요한다.
  • fragindex = fragmentation_index(zone, order);
    • compaction을 해야할지 여부를 판단하기 위해 요청 zone과 order에 대한 파편화 계수를 알아온다.
  • if (fragindex >= 0 && fragindex <= sysctl_extfrag_threshold) return COMPACT_NOT_SUITABLE_ZONE;
    • 파편화 계수가 0 ~ 전역 sysctl_extfrag_threshold 값 범위이내인 경우 compaction을 하지 않도록 한다.

 

다음 그림은 compaction을 수행해도 되는지 상태별 결과를 보여준다.

compaction_suitable-1b

 

fragmentation_index()

mm/vmstat.c

/* 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);
}

compaction을 해야할지 여부를 판단하기 위해 요청 zone과 order에 대한 파편화 계수를 알아온다.

  • fill_contig_page_info(zone, order, &info);
    • 지정된 zone의 버디 시스템에서 전체 free 블럭, 전체 free page 및 order 페이지의 할당 가능한 free 블럭 수 정보를 info에 담아온다.
  • return __fragmentation_index(order, &info);
    • 요청 order와 contig_page 정보를 사용하여 파편화 계수를 계산해온다.

 

fill_contig_page_info()

mm/vmstat.c

/*
 * Calculate the number of free pages in a zone, how many contiguous
 * pages are free and how many are large enough to satisfy an allocation of
 * the target size. Note that this function makes no attempt to estimate
 * how many suitable free blocks there *might* be if MOVABLE pages were
 * migrated. Calculating that is possible, but expensive and can be
 * figured out from userspace
 */
static void fill_contig_page_info(struct zone *zone,
                                unsigned int suitable_order,
                                struct contig_page_info *info)
{
        unsigned int order;

        info->free_pages = 0;
        info->free_blocks_total = 0;
        info->free_blocks_suitable = 0;

        for (order = 0; order < MAX_ORDER; order++) {
                unsigned long blocks;

                /* Count number of free blocks */
                blocks = zone->free_area[order].nr_free;
                info->free_blocks_total += blocks;

                /* Count free base pages */
                info->free_pages += blocks << order;

                /* Count the suitable free blocks */
                if (order >= suitable_order)
                        info->free_blocks_suitable += blocks <<
                                                (order - suitable_order);
        }
}

지정된 zone의 버디 시스템에서 전체 free 블럭, 전체 free page 및 suitable_order의 할당 가능한 free 블럭 수 정보를 info에 contig_page_info 구조체로 반환한다.

  • blocks = zone->free_area[order].nr_free; info->free_blocks_total += blocks;
    • info->free_blocks_total에 free 블럭 수
  • info->free_pages += blocks << order;
    • info->free_pages에 free 페이지 수
  • if (order >= suitable_order) info->free_blocks_suitable += blocks << (order – suitable_order);
    • info->free_blocks_suitable에  suitable_order의 할당 가능한 free 블럭 수

 

contig_page_info 구조체

mm/vmstat.c

#ifdef CONFIG_COMPACTION
struct contig_page_info {
        unsigned long free_pages;
        unsigned long free_blocks_total;
        unsigned long free_blocks_suitable;
};
#endif

 

__fragmentation_index()

mm/vmstat.c

/*
 * A fragmentation index only makes sense if an allocation of a requested
 * size would fail. If that is true, the fragmentation index indicates
 * whether external fragmentation or a lack of memory was the problem.
 * The value can be used to determine if page reclaim or compaction
 * should be used
 */
static int __fragmentation_index(unsigned int order, struct contig_page_info *info)
{
        unsigned long requested = 1UL << order;

        if (!info->free_blocks_total)
                return 0;

        /* Fragmentation index only makes sense when a request would fail */
        if (info->free_blocks_suitable)
                return -1000;

        /*
         * Index is between 0 and 1 so return within 3 decimal places
         *
         * 0 => allocation would fail due to lack of memory
         * 1 => allocation would fail due to fragmentation
         */
        return 1000 - div_u64( (1000+(div_u64(info->free_pages * 1000ULL, requested))), info->free_blocks_total);
}

요청 order와 free 페이지 및 free 블럭 정보를 사용하여 파편화 계수를 반환한다.

  • 0에 가까운 값은 메모리 부족으로 인해 할당이 실패될 상황이다.
    • 이후에 compaction 해도 할당 실패될 가능성 높은 상태
  • 1000에 가까운 값은 파편화로 인해 할당이 실패될 상황이다.
    • compaction 하면 할당 성공할 가능성 높은 상태
  • 음수 값은 요청 order 블럭이 존재하여 할당이 가능한 상태이다.
    • compaction이 필요하지 않다

 

  • if (!info->free_blocks_total) return 0;
    • 전체 free block 수가 0인 경우 compaction을 할 수 없어 0을 반환한다.
  • if (info->free_blocks_suitable) return -1000;
    • 요청 order 페이지를 처리할 수 있는 free block이 있는 경우 compaction이 필요 없으므로 -1000을 반환한다.
  • return 1000 – div_u64( (1000+(div_u64(info->free_pages * 1000ULL, requested))), info->free_blocks_total);
    • 1000 – (전체 free page x 1000 / 필요 page 수 + 1000) / 전체 free block 수
    • 0에 가까울 수록 메모리 부족으로 compaction을 허용하지 않는것이 좋다.
    • 1000에 가까울 수록 파편화된 페이지에 대해 compaction하는 것이 좋다.

 

다음 그림은 파편화 계수의 값에 따른 상태를 보여준다.

fragmentation_index-1

 

include/linux/compaction.h

/* Return values for compact_zone() and try_to_compact_pages() */
/* compaction didn't start as it was deferred due to past failures */
#define COMPACT_DEFERRED        0
/* compaction didn't start as it was not possible or direct reclaim was more suitable */
#define COMPACT_SKIPPED         1
/* compaction should continue to another pageblock */
#define COMPACT_CONTINUE        2
/* direct compaction partially compacted a zone and there are suitable pages */
#define COMPACT_PARTIAL         3
/* The full zone was compacted */
#define COMPACT_COMPLETE        4
/* For more detailed tracepoint output */
#define COMPACT_NO_SUITABLE_PAGE        5
#define COMPACT_NOT_SUITABLE_ZONE       6
/* When adding new state, please change compaction_status_string, too */

compaction 상태

  • COMPACT_DEFERRED
    • 실패 후 지연되어 compaction을 시작하지 못하고 있는 상태
  • COMPACT_SKIPPED
    • compaction 수행이 여의치 않은 상태
  • COMPACT_CONTINUE
    • compaction이 계속 진행되어야 하는 상태
  • COMPACT_PARTIAL
    • compaction이 부분적으로 완료되어 페이지를 할당할 수 있는 상태
  • COMPACT_COMPLETE
    • compaction이 완료된 상태

기타 내부용 상태

  • COMPACT_NO_SUITABLE_PAGE
  • COMPACT_NOT_SUITABLE_ZONE

 

include/linux/compaction.h

/* Used to signal whether compaction detected need_sched() or lock contention */
/* No contention detected */
#define COMPACT_CONTENDED_NONE  0
/* Either need_sched() was true or fatal signal pending */
#define COMPACT_CONTENDED_SCHED 1
/* Zone lock or lru_lock was contended in async compaction */
#define COMPACT_CONTENDED_LOCK  2

compaction 진행 중 혼잡 상태

  • COMPACT_CONTENDED
    • 혼잡하지 않은 상태
  • COMPACT_CONTENDED_SCHED
    • 리스케쥴 요청 또는 SIGKILL이 펜딩된 경우
  • COMPACT_CONTENDED_LOCK
    • 비동기에서 경쟁으로 인해 lock이 혼잡한 상태

 

include/linux/migrate_mode.h

/*
 * MIGRATE_ASYNC means never block
 * MIGRATE_SYNC_LIGHT in the current implementation means to allow blocking
 *      on most operations but not ->writepage as the potential stall time
 *      is too significant
 * MIGRATE_SYNC will block when migrating pages
 */
enum migrate_mode {
        MIGRATE_ASYNC,
        MIGRATE_SYNC_LIGHT,
        MIGRATE_SYNC,
};

migration 모드

  • MIGRATE_ASYNC
    • 비동기 migration 모드로 동작
  • MIGRATE_SYNC_LIGHT
    • writepage를 제외한 대부분을 동기 migration 모드로 동작
  • MIGRATE_SYNC
    • 동기 migration 모드로 동작

 

Compaction 수행

다음 그림은 direct compaction이 수행될 때의 함수 흐름을 보여준다.

__alloc_pages_direct_compact-2

 

__alloc_pages_direct_compact()

mm/page_alloc.c

#ifdef CONFIG_COMPACTION
/* Try memory compaction for high-order allocations before reclaim */
static struct page *
__alloc_pages_direct_compact(gfp_t gfp_mask, unsigned int order,
                int alloc_flags, const struct alloc_context *ac,
                enum migrate_mode mode, int *contended_compaction,
                bool *deferred_compaction)
{
        unsigned long compact_result;
        struct page *page;

        if (!order)
                return NULL;

        current->flags |= PF_MEMALLOC;
        compact_result = try_to_compact_pages(gfp_mask, order, alloc_flags, ac,
                                                mode, contended_compaction);
        current->flags &= ~PF_MEMALLOC;

        switch (compact_result) {
        case COMPACT_DEFERRED:
                *deferred_compaction = true;
                /* fall-through */
        case COMPACT_SKIPPED:
                return NULL;
        default:
                break;
        }

        /*
         * At least in one zone compaction wasn't deferred or skipped, so let's
         * count a compaction stall
         */
        count_vm_event(COMPACTSTALL);

        page = get_page_from_freelist(gfp_mask, order,
                                        alloc_flags & ~ALLOC_NO_WATERMARKS, ac);

        if (page) {
                struct zone *zone = page_zone(page);

                zone->compact_blockskip_flush = false;
                compaction_defer_reset(zone, order, true);
                count_vm_event(COMPACTSUCCESS);
                return page;
        }

        /*
         * It's bad if compaction run occurs and fails. The most likely reason
         * is that pages exist, but not enough to satisfy watermarks.
         */
        count_vm_event(COMPACTFAIL);

        cond_resched();

        return NULL;
}
#endif

compaction을 한 후 페이지 할당을 시도한다.

  • if (!order) return NULL;
    • order가 0인 경우 compaction으로 해결될 수 없으므로 처리하지 않는다.
  • current->flags |= PF_MEMALLOC;
    • 현재 태스크의 플래그 상태에 PF_MEMALLOC를 추가한다.
      • PF_MEMALLOC
        • 태스크의 플래그 상태에 페이지 compaction 또는 페이지 회수를 하는 동안에 설정되고 완료된 후 제거한다.
  • compact_result = try_to_compact_pages(gfp_mask, order, alloc_flags, ac, mode, contended_compaction);
    • 요청 order를 위해 compaction을 시도하고 compact 진행 상태를 반환하고 contended_compaction에 compaction 혼잡 상태를 알아온다.
  • current->flags &= ~PF_MEMALLOC;
    • 현재 태스크의 플래그 상태에 추가해 두었던 PF_MEMALLOC를 제거한다.
  • switch (compact_result) { case COMPACT_DEFERRED: *deferred_compaction = true;
    • compaction 결과가 COMPACT_DEFERRED인 경우 deferred_compaction에 true를 설정하고 처리를 중단하고 함수를 빠져나간다.
  • case COMPACT_SKIPPED: return NULL; default: break; }
    • compaction 결과가 COMPACT_SKIPPED인 경우 처리를 중단하고 함수를 빠져나간다. 그 외의 결과는 페이지 할당을 하기 위해 계속 진행한다.
  • count_vm_event(COMPACTSTALL);
    • COMPACTSTALL stat을 증가시킨다.
  • page = get_page_from_freelist(gfp_mask, order, alloc_flags & ~ALLOC_NO_WATERMARKS, ac);
    • ALLOC_NO_WATERMARKS를 제거한채 페이지 할당을 시도한다.
  • if (page) { zone->compact_blockskip_flush = false; compaction_defer_reset(zone, order, true); count_vm_event(COMPACTSUCCESS); return page; }
    • 페이지 할당이 성공한 경우 zone의 compact_blockskip_flush에 false를 대입하고 compaction에 대한 트래킹 카운터들을 리셋하며 COMPACTSUCCESS stat을 증가시키고 빠져나간다.
  • count_vm_event(COMPACTFAIL); cond_resched(); return NULL;
    • 페이지 할당이 실패한 경우 COMPACTFAIL stat을 증가시키고 리스케쥴 필요한 경우 sleep하고 함수를 빠져나간다.

 

다음 그림은 direct compaction의 함수별 진행 흐름을 보여준다.

__alloc_pages_direct_compact-1

 

try_to_compact_pages()

mm/compaction.c

/**
 * try_to_compact_pages - Direct compact to satisfy a high-order allocation
 * @gfp_mask: The GFP mask of the current allocation
 * @order: The order of the current allocation
 * @alloc_flags: The allocation flags of the current allocation
 * @ac: The context of current allocation
 * @mode: The migration mode for async, sync light, or sync migration
 * @contended: Return value that determines if compaction was aborted due to
 *             need_resched() or lock contention
 *
 * This is the main entry point for direct page compaction.
 */
unsigned long try_to_compact_pages(gfp_t gfp_mask, unsigned int order,
                        int alloc_flags, const struct alloc_context *ac,
                        enum migrate_mode mode, int *contended)
{
        int may_enter_fs = gfp_mask & __GFP_FS;
        int may_perform_io = gfp_mask & __GFP_IO;
        struct zoneref *z;
        struct zone *zone;
        int rc = COMPACT_DEFERRED;
        int all_zones_contended = COMPACT_CONTENDED_LOCK; /* init for &= op */

        *contended = COMPACT_CONTENDED_NONE;

        /* Check if the GFP flags allow compaction */
        if (!order || !may_enter_fs || !may_perform_io)
                return COMPACT_SKIPPED;

        trace_mm_compaction_try_to_compact_pages(order, gfp_mask, mode);

        /* Compact each zone in the list */
        for_each_zone_zonelist_nodemask(zone, z, ac->zonelist, ac->high_zoneidx,
                                                                ac->nodemask) {
                int status;
                int zone_contended;

                if (compaction_deferred(zone, order))
                        continue;

                status = compact_zone_order(zone, order, gfp_mask, mode,
                                &zone_contended, alloc_flags,
                                ac->classzone_idx);
                rc = max(status, rc);
                /*
                 * It takes at least one zone that wasn't lock contended
                 * to clear all_zones_contended.
                 */
                all_zones_contended &= zone_contended;

요청 order를 위해 compaction을 시도하고 compact 진행 상태를 반환하고 출력 인수로 혼잡 상태를 알아온다.

  • if (!order || !may_enter_fs || !may_perform_io) return COMPACT_SKIPPED;
    • order가 0이거나 __GFP_FS 또는 __GFP_IO 마스크가 없는 경우에는 compaction을 할 수 없다.
      • compaction은 order 1이상이면서 GFP_IOFS 마스크를 사용하여야 한다.
        • 참고로 GFP_USER 또는 GFP_KERNEL에서 GFP_IOFS를 사용한다.
  • for_each_zone_zonelist_nodemask(zone, z, ac->zonelist, ac->high_zoneidx, ac->nodemask) {
    • zonelist에서 지정된 nodemask와 high_zoneidx 이하의 zone에 대해 루프를 돈다.
  • if (compaction_deferred(zone, order)) continue;
    • 해당 zone에서 compaction이 지연되는 경우 skip한다.
  • status = compact_zone_order(zone, order, gfp_mask, mode, &zone_contended, alloc_flags, ac->classzone_idx);
    • 요청된 compact 모드로 compact 진행 후 진행 상태와 출력 인수 zone_contended에 zone의 혼잡 상태를  알아온다.
  • rc = max(status, rc);
    • 가장 높은 status 값을 보관하게 한다.
  • all_zones_contended &= zone_contended;
    • 하나의 zone이라도 혼잡하지 않은지 체크한다.

 

                /* If a normal allocation would succeed, stop compacting */
                if (zone_watermark_ok(zone, order, low_wmark_pages(zone),
                                        ac->classzone_idx, alloc_flags)) {
                        /*
                         * We think the allocation will succeed in this zone,
                         * but it is not certain, hence the false. The caller
                         * will repeat this with true if allocation indeed
                         * succeeds in this zone.
                         */
                        compaction_defer_reset(zone, order, false);
                        /*
                         * It is possible that async compaction aborted due to
                         * need_resched() and the watermarks were ok thanks to
                         * somebody else freeing memory. The allocation can
                         * however still fail so we better signal the
                         * need_resched() contention anyway (this will not
                         * prevent the allocation attempt).
                         */
                        if (zone_contended == COMPACT_CONTENDED_SCHED)
                                *contended = COMPACT_CONTENDED_SCHED;

                        goto break_loop;
                }

                if (mode != MIGRATE_ASYNC && status == COMPACT_COMPLETE) {
                        /*
                         * We think that allocation won't succeed in this zone
                         * so we defer compaction there. If it ends up
                         * succeeding after all, it will be reset.
                         */
                        defer_compaction(zone, order);
                }

                /*
                 * We might have stopped compacting due to need_resched() in
                 * async compaction, or due to a fatal signal detected. In that
                 * case do not try further zones and signal need_resched()
                 * contention.
                 */
                if ((zone_contended == COMPACT_CONTENDED_SCHED)
                                        || fatal_signal_pending(current)) {
                        *contended = COMPACT_CONTENDED_SCHED;
                        goto break_loop;
                }

                continue;
break_loop:
                /*
                 * We might not have tried all the zones, so  be conservative
                 * and assume they are not all lock contended.
                 */
                all_zones_contended = 0;
                break;
        }

        /*
         * If at least one zone wasn't deferred or skipped, we report if all
         * zones that were tried were lock contended.
         */
        if (rc > COMPACT_SKIPPED && all_zones_contended)
                *contended = COMPACT_CONTENDED_LOCK;

        return rc;
}
  • if (zone_watermark_ok(zone, order, low_wmark_pages(zone), ac->classzone_idx, alloc_flags)) { compaction_defer_reset(zone, order, false);
    • 요청 order로 low 워터마크 기준을 통과할 수 있는 경우 해당 zone의 compact_order_failed compaction 값에 order+1을 대입한다.
  • if (zone_contended == COMPACT_CONTENDED_SCHED) *contended = COMPACT_CONTENDED_SCHED;
    • COMPACT_CONTENDED_SCHED 상태인 경우 출력 인수 contended 값을 동일한 값으로 설정한다.
  • goto break_loop;
    • break_loop 레이블로 이동하고 현재 zone까지 진행하였던 가장 높은 compact 상태 값으로 반환한다.
  •  if (mode != MIGRATE_ASYNC && status == COMPACT_COMPLETE) { defer_compaction(zone, order); }
    • MIGRATE_ASYNC 요청이 아니면서 compact 완료 상태인 경우 zone의 compact_considered=0으로 리셋하고, compact_defer_shift를 증가시킨다.
  • if ((zone_contended == COMPACT_CONTENDED_SCHED) || fatal_signal_pending(current)) { *contended = COMPACT_CONTENDED_SCHED; goto break_loop; }
    • compaction 중 리스케쥴 되었거나 SIGKILL pending 상태인 경우 출력 인수에 COMPACT_CONTENDED_SCHED를 대입한다. 그런 후 break_loop로 이동하고 현재 zone까지 진행하였던 가장 높은 compact 상태 값으로 반환한다.
  • continue;
    • 다음 zone을 처리하기 위해 skip한다.
  • if (rc > COMPACT_SKIPPED && all_zones_contended) *contended = COMPACT_CONTENDED_LOCK;
    • 최종 compaction 상태가 COMPACT_SKIPPED 이상인 경우이면서 모든 zone에서 혼잡한 경우 출력 인수 contended에 COMPACT_CONTENDED_LOCK을 대입한다.
  • return rc;
    • 마지막으로 최종 compaction 상태를 반환한다.

 

compaction_deferred()

mm/compaction.c

/* Returns true if compaction should be skipped this time */
bool compaction_deferred(struct zone *zone, int order)
{
        unsigned long defer_limit = 1UL << zone->compact_defer_shift;

        if (order < zone->compact_order_failed)
                return false;

        /* Avoid possible overflow */
        if (++zone->compact_considered > defer_limit)
                zone->compact_considered = defer_limit;

        if (zone->compact_considered >= defer_limit)
                return false;

        trace_mm_compaction_deferred(zone, order);

        return true;
}

이번 타임에 compaction이 지연으로 skip되어야 하는 경우 true가 된다.

  • unsigned long defer_limit = 1UL << zone->compact_defer_shift;
  • if (order < zone->compact_order_failed) return false;
    • 요청 order가 compact_order_failed보다 작은 경우 false를 반환하여 compaction이 진행되게 해야 한다.
  • if (++zone->compact_considered > defer_limit) zone->compact_considered = defer_limit;
    • compact_considered(지연) 값을 증가시킨다. 이 값은 defer_limit 을 초과하지 않도록 제한된다.
  • if (zone->compact_considered >= defer_limit) return false;
    • compact_considered 값이 defer_limit 이상인 경우 false를 반환하여 compaction이 진행되게 해야 한다. 그렇지 않은 경우 true를 반환하여 compaction이 진행되지 않게 한다. 즉 지연 상태가 된다.

 

defer_compaction()

mm/compaction.c

/*
 * Compaction is deferred when compaction fails to result in a page
 * allocation success. 1 << compact_defer_limit compactions are skipped up
 * to a limit of 1 << COMPACT_MAX_DEFER_SHIFT
 */
void defer_compaction(struct zone *zone, int order)
{
        zone->compact_considered = 0;
        zone->compact_defer_shift++;

        if (order < zone->compact_order_failed)
                zone->compact_order_failed = order;

        if (zone->compact_defer_shift > COMPACT_MAX_DEFER_SHIFT)
                zone->compact_defer_shift = COMPACT_MAX_DEFER_SHIFT;

        trace_mm_compaction_defer_compaction(zone, order);
}

compaction이 완료될 때마다 zone의 compact_considered=0으로 리셋하고, compact_defer_shift를 증가시킨다.

  • zone->compact_considered = 0; zone->compact_defer_shift++;
    • zone의 compact_considered에 0을 대입하고 compact_defer_shift를 증가시킨다.
  • if (order < zone->compact_order_failed) zone->compact_order_failed = order;
    •  요청 order가 zone의 compact_order_failed보다 작은 경우 order를 대입한다.
  • if (zone->compact_defer_shift > COMPACT_MAX_DEFER_SHIFT) zone->compact_defer_shift = COMPACT_MAX_DEFER_SHIFT;
    • compact_defer_shifh 값은 최종 값(6)까지로 제한한다

 

compaction_defer_reset()

mm/compaction.c

/*
 * Update defer tracking counters after successful compaction of given order,
 * which means an allocation either succeeded (alloc_success == true) or is
 * expected to succeed.
 */
void compaction_defer_reset(struct zone *zone, int order,
                bool alloc_success) 
{
        if (alloc_success) {
                zone->compact_considered = 0;
                zone->compact_defer_shift = 0; 
        }               
        if (order >= zone->compact_order_failed)
                zone->compact_order_failed = order + 1;

        trace_mm_compaction_defer_reset(zone, order);
}

실제 페이지 할당이 성공된 후 호출된 경우 인수 alloc_success가 true이고 이 때 compact 진행카운터들을 초기화 한다. 실제 페이지 할당 없이 워터마크 테스트만 ok된 상태에서 호출된 경우는 인수 alloc_success가 false이며 이 때에는 compact_order_failed에 order + 1 값을 대입한다.

  • if (alloc_success) { zone->compact_considered = 0; zone->compact_defer_shift = 0; }
    • 실제 페이지 할당이 성공된 후 호출된 경우 인수 alloc_success가 true이고 이 때 compact 진행카운터들을 초기화 한다
  • if (order >= zone->compact_order_failed) zone->compact_order_failed = order + 1;
    • 실제 페이지 할당 없이 워터마크 테스트만 ok된 상태에서 호출된 경우는 인수 alloc_success가 false이며 이 때에는 compact_order_failed에 order + 1 값을 대입한다

 

compact_zone_order()

mm/compaction.c

static unsigned long compact_zone_order(struct zone *zone, int order,
                gfp_t gfp_mask, enum migrate_mode mode, int *contended,
                int alloc_flags, int classzone_idx)
{
        unsigned long ret;
        struct compact_control cc = {
                .nr_freepages = 0,
                .nr_migratepages = 0,
                .order = order,
                .gfp_mask = gfp_mask,
                .zone = zone,
                .mode = mode,
                .alloc_flags = alloc_flags,
                .classzone_idx = classzone_idx,
        };
        INIT_LIST_HEAD(&cc.freepages);
        INIT_LIST_HEAD(&cc.migratepages);

        ret = compact_zone(zone, &cc);

        VM_BUG_ON(!list_empty(&cc.freepages));
        VM_BUG_ON(!list_empty(&cc.migratepages));

        *contended = cc.contended;
        return ret;
}

compact_control_cc 구조체를 준비한 후 요청한 zone과 order 및 migrate 모드로 compact를 수행하고 결과를 반환하고 출력 인수 contended에도 혼잡 상태를 알아온다.

 

compact_zone()

mm/compaction.c

static int compact_zone(struct zone *zone, struct compact_control *cc)
{
        int ret;
        unsigned long start_pfn = zone->zone_start_pfn;
        unsigned long end_pfn = zone_end_pfn(zone);
        const int migratetype = gfpflags_to_migratetype(cc->gfp_mask);
        const bool sync = cc->mode != MIGRATE_ASYNC;
        unsigned long last_migrated_pfn = 0;

        ret = compaction_suitable(zone, cc->order, cc->alloc_flags,
                                                        cc->classzone_idx);
        switch (ret) {
        case COMPACT_PARTIAL:
        case COMPACT_SKIPPED:
                /* Compaction is likely to fail */
                return ret;
        case COMPACT_CONTINUE:
                /* Fall through to compaction */
                ;
        }

        /*
         * Clear pageblock skip if there were failures recently and compaction
         * is about to be retried after being deferred. kswapd does not do
         * this reset as it'll reset the cached information when going to sleep.
         */
        if (compaction_restarting(zone, cc->order) && !current_is_kswapd())
                __reset_isolation_suitable(zone);

        /*
         * Setup to move all movable pages to the end of the zone. Used cached
         * information on where the scanners should start but check that it
         * is initialised by ensuring the values are within zone boundaries.
         */
        cc->migrate_pfn = zone->compact_cached_migrate_pfn[sync];
        cc->free_pfn = zone->compact_cached_free_pfn;
        if (cc->free_pfn < start_pfn || cc->free_pfn > end_pfn) {
                cc->free_pfn = end_pfn & ~(pageblock_nr_pages-1);
                zone->compact_cached_free_pfn = cc->free_pfn;
        }
        if (cc->migrate_pfn < start_pfn || cc->migrate_pfn > end_pfn) {
                cc->migrate_pfn = start_pfn;
                zone->compact_cached_migrate_pfn[0] = cc->migrate_pfn;
                zone->compact_cached_migrate_pfn[1] = cc->migrate_pfn;
        }

        trace_mm_compaction_begin(start_pfn, cc->migrate_pfn,
                                cc->free_pfn, end_pfn, sync);

        migrate_prep_local();

요청 zone 및 order로 compaction을 수행할 수 있는지 확인하고 compaction을 시작하게 되면 compaction이 완료될 때까지 페이지들을 isolation 시키고 migration한다.

 

  • ret = compaction_suitable(zone, cc->order, cc->alloc_flags, cc->classzone_idx);
    • compaction을 진행할지 상태를 알아온다.
  • 상태 값이 COMPACT_CONTINUE가 아닌 경우 compaction 처리를 진행하지 않고 함수를 빠져나간다.
  • if (compaction_restarting(zone, cc->order) && !current_is_kswapd()) __reset_isolation_suitable(zone);
    • compaction 실패가 마지막 횟수까지 도달하였고 kswapd가 활동 상태가 아닌 경우 compaction을 다시 처음부터 하기 위해 zone의 usemap(pageblock_flags)에서 모든 PB_migrate_skip 비트를 clear한다.
  • cc->migrate_pfn = zone->compact_cached_migrate_pfn[sync];
    • migrate_pfn에 compact_cached_migrate_pfn[sync]를 대입한다.
      • 처음에는 z->start_pfn이 대입된다.
  • cc->free_pfn = zone->compact_cached_free_pfn;
    • free_pfn에 compact_cached_free_pfn을 대입한다.
      • 처음에는 z->end_pfn이 대입된다
  • if (cc->free_pfn < start_pfn || cc->free_pfn > end_pfn) { cc->free_pfn = end_pfn & ~(pageblock_nr_pages-1); zone->compact_cached_free_pfn = cc->free_pfn; }
    • free_pfn이 zone의 범위를 벗어나는 경우 free_pfn과 compact_cached_free_pfn에 end_pfn을 페이지 블럭단위로 round down(절삭)한 pfn을 대입한다.
  • if (cc->migrate_pfn < start_pfn || cc->migrate_pfn > end_pfn) { cc->migrate_pfn = start_pfn; zone->compact_cached_migrate_pfn[0] = cc->migrate_pfn; zone- >compact_cached_migrate_pfn[1] = cc->migrate_pfn; }
    • migrate_pfn이 zone의 범위를 벗어나는 경우 migrate_pfn과 compact_cached_migrate_pfn[0~1]에 start_pfn을 대입한다.
  • migrate_prep_local();
    • lru_add_drain_cpu(get_cpu()); put_cpu();을 호출하여  cpu 페이지 캐시(pagevec)에서 페이지를 회수하여 해당 zone(또는 memory cgroup의 zone)에 있는 lruvec로 이전하게 한다.

 

        while ((ret = compact_finished(zone, cc, migratetype)) ==
                                                COMPACT_CONTINUE) {
                int err;
                unsigned long isolate_start_pfn = cc->migrate_pfn;

                switch (isolate_migratepages(zone, cc)) {
                case ISOLATE_ABORT:
                        ret = COMPACT_PARTIAL;
                        putback_movable_pages(&cc->migratepages);
                        cc->nr_migratepages = 0;
                        goto out;
                case ISOLATE_NONE:
                        /*
                         * We haven't isolated and migrated anything, but
                         * there might still be unflushed migrations from
                         * previous cc->order aligned block.
                         */
                        goto check_drain;
                case ISOLATE_SUCCESS:
                        ;
                }

                err = migrate_pages(&cc->migratepages, compaction_alloc,
                                compaction_free, (unsigned long)cc, cc->mode,
                                MR_COMPACTION);

                trace_mm_compaction_migratepages(cc->nr_migratepages, err,
                                                        &cc->migratepages);

                /* All pages were either migrated or will be released */
                cc->nr_migratepages = 0;
                if (err) {
                        putback_movable_pages(&cc->migratepages);
                        /*
                         * migrate_pages() may return -ENOMEM when scanners meet
                         * and we want compact_finished() to detect it
                         */
                        if (err == -ENOMEM && cc->free_pfn > cc->migrate_pfn) {
                                ret = COMPACT_PARTIAL;
                                goto out;
                        }
                }

                /*
                 * Record where we could have freed pages by migration and not
                 * yet flushed them to buddy allocator. We use the pfn that
                 * isolate_migratepages() started from in this loop iteration
                 * - this is the lowest page that could have been isolated and
                 * then freed by migration.
                 */
                if (!last_migrated_pfn)
                        last_migrated_pfn = isolate_start_pfn;
  • while ((ret = compact_finished(zone, cc, migratetype)) == COMPACT_CONTINUE) {
    • compact를 수행하여 결과가 COMPACT_CONTINUE인 동안 루프를 돈다.
  • switch (isolate_migratepages(zone, cc)) {
    • 페이지를 isoaltion한 결과가
  • case ISOLATE_ABORT: ret = COMPACT_PARTIAL; putback_movable_pages(&cc->migratepages); cc->nr_migratepages = 0; goto out;
    • 결과가 ISOLATE_ABORT일 때 compaction 결과를 COMPACT_PARTIAL로 하고 migrate 페이지 수를 0으로 한후 out 레이블을 통해 함수를 빠져나간다.
  • case ISOLATE_NONE: goto check_drain;
    • 결과가 ISOLATE_NONE인 경우 아무 페이지도 isolation하지 않은 경우이고, 이 때에 cpu 캐시를 drain하기 위해 check_drain 레이블로 이동한 후 계속 루프를 진행하게 한다.
  • case ISOLATE_SUCCESS:
    • 결과가 ISOLATE_SUCCESS인 경우 migration을 위해 다음 루틴을 계속 진행한다.
  • err = migrate_pages(&cc->migratepages, compaction_alloc, compaction_free, (unsigned long)cc, cc->mode, MR_COMPACTION);
    • 페이지를 migration한다.
  • if (err) { putback_movable_pages(&cc->migratepages);
    • migration에 실패한 경우
  • if (err == -ENOMEM && cc->free_pfn > cc->migrate_pfn) { ret = COMPACT_PARTIAL; goto out; }
    • 만일 메모리 부족이면서 migrate_pfn이 free_pfn까지 완료되지 않은 상태인 경우 compaction 결과로 COMPACT_PARTIAL을 담고 out 레이블로 이동하여 함수를 빠져나간다.
  • if (!last_migrated_pfn) last_migrated_pfn = isolate_start_pfn;
    • last_migrated_pfn이 한번도 갱신되지 않은 경우 isolate_start_pfn 값을 담는다.

 

check_drain:
                /*
                 * Has the migration scanner moved away from the previous
                 * cc->order aligned block where we migrated from? If yes,
                 * flush the pages that were freed, so that they can merge and
                 * compact_finished() can detect immediately if allocation
                 * would succeed.
                 */
                if (cc->order > 0 && last_migrated_pfn) {
                        int cpu;
                        unsigned long current_block_start =
                                cc->migrate_pfn & ~((1UL << cc->order) - 1);

                        if (last_migrated_pfn < current_block_start) {
                                cpu = get_cpu();
                                lru_add_drain_cpu(cpu);
                                drain_local_pages(zone);
                                put_cpu();
                                /* No more flushing until we migrate again */
                                last_migrated_pfn = 0;
                        }
                }

        }

out:
        /*
         * Release free pages and update where the free scanner should restart,
         * so we don't leave any returned pages behind in the next attempt.
         */
        if (cc->nr_freepages > 0) {
                unsigned long free_pfn = release_freepages(&cc->freepages);

                cc->nr_freepages = 0;
                VM_BUG_ON(free_pfn == 0);
                /* The cached pfn is always the first in a pageblock */
                free_pfn &= ~(pageblock_nr_pages-1);
                /*
                 * Only go back, not forward. The cached pfn might have been
                 * already reset to zone end in compact_finished()
                 */
                if (free_pfn > zone->compact_cached_free_pfn)
                        zone->compact_cached_free_pfn = free_pfn;
        }

        trace_mm_compaction_end(start_pfn, cc->migrate_pfn,
                                cc->free_pfn, end_pfn, sync, ret);

        return ret;
}
  • check_drain: if (cc->order > 0 && last_migrated_pfn) {
    • 루프 마지막마다  last_migrated_pfn이 있는 경우
  • if (last_migrated_pfn < current_block_start) { lru_add_drain_cpu(cpu); drain_local_pages(zone); last_migrated_pfn = 0; }
    • last_migrated_pfn이 current_block_start보다 작은 경우 cpu 캐시를 drain 시키고 last_migrated_pfn을 0으로 리셋하고 계속 루프를 수행한다.
  • out:
    • 루프가 완료되었거나 함수를 빠져나가는 경우 진입되는 레이블
  • if (cc->nr_freepages > 0) { unsigned long free_pfn = release_freepages(&cc->freepages);
    • nr_freepages가 0을 초과한 경우 freepages를 해제한다.

 

compaction_restarting()

mm/compaction.c

/* Returns true if restarting compaction after many failures */
bool compaction_restarting(struct zone *zone, int order)
{
        if (order < zone->compact_order_failed)
                return false;

        return zone->compact_defer_shift == COMPACT_MAX_DEFER_SHIFT &&
                zone->compact_considered >= 1UL << zone->compact_defer_shift;
}

compaction 실패가 마지막 횟수(6)이고 compact 시도도 64 이상인 경우 true를 반환한다.

  • 요청 order가 compact_order_failed 보다 작은 경우 false를 반환
  • compact_defer_shift가 마지막(6)이면서 compact_considered값이 64이상인 경우 true를 반환

 

compact_finished()

mm/compaction.c

static int compact_finished(struct zone *zone, struct compact_control *cc,
                            const int migratetype)
{
        int ret;

        ret = __compact_finished(zone, cc, migratetype);
        trace_mm_compaction_finished(zone, cc->order, ret);
        if (ret == COMPACT_NO_SUITABLE_PAGE)
                ret = COMPACT_CONTINUE;

        return ret;
}

 

다음 그림은 compact 진행중 상태를 알아오는 모습을 보여준다.

compact_finished-1a

 

__compact_finished()

mm/compaction.c

static int __compact_finished(struct zone *zone, struct compact_control *cc,
                            const int migratetype)
{
        unsigned int order;
        unsigned long watermark;

        if (cc->contended || fatal_signal_pending(current))
                return COMPACT_PARTIAL;

        /* Compaction run completes if the migrate and free scanner meet */
        if (cc->free_pfn <= cc->migrate_pfn) {
                /* Let the next compaction start anew. */
                zone->compact_cached_migrate_pfn[0] = zone->zone_start_pfn;
                zone->compact_cached_migrate_pfn[1] = zone->zone_start_pfn;
                zone->compact_cached_free_pfn = zone_end_pfn(zone);

                /*
                 * Mark that the PG_migrate_skip information should be cleared
                 * by kswapd when it goes to sleep. kswapd does not set the
                 * flag itself as the decision to be clear should be directly
                 * based on an allocation request.
                 */
                if (!current_is_kswapd())
                        zone->compact_blockskip_flush = true;

                return COMPACT_COMPLETE;
        }

        /*
         * order == -1 is expected when compacting via
         * /proc/sys/vm/compact_memory
         */
        if (cc->order == -1)
                return COMPACT_CONTINUE;

        /* Compaction run is not finished if the watermark is not met */
        watermark = low_wmark_pages(zone);

        if (!zone_watermark_ok(zone, cc->order, watermark, cc->classzone_idx,
                                                        cc->alloc_flags))
                return COMPACT_CONTINUE;

        /* Direct compactor: Is a suitable page free? */
        for (order = cc->order; order < MAX_ORDER; order++) {
                struct free_area *area = &zone->free_area[order];

                /* Job done if page is free of the right migratetype */
                if (!list_empty(&area->free_list[migratetype]))
                        return COMPACT_PARTIAL;

                /* Job done if allocation would set block type */
                if (order >= pageblock_order && area->nr_free)
                        return COMPACT_PARTIAL;
        }

        return COMPACT_NO_SUITABLE_PAGE;
}
  • if (cc->contended || fatal_signal_pending(current)) return COMPACT_PARTIAL;
    • compaction이 혼잡한 상태이거나 SIGKILL 시그널이 펜딩된 상태인 경우 compaction 상태를 COMPACT_PARTIAL로 반환한다.
  • if (cc->free_pfn <= cc->migrate_pfn) {
    • free_pfn이 migrate_pfn 이하인 경우, 즉 마지막까지 처리된 경우
  • zone->compact_cached_migrate_pfn[0] = zone->zone_start_pfn; zone->compact_cached_migrate_pfn[1] = zone->zone_start_pfn;
    • compact_cached_migrate_pfn[] 값에 zone_start_pfn을 대입한다.
  • zone->compact_cached_free_pfn = zone_end_pfn(zone);
    • compact_cached_free_pfn에 zone_end_pfn을 대입한다.
  • if (!current_is_kswapd()) zone->compact_blockskip_flush = true;
    • 햔재 kswapd가 동작하지 않는 경우 compact_blockskip_flush를 true로 한다.
  • return COMPACT_COMPLETE;
    • compaction 완료로 함수를 빠져나간다.
  • if (cc->order == -1) return COMPACT_CONTINUE;
    • order가 -1로 요청된 경우는 COMPACT_CONTINUE로 함수를 빠져나간다.
      • /proc/sys/vm/compact_memory를 조작하여 강제로 compaction을 시작한 경우
  • watermark = low_wmark_pages(zone); if (!zone_watermark_ok(zone, cc->order, watermark, cc->classzone_idx, cc->alloc_flags)) return COMPACT_CONTINUE;
    • 요청 order 값으로 low 워터마크 기준을 ok 하는 경우 COMPACT_CONTINUE로 함수를 빠져나간다.
  • for (order = cc->order; order < MAX_ORDER; order++) { struct free_area *area = &zone->free_area[order];
    • 버디 시스템의 각 slot을 order 부터 MAX_ORDER-1 까지 루프를 돌며 조사한다.
  • if (!list_empty(&area->free_list[migratetype])) return COMPACT_PARTIAL;
    • 할당할 페이지가 발견된 경우 COMPACT_PARTIAL을 반환한다.
  • if (order >= pageblock_order && area->nr_free) return COMPACT_PARTIAL;
    • order가 pageblock_order 보다 크면서 nr_free가 존재하는 경우 COMPACT_PARTIAL을 반환한다.
  • return COMPACT_NO_SUITABLE_PAGE;
    • 적절한 free 페이지가 발견되지 못한 경우 COMPACT_NO_SUITABLE_PAGE를 반환한다.

 

참고

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.