Zoned Allocator -5- (Direct Compact-Isolation)

다음 그림은 두 개의 migration 스캐너와 free 스캐너가 동작하는 방향을 보여준다.

  • isolate_migratepages()는 cc->migrate_pfn에서 윗 방향으로 스캔을 한다.
    • isolate_migratepages_block()은 하나의 페이지 블럭에서 isolated page를 cc->migratepages에 추가한다.
  • isolate_freepages()는 cc->free_pfn에서 아래 방향으로 스캔을 한다.
    • isolate_freepages_block()은 하나의 페이지 블럭에서 isolated page를 cc->freepages에 추가한다.

isolation-1

 

Isolation Migratepages

__reset_isolation_suitable()

mm/compaction.c

/*
 * This function is called to clear all cached information on pageblocks that
 * should be skipped for page isolation when the migrate and free page scanner
 * meet.
 */
static void __reset_isolation_suitable(struct zone *zone)
{
        unsigned long start_pfn = zone->zone_start_pfn;
        unsigned long end_pfn = zone_end_pfn(zone);
        unsigned long pfn;

        zone->compact_cached_migrate_pfn[0] = start_pfn;
        zone->compact_cached_migrate_pfn[1] = start_pfn;
        zone->compact_cached_free_pfn = end_pfn;
        zone->compact_blockskip_flush = false;

        /* Walk the zone and mark every pageblock as suitable for isolation */
        for (pfn = start_pfn; pfn < end_pfn; pfn += pageblock_nr_pages) {
                struct page *page;

                cond_resched();

                if (!pfn_valid(pfn))
                        continue;

                page = pfn_to_page(pfn);
                if (zone != page_zone(page))
                        continue;

                clear_pageblock_skip(page);
        }
}

해당 zone의 isolate 및 free 스캔 시작 주소를 리셋하고 페이지블럭의 skip bit를 모두 클리어한다.

  • migration 대상 영역은 zone의 시작 pfn과 끝 pfn이다.
    • isolate 스캐너가 시작할 pfn으로 compact_cached_migrate_pfn[0..1]에 zone의 시작 pfn을 대입한다.
    • free 스캐너가 시작할 pfn으로 compact_cached_free_pfn에 zone의 끝 pfn을 대입한다.
  • pageblock->flags
    • Sparse 메모리 모델이 아닌 경우 zone->pageblock_flags가 지정한 usemap
    • Sparse 메모리 모델인 경우 mem_section[].pageblock_flags가 지정한 usemap

 

isolate_migratepages()

mm/compaction.c

/*
 * Isolate all pages that can be migrated from the first suitable block,
 * starting at the block pointed to by the migrate scanner pfn within
 * compact_control.
 */
static isolate_migrate_t isolate_migratepages(struct zone *zone,
                                        struct compact_control *cc)
{
        unsigned long low_pfn, end_pfn;
        struct page *page;
        const isolate_mode_t isolate_mode =
                (cc->mode == MIGRATE_ASYNC ? ISOLATE_ASYNC_MIGRATE : 0);

        /*
         * Start at where we last stopped, or beginning of the zone as
         * initialized by compact_zone()
         */
        low_pfn = cc->migrate_pfn;

        /* Only scan within a pageblock boundary */
        end_pfn = ALIGN(low_pfn + 1, pageblock_nr_pages);

        /*
         * Iterate over whole pageblocks until we find the first suitable.
         * Do not cross the free scanner.
         */
        for (; end_pfn <= cc->free_pfn;
                        low_pfn = end_pfn, end_pfn += pageblock_nr_pages) {

                /*
                 * This can potentially iterate a massively long zone with
                 * many pageblocks unsuitable, so periodically check if we
                 * need to schedule, or even abort async compaction.
                 */
                if (!(low_pfn % (SWAP_CLUSTER_MAX * pageblock_nr_pages))
                                                && compact_should_abort(cc))
                        break;

                page = pageblock_pfn_to_page(low_pfn, end_pfn, zone);
                if (!page)
                        continue;

                /* If isolation recently failed, do not retry */
                if (!isolation_suitable(cc, page))
                        continue;

                /*
                 * For async compaction, also only scan in MOVABLE blocks.
                 * Async compaction is optimistic to see if the minimum amount
                 * of work satisfies the allocation.
                 */
                if (cc->mode == MIGRATE_ASYNC &&
                    !migrate_async_suitable(get_pageblock_migratetype(page)))
                        continue;

                /* Perform the isolation */
                low_pfn = isolate_migratepages_block(cc, low_pfn, end_pfn,
                                                                isolate_mode);

                if (!low_pfn || cc->contended) {
                        acct_isolated(zone, cc);
                        return ISOLATE_ABORT;
                }

                /*
                 * Either we isolated something and proceed with migration. Or
                 * we failed and compact_zone should decide if we should
                 * continue or not.
                 */
                break;
        }

        acct_isolated(zone, cc);
        /*
         * Record where migration scanner will be restarted. If we end up in
         * the same pageblock as the free scanner, make the scanners fully
         * meet so that compact_finished() terminates compaction.
         */
        cc->migrate_pfn = (end_pfn <= cc->free_pfn) ? low_pfn : cc->free_pfn;

        return cc->nr_migratepages ? ISOLATE_SUCCESS : ISOLATE_NONE;
}

지정된 zone에서 compaction을 수행한다.

  • const isolate_mode_t isolate_mode = (cc->mode == MIGRATE_ASYNC ? ISOLATE_ASYNC_MIGRATE : 0);
    • migrate 모드가 비동기이면 isolate_mode에 ISOLATE_ASYNC_MIGRATE(4)를 대입하고 그렇지 않은 경우 0을 대입한다.
  • low_pfn = cc->migrate_pfn;
    • 마지막에 종료한 pfn 값 또는 compact_zone()에서 초기화한 zone의 시작 pfn 값부터 시작하도록 준비한다.
  • end_pfn = ALIGN(low_pfn + 1, pageblock_nr_pages);
    • 페이지 블럭 범위내에서 스캔할 수 있도록 준비한다.
  • for (; end_pfn <= cc->free_pfn; low_pfn = end_pfn, end_pfn += pageblock_nr_pages) {
    • end_pfn이 free 스캐너 미만에서 루프를 돌게하고, 증가치는 페이지 블럭단위로 한다.
  • if (!(low_pfn % (SWAP_CLUSTER_MAX * pageblock_nr_pages)) && compact_should_abort(cc)) break;
    • 처리할 범위가 크므로 주기적으로 중단 요소가 있는지 체크한다.
      • 체크 주기
        • SWAP_CLUSTER_MAX(32) 페이지 블럭단위
      •  중단요소
        • 비동기 migration 처리 중이면서 우선 순위 높은 태스크의 리스케쥴 요청이 있는 경우
  • page = pageblock_pfn_to_page(low_pfn, end_pfn, zone); if (!page) continue;
    • 페이지 블럭의 첫 페이지를 가져온다. pfn 범위가 요청한 zone이 아닌 경우 skip 한다.
      • zone의 마지막 페이지블럭이 partial인 경우 skip 한다.
  • if (!isolation_suitable(cc, page)) continue;
    • 해당 zone과 블럭에서 isolation을 실행해도 되는지 여부를 체크한다.
      • 최근에 해당 페이지블럭에서 isolation이 취소된적이 있는 경우 skip하도록 false를 반환한다.
  • if (cc->mode == MIGRATE_ASYNC && !migrate_async_suitable(get_pageblock_migratetype(page))) continue;
    • 비동기 migration 처리 중이면서 페이지 블럭의 migrate 타입이 MIGRATE_CMA 또는 MIGRATE_MOVABLE이 아닌 경우 skip한다.
      • MIGRATE_UNMOVABLE, MIGRATE_RECLAIMABLE, MIGRATE_RESERVE, MIGRATE_ISOLATE은 migration 대상이 될 수 없다.
  • low_pfn = isolate_migratepages_block(cc, low_pfn, end_pfn, isolate_mode);
    • 해당 페이지블럭을 isolation 한다.
  • if (!low_pfn || cc->contended) { acct_isolated(zone, cc); return ISOLATE_ABORT; }
    • isolation이 실패하거나 혼잡한 경우 isolate된 migrate 페이지 리스트에서 anon과 file 페이지에 대한 stat을 추가한다. 그런 후 ISOLATE_ABORT 결과로 빠져나간다.
  • break; } acct_isolated(zone, cc);
    • 정상적으로 isolation이 완료된 경우 isolate된 migrate 페이지 리스트에서 anon과 file 페이지에 대한 stat을 추가한다.
  • cc->migrate_pfn = (end_pfn <= cc->free_pfn) ? low_pfn : cc->free_pfn;
    • 다음에 중단된 pfn 부터 계속 처리하기 위해 백업한다.
  • return cc->nr_migratepages ? ISOLATE_SUCCESS : ISOLATE_NONE;
    • migrate된 페이지 수를 체크하여 결과를 반환한다.

 

isolation_suitable()

mm/compaction.c

/* Returns true if the pageblock should be scanned for pages to isolate. */
static inline bool isolation_suitable(struct compact_control *cc,
                                        struct page *page)
{
        if (cc->ignore_skip_hint)
                return true;

        return !get_pageblock_skip(page);
}

해당 zone과 블럭에서 isolation을 실행해도 되는지 여부를 체크한다.

  • 최근에 해당 페이지블럭에서 isolation이 취소된적이 있는 경우 skip하도록 false를 반환한다.

 

isolate_migratepages_block()

mm/compaction.c

/**
 * isolate_migratepages_block() - isolate all migrate-able pages within
 *                                a single pageblock
 * @cc:         Compaction control structure.
 * @low_pfn:    The first PFN to isolate
 * @end_pfn:    The one-past-the-last PFN to isolate, within same pageblock
 * @isolate_mode: Isolation mode to be used.
 *
 * Isolate all pages that can be migrated from the range specified by
 * [low_pfn, end_pfn). The range is expected to be within same pageblock.
 * Returns zero if there is a fatal signal pending, otherwise PFN of the
 * first page that was not scanned (which may be both less, equal to or more
 * than end_pfn).
 *
 * The pages are isolated on cc->migratepages list (not required to be empty),
 * and cc->nr_migratepages is updated accordingly. The cc->migrate_pfn field
 * is neither read nor updated.
 */
static unsigned long
isolate_migratepages_block(struct compact_control *cc, unsigned long low_pfn,
                        unsigned long end_pfn, isolate_mode_t isolate_mode)
{
        struct zone *zone = cc->zone;
        unsigned long nr_scanned = 0, nr_isolated = 0;
        struct list_head *migratelist = &cc->migratepages;
        struct lruvec *lruvec;
        unsigned long flags = 0;
        bool locked = false;
        struct page *page = NULL, *valid_page = NULL;
        unsigned long start_pfn = low_pfn;

        /*
         * Ensure that there are not too many pages isolated from the LRU
         * list by either parallel reclaimers or compaction. If there are,
         * delay for some time until fewer pages are isolated
         */
        while (unlikely(too_many_isolated(zone))) {
                /* async migration should just abort */
                if (cc->mode == MIGRATE_ASYNC)
                        return 0;

                congestion_wait(BLK_RW_ASYNC, HZ/10);

                if (fatal_signal_pending(current))
                        return 0;
        }

        if (compact_should_abort(cc))
                return 0;
  • unsigned long nr_scanned = 0, nr_isolated = 0;
    • 스캔 페이지 수와 isolated 페이지 수를 0으로 초기화한다.
  • struct list_head *migratelist = &cc->migratepages;
    • migratelist에는 migrate 스캐너에 의해 검색된 migrated될 페이지가 담긴다.
  • while (unlikely(too_many_isolated(zone))) {
    • 작은 확률로 isolated 페이지가 과다한 경우에만 루프를 돈다.
  • if (cc->mode == MIGRATE_ASYNC) return 0;
    • 비동기 migration 중이면 함수 처리를 중단한다.
  • congestion_wait(BLK_RW_ASYNC, HZ/10);
  • if (fatal_signal_pending(current)) return 0;
    • SIGKILL 시그널의 처리가 지연된 경우 함수 처리를 중단한다.
  • if (compact_should_abort(cc)) return 0;
    • 비동기 migration 처리 중이면서 우선 순위 높은 태스크의 리스케쥴 요청이 있는 경우 함수 처리를 중단한다.

 

        /* Time to isolate some pages for migration */
        for (; low_pfn < end_pfn; low_pfn++) {
                /*
                 * Periodically drop the lock (if held) regardless of its
                 * contention, to give chance to IRQs. Abort async compaction
                 * if contended.
                 */
                if (!(low_pfn % SWAP_CLUSTER_MAX)
                    && compact_unlock_should_abort(&zone->lru_lock, flags,
                                                                &locked, cc))
                        break;

                if (!pfn_valid_within(low_pfn))
                        continue;
                nr_scanned++;

                page = pfn_to_page(low_pfn);

                if (!valid_page)
                        valid_page = page;

                /*
                 * Skip if free. We read page order here without zone lock
                 * which is generally unsafe, but the race window is small and
                 * the worst thing that can happen is that we skip some
                 * potential isolation targets.
                 */
                if (PageBuddy(page)) {
                        unsigned long freepage_order = page_order_unsafe(page);

                        /*
                         * Without lock, we cannot be sure that what we got is
                         * a valid page order. Consider only values in the
                         * valid order range to prevent low_pfn overflow.
                         */
                        if (freepage_order > 0 && freepage_order < MAX_ORDER)
                                low_pfn += (1UL << freepage_order) - 1;
                        continue;
                }

                /*
                 * Check may be lockless but that's ok as we recheck later.
                 * It's possible to migrate LRU pages and balloon pages
                 * Skip any other type of page
                 */
                if (!PageLRU(page)) {
                        if (unlikely(balloon_page_movable(page))) {
                                if (balloon_page_isolate(page)) {
                                        /* Successfully isolated */
                                        goto isolate_success;
                                }
                        }
                        continue;
                }
  • for (; low_pfn < end_pfn; low_pfn++) {
    • low_pfn 부터 end_pfn까지 루프를 돈다.
  • if (!(low_pfn % SWAP_CLUSTER_MAX) && compact_unlock_should_abort(&zone->lru_lock, flags, &locked, cc)) break;
    • 처리할 범위가 크므로 주기적으로 lock된 경우 unlock을 하고 중단 요소가 있는 경우 cc->contended에 COMPACT_CONTENDED_SCHED를 설정하고 루프를 빠져나간다.
      • 체크 주기
        • SWAP_CLUSTER_MAX(32) 페이지 블럭단위
      •  중단요소
        • SIGKILL 신호가 처리 지연된 경우
        • 비동기 migration 중에 높은 우선 순위의 태스크의 리스케쥴 요청이 있는 경우
  • if (!pfn_valid_within(low_pfn)) continue;
    • 가용 메모리 페이지가 아닌 경우 skip
  •  nr_scanned++;
    • 스캔 카운터를 증가
  • page = pfn_to_page(low_pfn); if (!valid_page) valid_page = page;
    • low_pfn의 page를 알아오고 valid_page에 기록된 적이 없으면 update한다.
  • if (PageBuddy(page)) { unsigned long freepage_order = page_order_unsafe(page); if (freepage_order > 0 && freepage_order < MAX_ORDER) low_pfn += (1UL << freepage_order) – 1; continue; }
    • 버디 페이지인 경우 skip한다.
      • freepage_order를 알아와서 0보다 크면서 MAX_ORDER 보다 작은 경우 low_pfn 값을 페이지 수-1 만큼 증가시킨 후 skip한다.
        • page->private에 order가 담겨있다.
  • if (!PageLRU(page)) { if (unlikely(balloon_page_movable(page))) { if (balloon_page_isolate(page)) { goto isolate_success; } } continue;  }
    • lru를 가지고 있지 않으면 skip 하지만 작은 확률로  balloon 페이지가 movable 이면서 isolate된 경우 isolate_success 레이블로 이동한다.

 

                /*
                 * PageLRU is set. lru_lock normally excludes isolation
                 * splitting and collapsing (collapsing has already happened
                 * if PageLRU is set) but the lock is not necessarily taken
                 * here and it is wasteful to take it just to check transhuge.
                 * Check TransHuge without lock and skip the whole pageblock if
                 * it's either a transhuge or hugetlbfs page, as calling
                 * compound_order() without preventing THP from splitting the
                 * page underneath us may return surprising results.
                 */
                if (PageTransHuge(page)) {
                        if (!locked)
                                low_pfn = ALIGN(low_pfn + 1,
                                                pageblock_nr_pages) - 1;
                        else
                                low_pfn += (1 << compound_order(page)) - 1;

                        continue;
                }

                /*
                 * Migration will fail if an anonymous page is pinned in memory,
                 * so avoid taking lru_lock and isolating it unnecessarily in an
                 * admittedly racy check.
                 */
                if (!page_mapping(page) &&
                    page_count(page) > page_mapcount(page))
                        continue;

                /* If we already hold the lock, we can skip some rechecking */
                if (!locked) {
                        locked = compact_trylock_irqsave(&zone->lru_lock,
                                                                &flags, cc);
                        if (!locked)
                                break;

                        /* Recheck PageLRU and PageTransHuge under lock */
                        if (!PageLRU(page))
                                continue;
                        if (PageTransHuge(page)) {
                                low_pfn += (1 << compound_order(page)) - 1;
                                continue;
                        }
                }

                lruvec = mem_cgroup_page_lruvec(page, zone);

                /* Try isolate the page */
                if (__isolate_lru_page(page, isolate_mode) != 0)
                        continue;

                VM_BUG_ON_PAGE(PageTransCompound(page), page);

                /* Successfully isolated */
                del_page_from_lru_list(page, lruvec, page_lru(page));
  • if (PageTransHuge(page)) { if (!locked) low_pfn = ALIGN(low_pfn + 1, pageblock_nr_pages) – 1; else low_pfn += (1 << compound_order(page)) – 1; continue; }
    • compound 페이지인 경우 skip 한다.
      • lock이 걸려 있지 않은 경우 페이지 블럭단위로 low_pfn을 증가시키고 lock이 걸려 있는 경우 compound order 페이지 단위로 증가시킨다.
  • if (!page_mapping(page) && page_count(page) > page_mapcount(page)) continue;
    • anonymous 페이지를 사용하는 경우 skip 한다.
    • 페이지가 SwapCache 매핑된 경우가 아니면서 페이지 레퍼런스 카운터가 매핑 카운터보다 큰 경우 skip 한다.
  • if (!locked) { locked = compact_trylock_irqsave(&zone->lru_lock, &flags, cc);
    • 만일 lock이 걸려있지 않은 경우 lock을 획득한다.
  • if (!PageLRU(page)) continue; if (PageTransHuge(page)) { low_pfn += (1 << compound_order(page)) – 1; continue; }
    • lock이 걸려 있지 않았었던 경우 다시 한 번 lru를 사용하지 않는 페이지와 compound 페이지를 skip 한다.
  • lruvec = mem_cgroup_page_lruvec(page, zone);
    • memcg를 사용하는 경우 해당 zone에서 사용하는 lruvec를 알아온다.
  • if (__isolate_lru_page(page, isolate_mode) != 0) continue;
    • 페이지에서 LRU의 제거를 시도하고 실패한 경우 skip 한다.
  • del_page_from_lru_list(page, lruvec, page_lru(page));
    • isolation이 성공한 경우 lruvec에서 제거한다.

 

isolate_success:
                list_add(&page->lru, migratelist);
                cc->nr_migratepages++;
                nr_isolated++;

                /* Avoid isolating too much */
                if (cc->nr_migratepages == COMPACT_CLUSTER_MAX) {
                        ++low_pfn;
                        break;
                }
        }

        /*
         * The PageBuddy() check could have potentially brought us outside
         * the range to be scanned.
         */
        if (unlikely(low_pfn > end_pfn))
                low_pfn = end_pfn;

        if (locked)
                spin_unlock_irqrestore(&zone->lru_lock, flags);

        /*
         * Update the pageblock-skip information and cached scanner pfn,
         * if the whole pageblock was scanned without isolating any page.
         */
        if (low_pfn == end_pfn)
                update_pageblock_skip(cc, valid_page, nr_isolated, true);

        trace_mm_compaction_isolate_migratepages(start_pfn, low_pfn,
                                                nr_scanned, nr_isolated);

        count_compact_events(COMPACTMIGRATE_SCANNED, nr_scanned);
        if (nr_isolated)
                count_compact_events(COMPACTISOLATED, nr_isolated);

        return low_pfn;
}
  • isolate_success:
    • isolation이 성공되었다고 간주되어 이동되어올 수 있는 레이블이다.
  • list_add(&page->lru, migratelist); cc->nr_migratepages++; nr_isolated++;
    • migratelist에 페이지를 추가하고 관련 stat들을 증가시킨다.
  • if (cc->nr_migratepages == COMPACT_CLUSTER_MAX) { ++low_pfn; break; } }
    • migrate 페이지가 적정량(32)이 된 경우 처리를 중단한다. 그렇지 않은 경우 계속 다음 페이지를 처리하도록 루프를 돈다.
  • if (unlikely(low_pfn > end_pfn)) low_pfn = end_pfn;
    • low_pfn이 end_pfn을 초과한 경우 low_pfn 값에 end_pfn을 대입한다.
  • if (locked) spin_unlock_irqrestore(&zone->lru_lock, flags)
    • lock이 걸려 있는 상태이면 lock을 release한다.
  • if (low_pfn == end_pfn) update_pageblock_skip(cc, valid_page, nr_isolated, true);
    • 페이지 블럭의 끝까지 처리하였고 처리된 isolated 페이지가 없는 경우 valid_page에 해당하는 페이지 블럭의 migrate skip 비트를 설정하고 migrate 스캐너의 시작 pfn을 해당 페이지로 설정한다.
      • migrate 스캐너 시작 pfn
        • compact_cached_migrate_pfn[0..1]
  • count_compact_events(COMPACTMIGRATE_SCANNED, nr_scanned); if (nr_isolated) count_compact_events(COMPACTISOLATED, nr_isolated);
    • COMPACTMIGRATE_SCANNED stat을 nr_scanned 만큼 증가시키고, COMPACTISOLATED stat 역시 nr_isolated 만큼 증가시킨다.

 

too_many_isolated()

mm/compaction.c

/* Similar to reclaim, but different enough that they don't share logic */
static bool too_many_isolated(struct zone *zone)
{
        unsigned long active, inactive, isolated;

        inactive = zone_page_state(zone, NR_INACTIVE_FILE) +
                                        zone_page_state(zone, NR_INACTIVE_ANON);
        active = zone_page_state(zone, NR_ACTIVE_FILE) +
                                        zone_page_state(zone, NR_ACTIVE_ANON);
        isolated = zone_page_state(zone, NR_ISOLATED_FILE) +
                                        zone_page_state(zone, NR_ISOLATED_ANON);

        return isolated > (inactive + active) / 2;
}

isolated 페이지 수가 너무 많은 경우 true를 반환한다.

  • inactive file + active file stat의 절반을 초과하는 경우 true

 

compact_should_abort()

mm/compaction.c

/*
 * Aside from avoiding lock contention, compaction also periodically checks
 * need_resched() and either schedules in sync compaction or aborts async
 * compaction. This is similar to what compact_unlock_should_abort() does, but
 * is used where no lock is concerned.
 *
 * Returns false when no scheduling was needed, or sync compaction scheduled.
 * Returns true when async compaction should abort.
 */
static inline bool compact_should_abort(struct compact_control *cc)
{
        /* async compaction aborts if contended */
        if (need_resched()) {
                if (cc->mode == MIGRATE_ASYNC) {
                        cc->contended = COMPACT_CONTENDED_SCHED;
                        return true;
                }

                cond_resched();
        }

        return false;
}

비동기 migration 처리 중이면서 우선 순위 높은 태스크의 리스케쥴 요청이 있는 경우 true가 반환된다.

 

compact_unlock_should_abort()

mm/compaction.c

/*
 * Compaction requires the taking of some coarse locks that are potentially
 * very heavily contended. The lock should be periodically unlocked to avoid
 * having disabled IRQs for a long time, even when there is nobody waiting on
 * the lock. It might also be that allowing the IRQs will result in
 * need_resched() becoming true. If scheduling is needed, async compaction
 * aborts. Sync compaction schedules.
 * Either compaction type will also abort if a fatal signal is pending.
 * In either case if the lock was locked, it is dropped and not regained.
 *
 * Returns true if compaction should abort due to fatal signal pending, or
 *              async compaction due to need_resched()
 * Returns false when compaction can continue (sync compaction might have
 *              scheduled)
 */
static bool compact_unlock_should_abort(spinlock_t *lock,
                unsigned long flags, bool *locked, struct compact_control *cc)
{
        if (*locked) {
                spin_unlock_irqrestore(lock, flags);
                *locked = false;
        }

        if (fatal_signal_pending(current)) {
                cc->contended = COMPACT_CONTENDED_SCHED;
                return true;
        }

        if (need_resched()) {
                if (cc->mode == MIGRATE_ASYNC) {
                        cc->contended = COMPACT_CONTENDED_SCHED;
                        return true;
                }
                cond_resched();
        }

        return false;
}

lock된 경우 unlock하고 중단 요소가 있는 경우 cc->contended에 COMPACT_CONTENDED_SCHED를 설정하고 true를 반환한다.

  • 중단 요소
    • SIGKILL 신호가 처리 지연된 경우
    • 비동기 migration 중에 높은 우선 순위의 태스크의 리스케쥴 요청이 있는 경우

 

compact_trylock_irqsave()

mm/compaction.c

/*
 * Compaction requires the taking of some coarse locks that are potentially
 * very heavily contended. For async compaction, back out if the lock cannot
 * be taken immediately. For sync compaction, spin on the lock if needed.
 *
 * Returns true if the lock is held
 * Returns false if the lock is not held and compaction should abort
 */
static bool compact_trylock_irqsave(spinlock_t *lock, unsigned long *flags,
                                                struct compact_control *cc)
{
        if (cc->mode == MIGRATE_ASYNC) {
                if (!spin_trylock_irqsave(lock, *flags)) {
                        cc->contended = COMPACT_CONTENDED_LOCK;
                        return false;
                }
        } else {
                spin_lock_irqsave(lock, *flags);
        }

        return true;
}

비동기 migration 처리 중 compaction에 사용된 spin lock이 다른 cpu와 경쟁하여 한 번에 획득 시도가 실패한 경우 cc->contended에 COMPACT_CONTENDED_LOCK(compaction 락 혼잡)을 대입하고 false로 반환한다.

  • 동기 migration인 경우 무조건 lock 획득을 한다.

 

mem_cgroup_page_lruvec()

mm/memcontrol.c

/**               
 * mem_cgroup_page_lruvec - return lruvec for isolating/putting an LRU page
 * @page: the page      
 * @zone: zone of the page
 *
 * This function is only safe when following the LRU page isolation
 * and putback protocol: the LRU lock must be held, and the page must
 * either be PageLRU() or the caller must have isolated/allocated it.
 */
struct lruvec *mem_cgroup_page_lruvec(struct page *page, struct zone *zone)
{
        struct mem_cgroup_per_zone *mz;
        struct mem_cgroup *memcg;
        struct lruvec *lruvec;

        if (mem_cgroup_disabled()) {
                lruvec = &zone->lruvec;
                goto out;
        }

        memcg = page->mem_cgroup;
        /*
         * Swapcache readahead pages are added to the LRU - and
         * possibly migrated - before they are charged.
         */
        if (!memcg)
                memcg = root_mem_cgroup;

        mz = mem_cgroup_page_zoneinfo(memcg, page);
        lruvec = &mz->lruvec;
out:
        /*
         * Since a node can be onlined after the mem_cgroup was created,
         * we have to be prepared to initialize lruvec->zone here;
         * and if offlined then reonlined, we need to reinitialize it.
         */
        if (unlikely(lruvec->zone != zone))
                lruvec->zone = zone;
        return lruvec;
}

memcg가 활성화 되어 있는 경우 해당 페이지에 기록된 memcg의 zone에서 lruvec을 반환하고, memcg가 비활성화 되어 있는 경우 zone의 lruvec를 반환한다.

 

__isolate_lru_page()

mm/vmscan.c

/*
 * Attempt to remove the specified page from its LRU.  Only take this page
 * if it is of the appropriate PageActive status.  Pages which are being
 * freed elsewhere are also ignored.
 *
 * page:        page to consider
 * mode:        one of the LRU isolation modes defined above
 *
 * returns 0 on success, -ve errno on failure.
 */
int __isolate_lru_page(struct page *page, isolate_mode_t mode)
{
        int ret = -EINVAL;

        /* Only take pages on the LRU. */
        if (!PageLRU(page))                     
                return ret;              

        /* Compaction should not handle unevictable pages but CMA can do so */
        if (PageUnevictable(page) && !(mode & ISOLATE_UNEVICTABLE))
                return ret;
        
        ret = -EBUSY;
        
        /*
         * To minimise LRU disruption, the caller can indicate that it only
         * wants to isolate pages it will be able to operate on without
         * blocking - clean pages for the most part.
         *
         * ISOLATE_CLEAN means that only clean pages should be isolated. This
         * is used by reclaim when it is cannot write to backing storage
         *
         * ISOLATE_ASYNC_MIGRATE is used to indicate that it only wants to pages
         * that it is possible to migrate without blocking
         */
        if (mode & (ISOLATE_CLEAN|ISOLATE_ASYNC_MIGRATE)) {
                /* All the caller can do on PageWriteback is block */
                if (PageWriteback(page))
                        return ret;
                
                if (PageDirty(page)) {
                        struct address_space *mapping;
                        
                        /* ISOLATE_CLEAN means only clean pages */
                        if (mode & ISOLATE_CLEAN)
                                return ret;

                        /*
                         * Only pages without mappings or that have a
                         * ->migratepage callback are possible to migrate
                         * without blocking
                         */
                        mapping = page_mapping(page);
                        if (mapping && !mapping->a_ops->migratepage)
                                return ret;
                }
        }

        if ((mode & ISOLATE_UNMAPPED) && page_mapped(page))
                return ret;

        if (likely(get_page_unless_zero(page))) {
                /*
                 * Be careful not to clear PageLRU until after we're
                 * sure the page is not being freed elsewhere -- the
                 * page release code relies on it.
                 */
                ClearPageLRU(page);
                ret = 0;
        }

        return ret;
}

페이지에서 LRU의 제거를 시도한다.

  • if (!PageLRU(page)) return ret;
    • LRU 페이지가 아닌 경우 처리를 중단한다.
  • if (PageUnevictable(page) && !(mode & ISOLATE_UNEVICTABLE)) return ret;
    • unevictable 페이지이면서 isolation 모드가 ISOLATE_UNEVICTABLE을 지원하지 않는 경우 처리를 중단한다.
  • if (mode & (ISOLATE_CLEAN|ISOLATE_ASYNC_MIGRATE)) {
    • 모드가 ISOLATE_CLEAN 또는 ISOLATE_ASYNC_MIGRATE을 지원하는 경우
  • if (PageWriteback(page)) return ret;
    • WriteBack 캐시를 사용하는 경우 -EBUSY로 처리를 중단한다.
  • if (PageDirty(page)) { if (mode & ISOLATE_CLEAN) return ret;
    • Dirty 설정된 페이지인 경우 isolation 모드에 ISOLATE_CLEAN 요청이 있으면 처리를 중단한다.
  • mapping = page_mapping(page); if (mapping && !mapping->a_ops->migratepage) return ret;
    • 매핑된 페이지인 경우 migratepage 핸들러 함수가 등록되어 있지 않은 경우 처리를 중단한다.
  • if ((mode & ISOLATE_UNMAPPED) && page_mapped(page)) return ret;
    • isolation 모드에 ISOLATE_UNMAPPED 요청이 있는 경우 매핑된 페이지는 처리를 중단한다.
  • if (likely(get_page_unless_zero(page))) { ClearPageLRU(page); ret = 0; }
    • 많은 확률로 참조 카운터가 0이 아니면 lru 플래그를 클리어한다.

 

update_pageblock_skip()

mm/compaction.c

/*
 * If no pages were isolated then mark this pageblock to be skipped in the
 * future. The information is later cleared by __reset_isolation_suitable().
 */
static void update_pageblock_skip(struct compact_control *cc,
                        struct page *page, unsigned long nr_isolated,
                        bool migrate_scanner)
{
        struct zone *zone = cc->zone;
        unsigned long pfn;

        if (cc->ignore_skip_hint)
                return;

        if (!page)
                return;

        if (nr_isolated)
                return;

        set_pageblock_skip(page);

        pfn = page_to_pfn(page);

        /* Update where async and sync compaction should restart */
        if (migrate_scanner) {
                if (pfn > zone->compact_cached_migrate_pfn[0])
                        zone->compact_cached_migrate_pfn[0] = pfn;
                if (cc->mode != MIGRATE_ASYNC &&
                    pfn > zone->compact_cached_migrate_pfn[1])
                        zone->compact_cached_migrate_pfn[1] = pfn;
        } else {
                if (pfn < zone->compact_cached_free_pfn)
                        zone->compact_cached_free_pfn = pfn;
        }
}

처리된 isolated 페이지가 없는 경우 해당 페이지 블럭에 migrate skip 비트를 설정하고 migrate 또는 free 스캐너의 시작 pfn을 해당 페이지로 설정한다.

  • if (cc->ignore_skip_hint) return; if (!page) return; if (nr_isolated) return;
    • ignore_skip_hint가 설정된 경우 또는 페이지가 없거나 isolated된 페이지가 없는 경우 해당 페이지 블럭의 skip 비트를 설정하지 않도록 중단한다.
  • set_pageblock_skip(page);
    • 해당 페이지 블럭의 skip 비트를 설정한다.
  • if (migrate_scanner) { if (pfn > zone->compact_cached_migrate_pfn[0]) zone->compact_cached_migrate_pfn[0] = pfn; if (cc->mode != MIGRATE_ASYNC && pfn > zone->compact_cached_migrate_pfn[1]) zone->compact_cached_migrate_pfn[1] = pfn;
    • migrate 스캐너 루틴에서 요청이 온 경우 migrate 스캔 시작 pfn을 갱신한다.
  • } else {  if (pfn < zone->compact_cached_free_pfn) zone->compact_cached_free_pfn = pfn; }
    • free 스캐너 루틴에서 요청이 온 경우 free 스캔 시작 pfn을 갱신한다.

 

acct_isolated()

mm/compaction.c

/* Update the number of anon and file isolated pages in the zone */
static void acct_isolated(struct zone *zone, struct compact_control *cc)
{
        struct page *page;
        unsigned int count[2] = { 0, };

        if (list_empty(&cc->migratepages))
                return;

        list_for_each_entry(page, &cc->migratepages, lru)
                count[!!page_is_file_cache(page)]++;

        mod_zone_page_state(zone, NR_ISOLATED_ANON, count[0]);
        mod_zone_page_state(zone, NR_ISOLATED_FILE, count[1]);
}

migrate 페이지 리스트에서 anon과 file 페이지에 대한 stat을 추가한다.

 

Isolation 취소

putback_movable_pages()

mm/migrate.c

/*
 * Put previously isolated pages back onto the appropriate lists
 * from where they were once taken off for compaction/migration.
 *
 * This function shall be used whenever the isolated pageset has been
 * built from lru, balloon, hugetlbfs page. See isolate_migratepages_range()
 * and isolate_huge_page().
 */
void putback_movable_pages(struct list_head *l)
{
        struct page *page;
        struct page *page2;

        list_for_each_entry_safe(page, page2, l, lru) {
                if (unlikely(PageHuge(page))) {
                        putback_active_hugepage(page);
                        continue;
                }
                list_del(&page->lru);
                dec_zone_page_state(page, NR_ISOLATED_ANON +
                                page_is_file_cache(page));
                if (unlikely(isolated_balloon_page(page)))
                        balloon_page_putback(page);
                else
                        putback_lru_page(page);
        }
}

기존에 isolation된 페이지들을 다시 원래의 위치로 되돌린다.

  • list_for_each_entry_safe(page, page2, l, lru) {
    • 리스트에 있는 페이지들 만큼 루프를 돈다.
  • if (unlikely(PageHuge(page))) { putback_active_hugepage(page); continue; }
    • 적은 확률로 huge 페이지인 경우 hstate[].hugepage_activelist의 후미로 이동시키고 skip 한다.
      • huge page는 hstate[]에서 관리한다.
  • dec_zone_page_state(page, NR_ISOLATED_ANON + page_is_file_cache(page));
    • 페이지의 타입에 따라 NR_ISOLATE_ANON 또는 NR_ISOLATED_FILE stat을 감소시킨다.
  • if (unlikely(isolated_balloon_page(page))) balloon_page_putback(page);
    • 적은 확률로 balloon 페이지인 경우 balloon_dev_info의 pages 리스트에 되돌린다.
      • balloon page는 balloon 디바이스에서 관리한다.
  • else putback_lru_page(page);
    • 페이지를 lurvec.lists[]에 되돌린다.

 

LRU 페이지 관리

참고: UNEVICTABLE LRU INFRASTRUCTURE | kernel.org

putback_lru_page()

mm/vmscan.c

/**
 * putback_lru_page - put previously isolated page onto appropriate LRU list
 * @page: page to be put back to appropriate lru list
 *
 * Add previously isolated @page to appropriate LRU list.
 * Page may still be unevictable for other reasons.
 *
 * lru_lock must not be held, interrupts must be enabled.
 */
void putback_lru_page(struct page *page)
{
        bool is_unevictable;
        int was_unevictable = PageUnevictable(page);

        VM_BUG_ON_PAGE(PageLRU(page), page);

redo:
        ClearPageUnevictable(page);

        if (page_evictable(page)) {
                /*
                 * For evictable pages, we can use the cache.
                 * In event of a race, worst case is we end up with an
                 * unevictable page on [in]active list.
                 * We know how to handle that.
                 */
                is_unevictable = false;
                lru_cache_add(page);
        } else {
                /*
                 * Put unevictable pages directly on zone's unevictable
                 * list.
                 */
                is_unevictable = true;
                add_page_to_unevictable_list(page);
                /*
                 * When racing with an mlock or AS_UNEVICTABLE clearing
                 * (page is unlocked) make sure that if the other thread
                 * does not observe our setting of PG_lru and fails
                 * isolation/check_move_unevictable_pages,
                 * we see PG_mlocked/AS_UNEVICTABLE cleared below and move
                 * the page back to the evictable list.
                 *
                 * The other side is TestClearPageMlocked() or shmem_lock().
                 */
                smp_mb();
        }

        /*
         * page's status can change while we move it among lru. If an evictable
         * page is on unevictable list, it never be freed. To avoid that,
         * check after we added it to the list, again.
         */
        if (is_unevictable && page_evictable(page)) {
                if (!isolate_lru_page(page)) {
                        put_page(page);
                        goto redo;
                }
                /* This means someone else dropped this page from LRU
                 * So, it will be freed or putback to LRU again. There is
                 * nothing to do here.
                 */
        }

        if (was_unevictable && !is_unevictable)
                count_vm_event(UNEVICTABLE_PGRESCUED);
        else if (!was_unevictable && is_unevictable)
                count_vm_event(UNEVICTABLE_PGCULLED);

        put_page(page);         /* drop ref from isolate */
}

isolation되었던 페이지를 다시 lruvec에 되돌린다.

 

  • int was_unevictable = PageUnevictable(page);
    • 페이지가 unevictable 상태인지 여부를 알아온다.
  • ClearPageUnevictable(page);
    • 페이지의 PG_unevictable 플래그를 클리어한다.
  • if (page_evictable(page)) { is_unevictable = false; lru_cache_add(page);
    • 페이지 매핑 상태를 보아 evictable 상태인 경우 is_unevictable에 false를 담고 페이지를 lru_add_pvec 캐시에 등록한다.
  • } else { is_unevictable = true; add_page_to_unevictable_list(page); smp_mb(); }
    • lruvec.list[LRU_UNEVICTABLE]에 페이지를 추가한다.
  • if (is_unevictable && page_evictable(page)) { if (!isolate_lru_page(page)) { put_page(page); goto redo; } }
    • lruvec.list[LRU_UNEVICTABLE]에 추가한 페이지가 evictable 상태로 바뀐 경우 이 페이지는 절대 free 되지 않는다. 이를 피하기 위해 다시 한 번 이 페이지를 isolation 하여 체크하게 반복한다.
  • if (was_unevictable && !is_unevictable) count_vm_event(UNEVICTABLE_PGRESCUED);
    • unevictable 이었으면서 지금은 unevictable이 아닌 경우 UNEVICTABLE_PGRESCUED stat을 증가시킨다.
  • else if (!was_unevictable && is_unevictable) count_vm_event(UNEVICTABLE_PGCULLED);
    • unevictable 이 아니었으면서 지금은 unevictable인 경우 UNEVICTABLE_PG CULLED stat을 증가시킨다.
  • put_page(page);
    • 페이지에서 LRU 비트 플래그를 클리어하고  lru 리스트에서 제거하며 버디 시스템에 페이지를 hot 방향으로 free한다.

 

Huge Page & Huge TLB

  • Huge TLB를 지원하는 아키텍처에서만 사용할 수 있다.
    • x86, ia64, arm with LPAE, sparc64, s390 등에서 사용할 수 있다.
    • 참고: hugetlbpage.txt | kernel.org
  • Huge TLB를 사용하는 경우 큰 페이지를 하나의 TLB 엔트리로 로드하여 사용하므로 매핑에 대한 overhead가 줄어들어 빠른 access 성능을 유지할 수 있게된다.
  • Huge TLB를 사용하는 경우 TLB H/W의 성능 향상을 위해 페이지 블럭을 MAX_ORDER-1 페이지 단위가 아닌 HugeTLB 단위에 맞게 운용할 수 있다.
  • 전역 hstate[]는 배열로 구성되어 size가 다른 여러 개의 TLB 엔트리를 구성하여 사용할 수 있다.
  • 커널 파라메터를 사용하여 지정된 크기의 공간을 reserve 하여 사용한다.
    • 예) “default_hugepagesz=1G hugepagesz=1G”
  • 런타임 시 설정 변경
    • “/proc/sys/vm/nr_hugepages” 이며 NUMA 시스템에서는  “/sys/devices/system/node/node_id/hugepages/hugepages”을 설정하여 사용한다.
  • shared 메모리를 open 하여 만들 때 SHM_HUGETLB  옵션을 사용하여 huge tlb를 사용하게 할 수 있다.
    • 예) shmid = shmget(2, LENGTH, SHM_HUGETLB | IPC_CREAT | SHM_R | SHM_W)) < 0)

 

HugtTLBFS

  • 파일 시스템과 같이 동작하므로 마운트하여 사용한다.
    • 예) mount -t hugetlbfs -o uid=<value>,gid=<value>,mode=<value>,size=<value>,nr_inodes=<value> none /mnt/huge
  • 마운트된 디렉토리(/mnt/huge)내에서 만들어진 파일들은 huge tlb를 사용하여 매핑된다.

 

putback_active_hugepage()

mm/hugetlb.c

void putback_active_hugepage(struct page *page)
{                                       
        VM_BUG_ON_PAGE(!PageHead(page), page);
        spin_lock(&hugetlb_lock);
        list_move_tail(&page->lru, &(page_hstate(page))->hugepage_activelist);
        spin_unlock(&hugetlb_lock);
        put_page(page);
}

isolation되었던 페이지를 전역 hstate[]의 hugepage_activelist의 후미에 다시 되돌린다.

  • isolation때 증가시킨 참조 카운터를 감소 시킨다.

 

Balloon 페이지 관리

  • 리눅스는 KVM 및 GEN과 같은 가상 머신을 위한 Balloon 디바이스 드라이버를 제공한다.
  • 메모리 파편화를 막기위해 Balloon 메모리 compaction을 지원한다.

 

balloon_page_putback()

mm/balloon_compaction.c

/* putback_lru_page() counterpart for a ballooned page */
void balloon_page_putback(struct page *page)
{
        /*
         * 'lock_page()' stabilizes the page and prevents races against
         * concurrent isolation threads attempting to re-isolate it.
         */
        lock_page(page);

        if (__is_movable_balloon_page(page)) {
                __putback_balloon_page(page);
                /* drop the extra ref count taken for page isolation */
                put_page(page);
        } else {
                WARN_ON(1);
                dump_page(page, "not movable balloon page");
        }
        unlock_page(page);
}

isolation되었던 페이지가 ballon 페이지인 경우 페이지에 기록된 ballon 디바이스의 pages 리스트에 다시 되돌린다.

  • isolation때 증가시킨 참조 카운터를 감소 시킨다.

 

__is_movable_balloon_page()

include/linux/balloon_compaction.h

/*
 * __is_movable_balloon_page - helper to perform @page PageBalloon tests
 */             
static inline bool __is_movable_balloon_page(struct page *page)
{
        return PageBalloon(page);
}

Ballon 페이지 여부를 반환한다.

 

__putback_balloon_page()

mm/balloon_compaction.c

static inline void __putback_balloon_page(struct page *page)
{
        struct balloon_dev_info *b_dev_info = balloon_page_device(page);
        unsigned long flags;

        spin_lock_irqsave(&b_dev_info->pages_lock, flags);
        SetPagePrivate(page);
        list_add(&page->lru, &b_dev_info->pages);
        b_dev_info->isolated_pages--;
        spin_unlock_irqrestore(&b_dev_info->pages_lock, flags);
}

페이지에 PG_private 플래그를 설정하고 페이지에 기록된 ballon 페이지 디바이스의 pages 리스트에 되돌린다.

 

balloon_page_device()

include/linux/balloon_compaction.h

/*
 * balloon_page_device - get the b_dev_info descriptor for the balloon device
 *                       that enqueues the given page.
 */
static inline struct balloon_dev_info *balloon_page_device(struct page *page)
{
        return (struct balloon_dev_info *)page_private(page);
}

ballon 페이지 디바이스를 알아온다.

 

Isolation Freepages

Get new page

compaction_alloc()

mm/compaction.c

/*
 * This is a migrate-callback that "allocates" freepages by taking pages
 * from the isolated freelists in the block we are migrating to.
 */
static struct page *compaction_alloc(struct page *migratepage,
                                        unsigned long data,
                                        int **result)
{
        struct compact_control *cc = (struct compact_control *)data;
        struct page *freepage;

        /*
         * Isolate free pages if necessary, and if we are not aborting due to
         * contention.
         */
        if (list_empty(&cc->freepages)) {
                if (!cc->contended)
                        isolate_freepages(cc);

                if (list_empty(&cc->freepages))
                        return NULL;
        }

        freepage = list_entry(cc->freepages.next, struct page, lru);
        list_del(&freepage->lru);
        cc->nr_freepages--;

        return freepage;
}

free 스캐너로부터 isolation된 cc->freepages 리스트에서 선두의 free 페이지를 반환한다. 만일 반환할 free 페이지가 없으면 free 스캐너를 가동한다. 실패한 경우 null을 반환한다.

 

/*
 * Based on information in the current compact_control, find blocks
 * suitable for isolating free pages from and then isolate them.
 */
static void isolate_freepages(struct compact_control *cc)
{
        struct zone *zone = cc->zone;
        struct page *page;
        unsigned long block_start_pfn;  /* start of current pageblock */
        unsigned long isolate_start_pfn; /* exact pfn we start at */
        unsigned long block_end_pfn;    /* end of current pageblock */
        unsigned long low_pfn;       /* lowest pfn scanner is able to scan */
        struct list_head *freelist = &cc->freepages;

        /*
         * Initialise the free scanner. The starting point is where we last
         * successfully isolated from, zone-cached value, or the end of the
         * zone when isolating for the first time. For looping we also need
         * this pfn aligned down to the pageblock boundary, because we do
         * block_start_pfn -= pageblock_nr_pages in the for loop.
         * For ending point, take care when isolating in last pageblock of a
         * a zone which ends in the middle of a pageblock.
         * The low boundary is the end of the pageblock the migration scanner
         * is using.
         */
        isolate_start_pfn = cc->free_pfn;
        block_start_pfn = cc->free_pfn & ~(pageblock_nr_pages-1);
        block_end_pfn = min(block_start_pfn + pageblock_nr_pages,
                                                zone_end_pfn(zone));
        low_pfn = ALIGN(cc->migrate_pfn + 1, pageblock_nr_pages);
  • isolate_start_pfn = cc->free_pfn;
    • free 스캐너의 시작 pfn을 위해 중단되었었던 cc->free_pfn 부터 시작하게 한다. 만일 처음 인 경우 zone의 마지막 pfn 부터 시작한다.
      • free 스캐너는 페이지 블럭단위로 zone의 시작 부분으로 cc->migrate_pfn 까지 이동 하면서 스캔한다.
  • block_start_pfn = cc->free_pfn & ~(pageblock_nr_pages-1);
    • 블럭 시작 pfn으로 cc->free_pfn을 페이지 블럭단위로 round down한 값을 지정한다.
  • block_end_pfn = min(block_start_pfn + pageblock_nr_pages, zone_end_pfn(zone));
    • 한 블럭 이내로 블럭 끝 pfn을 지정한다.
  • low_pfn = ALIGN(cc->migrate_pfn + 1, pageblock_nr_pages);
    • migrate 스캐너가 작업하는 블럭을 침범하지 않게 하기 위해 low_pfn으로 cc->migrate_pfn+1을 페이지 블럭 단위로 정렬한 pfn 값을 대입한다.

 

        /*
         * Isolate free pages until enough are available to migrate the
         * pages on cc->migratepages. We stop searching if the migrate
         * and free page scanners meet or enough free pages are isolated.
         */
        for (; block_start_pfn >= low_pfn &&
                        cc->nr_migratepages > cc->nr_freepages;
                                block_end_pfn = block_start_pfn,
                                block_start_pfn -= pageblock_nr_pages,
                                isolate_start_pfn = block_start_pfn) {

                /*
                 * This can iterate a massively long zone without finding any
                 * suitable migration targets, so periodically check if we need
                 * to schedule, or even abort async compaction.
                 */
                if (!(block_start_pfn % (SWAP_CLUSTER_MAX * pageblock_nr_pages))
                                                && compact_should_abort(cc))
                        break;

                page = pageblock_pfn_to_page(block_start_pfn, block_end_pfn,
                                                                        zone);
                if (!page)
                        continue;

                /* Check the block is suitable for migration */
                if (!suitable_migration_target(page))
                        continue;

                /* If isolation recently failed, do not retry */
                if (!isolation_suitable(cc, page))
                        continue;

                /* Found a block suitable for isolating free pages from. */
                isolate_freepages_block(cc, &isolate_start_pfn,
                                        block_end_pfn, freelist, false);
  • for (; block_start_pfn >= low_pfn && cc->nr_migratepages > cc->nr_freepages; block_end_pfn = block_start_pfn, block_start_pfn -= pageblock_nr_pages, isolate_start_pfn = block_start_pfn) {
    • free 스캐너는 low_pfn까지 페이지 블럭 단위로 감소하며 루프를 돈다.
  • if (!(block_start_pfn % (SWAP_CLUSTER_MAX * pageblock_nr_pages)) && compact_should_abort(cc)) break;
    • zone을 free 스캐너로 한꺼번에 isolation하는 경우 영역이 너무 커서 중간에 주기적으로 한 번씩 compaction 중단 요소가 있는지 확인한다.
      • 체크 주기
        • SWAP_CLUSTER_MAX(32) 페이지 블럭 단위로 체크한다.
      • 중단 요소
        • 비동기 compaction 처리 중이면서 우선 순위 높은 태스크의 리스케쥴 요청이 있는 경우
  • page = pageblock_pfn_to_page(block_start_pfn, block_end_pfn, zone); if (!page) continue;
    • 페이지 블럭의 첫 페이지를 가져온다. pfn 범위가 요청한 zone이 아닌 경우 skip 한다.
      • zone의 마지막 페이지블럭이 partial인 경우 skip 한다
  • if (!suitable_migration_target(page)) continue;
    • page가 migration에 적합한 블럭에 있는지 여부를 반환한다.
  • if (!isolation_suitable(cc, page)) continue;
    • 해당 zone과 블럭에서 isolation을 실행해도 되는지 여부를 체크한다.
      • 최근에 해당 페이지블럭에서 isolation이 취소된적이 있는 경우 skip하도록 false를 반환한다.
  • isolate_freepages_block(cc, &isolate_start_pfn, block_end_pfn, freelist, false);
    • 해당 페이지블럭을 cc->freepages 리스트에 isolation 한다.

 

                /*
                 * Remember where the free scanner should restart next time,
                 * which is where isolate_freepages_block() left off.
                 * But if it scanned the whole pageblock, isolate_start_pfn
                 * now points at block_end_pfn, which is the start of the next
                 * pageblock.
                 * In that case we will however want to restart at the start
                 * of the previous pageblock.
                 */
                cc->free_pfn = (isolate_start_pfn < block_end_pfn) ?
                                isolate_start_pfn :
                                block_start_pfn - pageblock_nr_pages;

                /*
                 * isolate_freepages_block() might have aborted due to async
                 * compaction being contended
                 */
                if (cc->contended)
                        break;
        }

        /* split_free_page does not map the pages */
        map_pages(freelist);

        /*
         * If we crossed the migrate scanner, we want to keep it that way
         * so that compact_finished() may detect this
         */
        if (block_start_pfn < low_pfn)
                cc->free_pfn = cc->migrate_pfn;
}
  • cc->free_pfn = (isolate_start_pfn < block_end_pfn) ? isolate_start_pfn : block_start_pfn – pageblock_nr_pages;
    • 다음에 중단된 pfn 부터 계속 처리하기 위해 백업한다.
  • if (cc->contended) break;
    • 비동기 migration 중 혼잡이 발생하는 경우 처리를 중단하기 위해 루프를 벗어난다.
  • map_pages(freelist);
  • if (block_start_pfn < low_pfn) cc->free_pfn = cc->migrate_pfn;
    • cc->free_pfn이 migrate 스캐너보다 낮아지지 않게 조절한다.

 

suitable_migration_target()

mm/compaction.c

/* Returns true if the page is within a block suitable for migration to */
static bool suitable_migration_target(struct page *page)
{
        /* If the page is a large free page, then disallow migration */
        if (PageBuddy(page)) {
                /*
                 * We are checking page_order without zone->lock taken. But
                 * the only small danger is that we skip a potentially suitable
                 * pageblock, so it's not worth to check order for valid range.
                 */
                if (page_order_unsafe(page) >= pageblock_order)
                        return false;
        }

        /* If the block is MIGRATE_MOVABLE or MIGRATE_CMA, allow migration */
        if (migrate_async_suitable(get_pageblock_migratetype(page)))
                return true;

        /* Otherwise skip the block */
        return false;
}

page가 migration에 적합한 블럭에 있는지 여부를 반환한다.

  • migrate 타입이 MIGRATE_CMA 또는 MIGRATE_MOVABLE인 경우 true를 반환한다. 단 Buddy 페이지이면서 page_order가 pageblock_order 보다 큰 경우 false를 반환한다.

 

isolate_freepages_block()

mm/compaction.c

/*
 * Isolate free pages onto a private freelist. If @strict is true, will abort
 * returning 0 on any invalid PFNs or non-free pages inside of the pageblock
 * (even though it may still end up isolating some pages).
 */
static unsigned long isolate_freepages_block(struct compact_control *cc,
                                unsigned long *start_pfn,
                                unsigned long end_pfn,
                                struct list_head *freelist,
                                bool strict)
{
        int nr_scanned = 0, total_isolated = 0;
        struct page *cursor, *valid_page = NULL;
        unsigned long flags = 0;
        bool locked = false;
        unsigned long blockpfn = *start_pfn;

        cursor = pfn_to_page(blockpfn);

        /* Isolate free pages. */
        for (; blockpfn < end_pfn; blockpfn++, cursor++) {
                int isolated, i;
                struct page *page = cursor;

                /*
                 * Periodically drop the lock (if held) regardless of its
                 * contention, to give chance to IRQs. Abort if fatal signal
                 * pending or async compaction detects need_resched()
                 */
                if (!(blockpfn % SWAP_CLUSTER_MAX)
                    && compact_unlock_should_abort(&cc->zone->lock, flags,
                                                                &locked, cc))
                        break;

                nr_scanned++;
                if (!pfn_valid_within(blockpfn))
                        goto isolate_fail;

                if (!valid_page)
                        valid_page = page;
                if (!PageBuddy(page))
                        goto isolate_fail;

 

  • int nr_scanned = 0, total_isolated = 0;
    • 스캔 페이지 수와 isolated 페이지 수를 0으로 초기화한다.
  • unsigned long blockpfn = *start_pfn; cursor = pfn_to_page(blockpfn);
    • 시작 pfn에 해당하는 page를 cursor에 대입한다.
  • for (; blockpfn < end_pfn; blockpfn++, cursor++) {
    • start_pfn ~ end_pfn 까지 루프를 돈다.
  • if (!(blockpfn % SWAP_CLUSTER_MAX) && compact_unlock_should_abort(&cc->zone->lock, flags, &locked, cc)) break;
    • 한 block을 스캔하는 동안 처리할 범위가 크므로 주기적으로 lock된 경우 unlock을 하고 중단 요소가 있는 경우 cc->contended에 COMPACT_CONTENDED_SCHED를 설정하고 루프를 빠져나간다.
      • 체크 주기
        • SWAP_CLUSTER_MAX(32) 페이지 블럭단위
      •  중단요소
        • SIGKILL 신호가 처리 지연된 경우
        • 비동기 migration 중에 높은 우선 순위의 태스크의 리스케쥴 요청이 있는 경우
  • if (!pfn_valid_within(blockpfn)) goto isolate_fail;
    • 가용 메모리 페이지가 아닌 경우 skip
  • if (!valid_page) valid_page = page;
    • valid_page에 기록된 적이 없으면 현재 처리하는 page로 update한다.
  • if (!PageBuddy(page)) goto isolate_fail;
    • Buddy 페이지가 아니면 skip

 

                /*
                 * If we already hold the lock, we can skip some rechecking.
                 * Note that if we hold the lock now, checked_pageblock was
                 * already set in some previous iteration (or strict is true),
                 * so it is correct to skip the suitable migration target
                 * recheck as well.
                 */
                if (!locked) {
                        /*
                         * The zone lock must be held to isolate freepages.
                         * Unfortunately this is a very coarse lock and can be
                         * heavily contended if there are parallel allocations
                         * or parallel compactions. For async compaction do not
                         * spin on the lock and we acquire the lock as late as
                         * possible.
                         */
                        locked = compact_trylock_irqsave(&cc->zone->lock,
                                                                &flags, cc);
                        if (!locked)
                                break;

                        /* Recheck this is a buddy page under lock */
                        if (!PageBuddy(page))
                                goto isolate_fail;
                }

                /* Found a free page, break it into order-0 pages */
                isolated = split_free_page(page);
                total_isolated += isolated;
                for (i = 0; i < isolated; i++) {
                        list_add(&page->lru, freelist);
                        page++;
                }

                /* If a page was split, advance to the end of it */
                if (isolated) {
                        cc->nr_freepages += isolated;
                        if (!strict &&
                                cc->nr_migratepages <= cc->nr_freepages) {
                                blockpfn += isolated;
                                break;
                        }

                        blockpfn += isolated - 1;
                        cursor += isolated - 1;
                        continue;
                }
  • if (!locked) { locked = compact_trylock_irqsave(&cc->zone->lock, &flags, cc); if (!locked) break;
    • 만일 lock이 걸려있지 않은 경우 lock을 획득한다. 획득이 실패한 경우 처리를 중단한다.
  • if (!PageBuddy(page)) goto isolate_fail; }
    • 다시 한 번 더 체크하여 Buddy 페이지가 아니면 skip
  • isolated = split_free_page(page);
    • 버디 시스템에서 free 페이지를 분리해 온다.
  • for (i = 0; i < isolated; i++) { list_add(&page->lru, freelist); page++; }
    • 분리한 order-0 free 페이지들 만큼 cc->freepages로 옮긴다.
  • if (isolated) { cc->nr_freepages += isolated; if (!strict && cc->nr_migratepages <= cc->nr_freepages) { blockpfn += isolated; break; }
    • isolated 페이지가 있는 경우 cc->nr_freepages를 그만큼 증가시키고 만일 strict가 false이면서 migrate 스캐너가 isolation한 페이지 수를 처리할 만큼의 free 페이지가 있는 경우 blockpfn에 isolated 만큼 증가시키고 루프를 탈출한다.
  • blockpfn += isolated – 1; cursor += isolated – 1; continue; }
    • blockpfn 및 cursor에 isolated-1 만큼 증가시키고 루프를 계속한다.
  • isolate_fail: if (strict) break; else continue; }
    • 인수 strict가 tue인 경우 처리를 중단하고 그렇지 않은 경우 루프를 계속한다.

 

isolate_fail:
                if (strict)
                        break;
                else
                        continue;

        }

        trace_mm_compaction_isolate_freepages(*start_pfn, blockpfn,
                                        nr_scanned, total_isolated);

        /* Record how far we have got within the block */
        *start_pfn = blockpfn;

        /*
         * If strict isolation is requested by CMA then check that all the
         * pages requested were isolated. If there were any failures, 0 is
         * returned and CMA will fail.
         */
        if (strict && blockpfn < end_pfn)
                total_isolated = 0;

        if (locked)
                spin_unlock_irqrestore(&cc->zone->lock, flags);

        /* Update the pageblock-skip if the whole pageblock was scanned */
        if (blockpfn == end_pfn)
                update_pageblock_skip(cc, valid_page, total_isolated, false);

        count_compact_events(COMPACTFREE_SCANNED, nr_scanned);
        if (total_isolated)
                count_compact_events(COMPACTISOLATED, total_isolated);
        return total_isolated;
}
  • if (strict && blockpfn < end_pfn) total_isolated = 0;
    • strict isolation은 CMA에서 요청되는데 한 번이라도 실패하는 경우 실패로 0을 반환한다.
  • if (blockpfn == end_pfn) update_pageblock_skip(cc, valid_page, total_isolated, false);
    • 전체 페이지 블럭을 스캔하였고 처리된 isolated 페이지가 없는 경우 valid_page에 해당하는 페이지 블럭의 migrate_skip 비트를 설정하고 free 스캐너의 시작 pfn을 해당 페이지로 설정한다.
      • free 스캐너 시작 pfn
        • compact_cached_free_pfn
  • count_compact_events(COMPACTFREE_SCANNED, nr_scanned);
    • COMPACTFREE_SCANNED stat을 nr_scanned 수 만큼 증가시킨다.
  • if (total_isolated) count_compact_events(COMPACTISOLATED, total_isolated);
    • isolate된 수 만큼 COMPACTISOLATED stat을 증가시킨다.

 

split_free_page()

mm/page_alloc.c

/*
 * Similar to split_page except the page is already free. As this is only
 * being used for migration, the migratetype of the block also changes.
 * As this is called with interrupts disabled, the caller is responsible
 * for calling arch_alloc_page() and kernel_map_page() after interrupts
 * are enabled.
 *
 * Note: this is probably too low level an operation for use in drivers.
 * Please consult with lkml before using this in your driver.
 */
int split_free_page(struct page *page)
{
        unsigned int order;
        int nr_pages;

        order = page_order(page);

        nr_pages = __isolate_free_page(page, order);
        if (!nr_pages)
                return 0;

        /* Split into individual pages */
        set_page_refcounted(page);
        split_page(page, order);
        return nr_pages;
}

order page가 free page인 경우 order-0 free page로 분해하여 반환한다.

 

__isolatet_free_page()

mm/page_alloc.c

int __isolate_free_page(struct page *page, unsigned int order)
{
        unsigned long watermark;
        struct zone *zone;
        int mt;

        BUG_ON(!PageBuddy(page));

        zone = page_zone(page);
        mt = get_pageblock_migratetype(page);

        if (!is_migrate_isolate(mt)) {
                /* Obey watermarks as if the page was being allocated */
                watermark = low_wmark_pages(zone) + (1 << order);
                if (!zone_watermark_ok(zone, 0, watermark, 0, 0))
                        return 0;

                __mod_zone_freepage_state(zone, -(1UL << order), mt);
        }

        /* Remove page from free list */
        list_del(&page->lru);
        zone->free_area[order].nr_free--;
        rmv_page_order(page);

        /* Set the pageblock if the isolated page is at least a pageblock */
        if (order >= pageblock_order - 1) {
                struct page *endpage = page + (1 << order) - 1;
                for (; page < endpage; page += pageblock_nr_pages) {
                        int mt = get_pageblock_migratetype(page);
                        if (!is_migrate_isolate(mt) && !is_migrate_cma(mt))
                                set_pageblock_migratetype(page,
                                                          MIGRATE_MOVABLE);
                }
        }

        set_page_owner(page, order, 0);
        return 1UL << order;
}

order page가 free page인 경우 order-0 free page로 분해하여 반환한다.

  • zone = page_zone(page); mt = get_pageblock_migratetype(page);
    • 페이지에 해당하는 zone과 migrate 타입을 알아온다.
  • if (!is_migrate_isolate(mt)) {
    • isolation이 불가능한 migrate 타입인 경우
  • watermark = low_wmark_pages(zone) + (1 << order); if (!zone_watermark_ok(zone, 0, watermark, 0, 0)) return 0;
    • order 0로 low 워터마크 + order 페이지 수 값으로도 워터마크 경계를 ok하지 못한 경우 실패로 0을 반환한다.
  • __mod_zone_freepage_state(zone, -(1UL << order), mt); }
    • freepage 수를 order page 수 만큼 감소시킨다.
  • list_del(&page->lru); zone->free_area[order].nr_free–; rmv_page_order(page);
    • 버디 시스템에서 page를 제거하고 페이지의 buddy 설정과 private에 0을 대입한다.
  • if (order >= pageblock_order – 1) { struct page *endpage = page + (1 << order) – 1;
    • order가 페이지 블럭 order-1 보다 큰 경우 endpage를 구한다.
  • for (; page < endpage; page += pageblock_nr_pages) { int mt = get_pageblock_migratetype(page); if (!is_migrate_isolate(mt) && !is_migrate_cma(mt)) set_pageblock_migratetype(page, MIGRATE_MOVABLE); }
    • endpage만큼 페이지 블럭 단위만큼 증분하며 루프를 돌면서 페이지 블럭이 isolate되지 못하는 타입이면서 cma 타입이 아닌 경우 페이지 블럭의 타입을 MIGRATE_MOVABLE로 설정한다.
  • set_page_owner(page, order, 0);
    • 페이지에 owner를 설정한다.

 

split_page()

mm/page_alloc.c

/*
 * split_page takes a non-compound higher-order page, and splits it into
 * n (1<<order) sub-pages: page[0..n]
 * Each sub-page must be freed individually.
 *
 * Note: this is probably too low level an operation for use in drivers.
 * Please consult with lkml before using this in your driver.
 */
void split_page(struct page *page, unsigned int order)
{
        int i;

        VM_BUG_ON_PAGE(PageCompound(page), page);
        VM_BUG_ON_PAGE(!page_count(page), page);

#ifdef CONFIG_KMEMCHECK
        /*
         * Split shadow pages too, because free(page[0]) would
         * otherwise free the whole shadow.
         */
        if (kmemcheck_page_is_tracked(page))
                split_page(virt_to_page(page[0].shadow), order);
#endif

        set_page_owner(page, 0, 0);
        for (i = 1; i < (1 << order); i++) {
                set_page_refcounted(page + i);
                set_page_owner(page + i, 0, 0);
        }
}
EXPORT_SYMBOL_GPL(split_page);

페이지에 owner와 order를 설정하고, oreder 페이지의 각 페이지에 대해 참조 카운터를  1로 그리고 owner와 0-order를 설정한다.

 

Put new page

compaction_free()

mm/compaction.c

/*
 * This is a migrate-callback that "frees" freepages back to the isolated
 * freelist.  All pages on the freelist are from the same zone, so there is no
 * special handling needed for NUMA.
 */
static void compaction_free(struct page *page, unsigned long data)
{
        struct compact_control *cc = (struct compact_control *)data;

        list_add(&page->lru, &cc->freepages);
        cc->nr_freepages++;
}

페이지를 다시 cc->freepages 리스트에 추가한다.

 

release_freepages()

mm/compaction.c

static unsigned long release_freepages(struct list_head *freelist)
{
        struct page *page, *next;
        unsigned long high_pfn = 0;

        list_for_each_entry_safe(page, next, freelist, lru) {
                unsigned long pfn = page_to_pfn(page);
                list_del(&page->lru);
                __free_page(page);
                if (pfn > high_pfn)
                        high_pfn = pfn;
        }

        return high_pfn;
}

freelist에 있는 페이지들을 제거하고 모두 해제하고 가장 큰 pfn 값을 반환한다.

 

참고

답글 남기기

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