Zoned Allocator -9- (Direct Compact-Migration)

<kernel v5.0>

Migration

 

non-movable 페이지의 migration 지원

커널은 전통적으로 movable LRU 페이지에 대해서만 migration을 지원해왔다. 최근 임베디드 시스템인 WbOS, android 등에서 많은 수의 non-movable 페이지들을 사용하여 왔다. 이러한 non-movable 페이지들이 많이 사용되면서 high order 할당에 문제가 생기는 리포트들이 보고되어왔다. 따라서 이러한 문제점들을 제거하기 위해 몇개의 노력을 해왔었지만 (예를 들면 압축 알고리즘 개선, slub fallback 시 0 order 할당, reserved 메모리, vmalloc, …) 여전히 non-movable 페이지들이 많이 사용되면 장기적으로는 효과가 없었다.

 

이 번에는 non-movable 페이지들을 movable이 가능하도록 지원하기 위해 아래 패치를 통해 드라이버(zram, GPU memory, …)들에 (*isolate_page), (*migratepage) 등의 후크 함수를 구현할 수 있도록 하였다. 이러한 후크 함수가 지원되는 드라이버의 경우 커널이 non-movable로 분류할지라도 이러한 드라이버를 통해서 migration 할 수 있게 하였다.

 

다음 그림은 페이지 유형별 isolation 및 migration 지원 여부를 보여준다.

 

migrate_pages()

mm/migrate.c

/*
 * migrate_pages - migrate the pages specified in a list, to the free pages
 *                 supplied as the target for the page migration
 *
 * @from:               The list of pages to be migrated.
 * @get_new_page:       The function used to allocate free pages to be used
 *                      as the target of the page migration.
 * @put_new_page:       The function used to free target pages if migration
 *                      fails, or NULL if no special handling is necessary.
 * @private:            Private data to be passed on to get_new_page()
 * @mode:               The migration mode that specifies the constraints for
 *                      page migration, if any.
 * @reason:             The reason for page migration.
 *
 * The function returns after 10 attempts or if no pages are movable any more
 * because the list has become empty or no retryable pages exist any more.
 * The caller should call putback_movable_pages() to return pages to the LRU
 * or free list only if ret != 0.
 *
 * Returns the number of pages that were not migrated, or an error code.
 */
int migrate_pages(struct list_head *from, new_page_t get_new_page,
                free_page_t put_new_page, unsigned long private,
                enum migrate_mode mode, int reason)
{
        int retry = 1;
        int nr_failed = 0;
        int nr_succeeded = 0;
        int pass = 0;
        struct page *page;
        struct page *page2;
        int swapwrite = current->flags & PF_SWAPWRITE;
        int rc;

        if (!swapwrite)
                current->flags |= PF_SWAPWRITE;

        for(pass = 0; pass < 10 && retry; pass++) {
                retry = 0;

                list_for_each_entry_safe(page, page2, from, lru) {
retry:
                        cond_resched();

                        if (PageHuge(page))
                                rc = unmap_and_move_huge_page(get_new_page,
                                                put_new_page, private, page,
                                                pass > 2, mode, reason);
                        else
                                rc = unmap_and_move(get_new_page, put_new_page,
                                                private, page, pass > 2, mode,
                                                reason);

                        switch(rc) {
                        case -ENOMEM:
                                /*
                                 * THP migration might be unsupported or the
                                 * allocation could've failed so we should
                                 * retry on the same page with the THP split
                                 * to base pages.
                                 *
                                 * Head page is retried immediately and tail
                                 * pages are added to the tail of the list so
                                 * we encounter them after the rest of the list
                                 * is processed.
                                 */
                                if (PageTransHuge(page) && !PageHuge(page)) {
                                        lock_page(page);
                                        rc = split_huge_page_to_list(page, from);
                                        unlock_page(page);
                                        if (!rc) {
                                                list_safe_reset_next(page, page2, lru);
                                                goto retry;
                                        }
                                }
                                nr_failed++;
                                goto out;
                        case -EAGAIN:
                                retry++;
                                break;
                        case MIGRATEPAGE_SUCCESS:
                                nr_succeeded++;
                                break;
                        default:
                                /*
                                 * Permanent failure (-EBUSY, -ENOSYS, etc.):
                                 * unlike -EAGAIN case, the failed page is
                                 * removed from migration page list and not
                                 * retried in the next outer loop.
                                 */
                                nr_failed++;
                                break;
                        }
                }
        }
        nr_failed += retry;
        rc = nr_failed;
out:
        if (nr_succeeded)
                count_vm_events(PGMIGRATE_SUCCESS, nr_succeeded);
        if (nr_failed)
                count_vm_events(PGMIGRATE_FAIL, nr_failed);
        trace_mm_migrate_pages(nr_succeeded, nr_failed, mode, reason);

        if (!swapwrite)
                current->flags &= ~PF_SWAPWRITE;

        return rc;
}

최대 10번을 시도하여 migrate 스캐너가 isolation한 페이지를 unmap 한 후 free 스캐너가 isolation한 free 페이지로 migration한다.

  • 코드 라인 11~15에서 현재 태스크가 swap write를 지원하지 않는 경우 migration을 하는 동안만 swap write를 지원하도록 플래그를 추가한다.
  • 코드 라인 17~18에서 최대 반복 횟수를 10번으로 제한을 한다.
  • 코드 라인 20에서 인수로 전달받은 @from 리스트의 페이지들 만큼 루프를 돈다.
  • 코드 라인 24~31에서 huge 페이지 또는 일반 페이지의 unmap과 move를 수행한다. 10번 시도 중 4번째 시도 부터는 force 값을 1로 하여 full sync 모드에서 writeback 페이지들도 강제로 writeback이 끝날 때까지 대기하도록 강제한다.
  • 코드 라인 33~56에서 migration 결과가 메모리 부족인 경우 처리를 중단한다. 만일 TransHuge 페이지인 경우에 한해 페이지를 split 한 후 retry한다.
  • 코드 라인 57~59에서 migration을 다시 시도해야 하는 경우이다.
  • 코드 라인 60~62에서 migration 결과가 성공한 경우이다.
  • 코드 라인 63~72에서 migration 결과가 실패한 경우이다.
  • 코드 라인 77에서 10번을 시도하고 완료하였거나 메모리 부족으로 처리를 완료하고자 도달하는 out 레이블이다.
  • 코드 라인 84~85에서 현재 태스크에 설정해둔 swap writing 을 원래 상태로 돌려놓는다.

 


일반 페이지의 Migration

unmap_and_move()

mm/migrate.c -1/2-

/*
 * Obtain the lock on page, remove all ptes and migrate the page
 * to the newly allocated page in newpage.
 */
static ICE_noinline int unmap_and_move(new_page_t get_new_page,
                                   free_page_t put_new_page,
                                   unsigned long private, struct page *page,
                                   int force, enum migrate_mode mode,
                                   enum migrate_reason reason)
{
        int rc = MIGRATEPAGE_SUCCESS;
        struct page *newpage;

        if (!thp_migration_supported() && PageTransHuge(page))
                return -ENOMEM;

        newpage = get_new_page(page, private);
        if (!newpage)
                return -ENOMEM;

        if (page_count(page) == 1) {
                /* page was freed from under us. So we are done. */
                ClearPageActive(page);
                ClearPageUnevictable(page);
                if (unlikely(__PageMovable(page))) {
                        lock_page(page);
                        if (!PageMovable(page))
                                __ClearPageIsolated(page);
                        unlock_page(page);
                }
                if (put_new_page)
                        put_new_page(newpage, private);
                else
                        put_page(newpage);
                goto out;
        }

        rc = __unmap_and_move(page, newpage, force, mode);
        if (rc == MIGRATEPAGE_SUCCESS)
                set_page_owner_migrate_reason(newpage, reason);

migrate 스캐너가 isolation한 페이지를 unmap한 후 free 스캐너가 isolation한 free 페이지로 migration한다.

  • 코드 라인 10~11에서 thp(Transparent Huge Page) migration이 지원하지 않을 때 thp 에 대해 -ENOMEM을 반환한다.
  • 코드 라인 13~15에서 @get_new_page 함수를 통해 free 페이지를 가져온다.
    • compaction 시에는 @get_new_page에 compaction_alloc() 함수를 사용하여 free 스캐너가 관리하는 리스트에서 선두의 free 페이지를 가져온다.
  • 코드 라인 17~32에서 page가 이미 free 된 경우 페이지의 active 및 unevictable 플래그를 클리어한다.
  • 코드 라인 21~26에서 낮은 확률로 non-lru movable 페이지이지만 드라이버에 (*isolate_page) 후크 함수의 구현이 없는 경우 isolated 플래그를 클리어한다.
  • 코드 라인 27~31에서 @put_new_page 함수가 주어진 경우 이 함수를 통해 free 페이지를 다시 되돌려 놓고, 지정되지 않은 경우 페이지 를 버디 시스템으로 돌려 놓는다.
    • compaction 시에는 @put_new_page에 compaction_free()  함수를 사용하여 free 스캐너가 관리하는 리스트로 되돌려 놓는다.
  • 코드 라인 34~36에서 매핑을 푼 후 page를 newpage로 migration 한다. migration이 성공한 경우 디버그를 위해 페이지 owner에 reason을 기록해둔다.

 

mm/migrate.c -2/2-

out:
        if (rc != -EAGAIN) {
                /*
                 * A page that has been migrated has all references
                 * removed and will be freed. A page that has not been
                 * migrated will have kepts its references and be
                 * restored.
                 */
                list_del(&page->lru);

                /*
                 * Compaction can migrate also non-LRU pages which are
                 * not accounted to NR_ISOLATED_*. They can be recognized
                 * as __PageMovable
                 */
                if (likely(!__PageMovable(page)))
                        mod_node_page_state(page_pgdat(page), NR_ISOLATED_ANON +
                                        page_is_file_cache(page), -hpage_nr_pages(page));
        }

        /*
         * If migration is successful, releases reference grabbed during
         * isolation. Otherwise, restore the page to right list unless
         * we want to retry.
         */
        if (rc == MIGRATEPAGE_SUCCESS) {
                put_page(page);
                if (reason == MR_MEMORY_FAILURE) {
                        /*
                         * Set PG_HWPoison on just freed page
                         * intentionally. Although it's rather weird,
                         * it's how HWPoison flag works at the moment.
                         */
                        if (set_hwpoison_free_buddy_page(page))
                                num_poisoned_pages_inc();
                }
        } else {
                if (rc != -EAGAIN) {
                        if (likely(!__PageMovable(page))) {
                                putback_lru_page(page);
                                goto put_new;
                        }

                        lock_page(page);
                        if (PageMovable(page))
                                putback_movable_page(page);
                        else
                                __ClearPageIsolated(page);
                        unlock_page(page);
                        put_page(page);
                }
put_new:
                if (put_new_page)
                        put_new_page(newpage, private);
                else
                        put_page(newpage);
        }

        return rc;
}
  • 코드 라인 1~19에서 out: 레이블이다. migration 결과가 -EAGAIN이 아닌 경우 페이지를 LRU 리스트에서 분리한다. 그리고 높은 확률로 non-lru movable 페이지가 아닌 경우 nr_isolated_anon 또는 nr_isolated_file 카운터를 감소시킨다.
  • 코드 라인 26~36에서 migrate가 성공한 경우 페이지의 참조 카운터를 감소시켜 버디 시스템에 돌려보낸다.
  • 코드 라인 37~42에서 migrate 결과가 -EAGAIN인 경우 movable 매핑된 페이지가 아니면 LRU 리스트에 되돌려 놓는다.
  • 코드 라인 44~50에서 migrate 실패한 경우의 처리이다. non-lru movable free 페이지에 대해서는 파일 시스템의 (*putback_page) 후크 함수를 통해 원 위치로 돌려놓고, lru movable인 경우 원래 있었던 위치인 LRU 리스트로 되돌린다.
  • 코드 라인 52~56에서 put_new: 레이블이다. @put_new_page가 주어진 경우 해당 함수를 통해 free 페이지를 다시 되돌려 놓는다. 그렇지 않은 경우 버디 시스템으로 되돌려 놓는다.

 

__unmap_and_move()

mm/migrate.c -1/3-

static int __unmap_and_move(struct page *page, struct page *newpage,
                                int force, enum migrate_mode mode)
{
        int rc = -EAGAIN;
        int page_was_mapped = 0;
        struct anon_vma *anon_vma = NULL;
        bool is_lru = !__PageMovable(page);

        if (!trylock_page(page)) {
                if (!force || mode == MIGRATE_ASYNC)
                        goto out;

                /*
                 * It's not safe for direct compaction to call lock_page.
                 * For example, during page readahead pages are added locked
                 * to the LRU. Later, when the IO completes the pages are
                 * marked uptodate and unlocked. However, the queueing
                 * could be merging multiple pages for one bio (e.g.
                 * mpage_readpages). If an allocation happens for the
                 * second or third page, the process can end up locking
                 * the same page twice and deadlocking. Rather than
                 * trying to be clever about what pages can be locked,
                 * avoid the use of lock_page for direct compaction
                 * altogether.
                 */
                if (current->flags & PF_MEMALLOC)
                        goto out;

                lock_page(page);
        }

        if (PageWriteback(page)) {
                /*
                 * Only in the case of a full synchronous migration is it
                 * necessary to wait for PageWriteback. In the async case,
                 * the retry loop is too short and in the sync-light case,
                 * the overhead of stalling is too much
                 */
                switch (mode) {
                case MIGRATE_SYNC:
                case MIGRATE_SYNC_NO_COPY:
                        break;
                default:
                        rc = -EBUSY;
                        goto out_unlock;
                }
                if (!force)
                        goto out_unlock;
                wait_on_page_writeback(page);
        }

page를 unmapping 하고 newpage에 migration한다.

  • 코드 라인 7에서 lru movable 페이지 여부를 is_lru에 담는다.
    • true=lru movable페이지
    • false=non-lru movable 페이지
  • 코드 라인 9~30에서 에서 page에 대한 lock을 획득한다. 만일 락 획득 시도가 실패한 경우 @force가 0이거나 async migrate 모드인 경우 -EAGAIN 에러로 함수를 종료한다. 그리고 pfmemalloc상황에서 호출된 경우 migration이 이중으로 동작할 필요 없다.
  • 코드 라인 32~50에서 page가 파일시스템에 writeback 중인 경우 sync 또는 sync_no_copy인 경우 -EAGAIN 에러를 반환한다. 단 @force가 강제된 경우 writeback이 완료될 때까지 대기한다. 그 외의 async 및 sync_light 모드의 경우 -EBUSY 에러를 반환한다.

 

mm/migrate.c -2/3-

.       /*
         * By try_to_unmap(), page->mapcount goes down to 0 here. In this case,
         * we cannot notice that anon_vma is freed while we migrates a page.
         * This get_anon_vma() delays freeing anon_vma pointer until the end
         * of migration. File cache pages are no problem because of page_lock()
         * File Caches may use write_page() or lock_page() in migration, then,
         * just care Anon page here.
         *
         * Only page_get_anon_vma() understands the subtleties of
         * getting a hold on an anon_vma from outside one of its mms.
         * But if we cannot get anon_vma, then we won't need it anyway,
         * because that implies that the anon page is no longer mapped
         * (and cannot be remapped so long as we hold the page lock).
         */
        if (PageAnon(page) && !PageKsm(page))
                anon_vma = page_get_anon_vma(page);

        /*
         * Block others from accessing the new page when we get around to
         * establishing additional references. We are usually the only one
         * holding a reference to newpage at this point. We used to have a BUG
         * here if trylock_page(newpage) fails, but would like to allow for
         * cases where there might be a race with the previous use of newpage.
         * This is much like races on refcount of oldpage: just don't BUG().
         */
        if (unlikely(!trylock_page(newpage)))
                goto out_unlock;

        if (unlikely(!is_lru)) {
                rc = move_to_new_page(newpage, page, mode);
                goto out_unlock_both;
        }

        /*
         * Corner case handling:
         * 1. When a new swap-cache page is read into, it is added to the LRU
         * and treated as swapcache but it has no rmap yet.
         * Calling try_to_unmap() against a page->mapping==NULL page will
         * trigger a BUG.  So handle it here.
         * 2. An orphaned page (see truncate_complete_page) might have
         * fs-private metadata. The page can be picked up due to memory
         * offlining.  Everywhere else except page reclaim, the page is
         * invisible to the vm, so the page can not be migrated.  So try to
         * free the metadata, so the page can be freed.
         */
        if (!page->mapping) {
                VM_BUG_ON_PAGE(PageAnon(page), page);
                if (page_has_private(page)) {
                        try_to_free_buffers(page);
                        goto out_unlock_both;
                }
        } else if (page_mapped(page)) {
                /* Establish migration ptes */
                VM_BUG_ON_PAGE(PageAnon(page) && !PageKsm(page) && !anon_vma,
                                page);
                try_to_unmap(page,
                        TTU_MIGRATION|TTU_IGNORE_MLOCK|TTU_IGNORE_ACCESS);
                page_was_mapped = 1;
        }

        if (!page_mapped(page))
                rc = move_to_new_page(newpage, page, mode);

        if (page_was_mapped)
                remove_migration_ptes(page,
                        rc == MIGRATEPAGE_SUCCESS ? newpage : page, false);
  • 코드 라인 15~16에서 KSM(Kernel Shared Memory)을 제외한 anon 페이지인 경우anon_vma를구해온다.
  • 코드 라인 29~32에서 낮은 확률로 non-lru movable 페이지인 경우 페이지를 new 페이지로 옮기고 매핑 제거 루틴을 수행할 필요가 없으므로 곧장 out_unlock_both: 레이블로 이동한다.
  • 코드 라인 46~59에서 다음은 일반적이지 않은 코너 케이스에 해당하는 처리이다. 만일 anon 페이지이면서 별도의 버퍼를 사용하는(예: ksm) 경우 free 버퍼를 제거하고 함수를 빠져나간다. 그 외의 경우 이 페이지로 매핑된 모든 페이지 테이블에서 매핑을 해제하게 한다.
  • 코드 라인 61~62에서 페이지 테이블에 매핑되지 않은 페이지인 경우 페이지를 new 페이지로 옮긴 후 매핑 제거 루틴을 수행할 필요 없이 곧바로 out_unlock_both 레이블로 이동한다.
  • 코드 라인 64~66에서 페이지가 매핑되었었던 경우 기존 페이지에 연결된 모든 매핑을 새 페이지로 옮긴다.

 

mm/migrate.c -3/3-

out_unlock_both:
        unlock_page(newpage);
out_unlock:
        /* Drop an anon_vma reference if we took one */
        if (anon_vma)
                put_anon_vma(anon_vma);
        unlock_page(page);
out:
        /*
         * If migration is successful, decrease refcount of the newpage
         * which will not free the page because new page owner increased
         * refcounter. As well, if it is LRU page, add the page to LRU
         * list in here. Use the old state of the isolated source page to
         * determine if we migrated a LRU page. newpage was already unlocked
         * and possibly modified by its owner - don't rely on the page
         * state.
         */
        if (rc == MIGRATEPAGE_SUCCESS) {
                if (unlikely(!is_lru))
                        put_page(newpage);
                else
                        putback_lru_page(newpage);
        }

        return rc;
}
  • 코드 라인 1~2에서 out_unlock_both: 레이블에서는 새 페이지에 대한 lock을 먼저 release 한다.
  • 코드 라인 3~7에서 out_unlock: 레이블에서는 기존 페이지에 대해 lock을 release 한다. 만일 anon 페이지인 경우 anon_vma에 대한 사용이 완료되었으므로 참조카운터를 감소시킨다.
  • 코드 라인 8~23에서 migration이 성공한 경우 사용 중으로 바뀐 newpage의 사용을 완료시킨다. lru movable 페이지인 경우에는 LRU 리스트로 되돌린다.

 

Non-lru movable 페이지 여부 확인

PageMovable()

mm/compaction.c

int PageMovable(struct page *page)
{
        struct address_space *mapping;

        VM_BUG_ON_PAGE(!PageLocked(page), page);
        if (!__PageMovable(page))
                return 0;

        mapping = page_mapping(page);
        if (mapping && mapping->a_ops && mapping->a_ops->isolate_page)
                return 1;

        return 0;
}
EXPORT_SYMBOL(PageMovable);

non-lru movable 페이지 여부를 반환한다.

  • 코드 라인 6~7에서 non-lru movable 페이지가 아닌 경우 0을 반환한다.
  • 코드 라인 9~13에서 매핑된 페이지의 드라이버에 (*isolate_page) 후크 함수가 지원되는 경우 1을 반환하고, 그렇지 않은 경우 0을 반환한다.

 

__PageMovable()

static __always_inline int __PageMovable(struct page *page)
{
        return ((unsigned long)page->mapping & PAGE_MAPPING_FLAGS) ==
                                PAGE_MAPPING_MOVABLE;
}

non-lru movable 페이지 여부를 반환한다.

 

writeback 완료까지 대기

wait_on_page_writeback()

include/linux/pagemap.h

/* 
 * Wait for a page to complete writeback
 */
static inline void wait_on_page_writeback(struct page *page)
{
        if (PageWriteback(page))
                wait_on_page_bit(page, PG_writeback);
}

page가 writeback이 완료될 때까지 기다린다.

 


새 페이지로 이동

move_to_new_page()

mm/migrate.c

/*
 * Move a page to a newly allocated page
 * The page is locked and all ptes have been successfully removed.
 *
 * The new page will have replaced the old page if this function
 * is successful.
 *
 * Return value:
 *   < 0 - error code
 *  MIGRATEPAGE_SUCCESS - success
 */
static int move_to_new_page(struct page *newpage, struct page *page,
                                enum migrate_mode mode)
{
        struct address_space *mapping;
        int rc = -EAGAIN;
        bool is_lru = !__PageMovable(page);

        VM_BUG_ON_PAGE(!PageLocked(page), page);
        VM_BUG_ON_PAGE(!PageLocked(newpage), newpage);

        mapping = page_mapping(page);

        if (likely(is_lru)) {
                if (!mapping)
                        rc = migrate_page(mapping, newpage, page, mode);
                else if (mapping->a_ops->migratepage)
                        /*
                         * Most pages have a mapping and most filesystems
                         * provide a migratepage callback. Anonymous pages
                         * are part of swap space which also has its own
                         * migratepage callback. This is the most common path
                         * for page migration.
                         */
                        rc = mapping->a_ops->migratepage(mapping, newpage,
                                                        page, mode);
                else
                        rc = fallback_migrate_page(mapping, newpage,
                                                        page, mode);
        } else {
                /*
                 * In case of non-lru page, it could be released after
                 * isolation step. In that case, we shouldn't try migration.
                 */
                VM_BUG_ON_PAGE(!PageIsolated(page), page);
                if (!PageMovable(page)) {
                        rc = MIGRATEPAGE_SUCCESS;
                        __ClearPageIsolated(page);
                        goto out;
                }

                rc = mapping->a_ops->migratepage(mapping, newpage,
                                                page, mode);
                WARN_ON_ONCE(rc == MIGRATEPAGE_SUCCESS &&
                        !PageIsolated(page));
        }

        /*
         * When successful, old pagecache page->mapping must be cleared before
         * page is freed; but stats require that PageAnon be left as PageAnon.
         */
        if (rc == MIGRATEPAGE_SUCCESS) {
                if (__PageMovable(page)) {
                        VM_BUG_ON_PAGE(!PageIsolated(page), page);

                        /*
                         * We clear PG_movable under page_lock so any compactor
                         * cannot try to migrate this page.
                         */
                        __ClearPageIsolated(page);
                }

                /*
                 * Anonymous and movable page->mapping will be cleard by
                 * free_pages_prepare so don't reset it here for keeping
                 * the type to work PageAnon, for example.
                 */
                if (!PageMappingFlags(page))
                        page->mapping = NULL;
        }
out:
        return rc;
}

페이지를 새로 할당 받은 페이지로 migration 한다.

  • 코드 라인 6에서 movable 페이지가 lru 리스트에서 관리되는 페이지인지 여부를 알아온다.
  • 코드 라인 11~28에서 lru movable 페이지의 migration을 수행한다. 처리 유형은 다음 3가지이다.
    • A) anon 페이지의 migration
    • B) swap 캐시 및 파일 캐시 페이지의 (*migratepage)를 사용한 migration
    • C) swap 캐시 및 파일 캐시 페이지의 (*migratepage)가 없을 때 사용한 fallback migration
  •  코드 라인 29~45에서 non-lru movable 페이지의 migration을 수행한다. 처리 유형은 1 가지이다.
    • D) non-lru 페이지 migration을 수행한다. 단 파일 시스템에 (*migratepages) 후크가 구현되지 않은 경우 isolated 플래그를 제거하고 성공을 반환한다.
  • 코드 라인 51~69에서 migration이 성공한 경우 기존 페이지는 이제 free 페이지가 된 경우이다. non-lru 페이지였던 경우 isolated 플래그를 제거한다. 그리고 매핑되었던 경우 매핑을 제거한다.

 

A) lru movable – anon 페이지의 migration

migrate_page()

mm/migrate.c

/*
 * Common logic to directly migrate a single LRU page suitable for
 * pages that do not use PagePrivate/PagePrivate2.
 *
 * Pages are locked upon entry and exit.
 */
int migrate_page(struct address_space *mapping,
                struct page *newpage, struct page *page,
                enum migrate_mode mode)
{
        int rc;

        BUG_ON(PageWriteback(page));    /* Writeback must be complete */

        rc = migrate_page_move_mapping(mapping, newpage, page, mode, 0);

        if (rc != MIGRATEPAGE_SUCCESS)
                return rc;

        if (mode != MIGRATE_SYNC_NO_COPY)
                migrate_page_copy(newpage, page);
        else
                migrate_page_states(newpage, page);
        return MIGRATEPAGE_SUCCESS;
}
EXPORT_SYMBOL(migrate_page);

lru movable 페이지를 새로 할당 받은 페이지로 매핑을 migration하고 copy 한다.

  • 코드 라인 9~12에서 페이지를 migrate 한다.
  • 코드 라인 14~17에서 MIGRATE_SYNC_NO_COPY 모드에서는 페이지 디스크립터 정보만 옮기지만, 그 외의 모드에서는 cpu에서 페이지 프레임 복사도 추가하여 수행한다.

 

B) swap 캐시 및 파일 캐시 페이지의 (*migratepage)를 사용한 migration

다음 루틴에서 지원한다.

  • mm/shmem.c – migrate_page()
  • mm/swap_state.c – migrate_page()
  • fs/block_dev.c – buffer_migrate_page_norefs()
  • fs/ubifs/file.c – ubifs_migrate_page()
  • fs/ext2/inode.c – buffer_migrate_page()
  • fs/btrfs/disk-io.c – btree_migratepage()
  • fs/f2fs/checkpoint.c – f2fs_migrate_page()
  • fs/xfs/xfs_aops.c – iomap_migrate_page()
  • fs/hugetlbfs/inode.c – hugetlbfs_migrate_page()
  • fs/nfs/file.c – nfs_migrate_page()

 

C) fallback migrate

fallback_migrate_page()

mm/migrate.c

/*
 * Default handling if a filesystem does not provide a migration function.
 */
static int fallback_migrate_page(struct address_space *mapping,
        struct page *newpage, struct page *page, enum migrate_mode mode)
{
        if (PageDirty(page)) {
                /* Only writeback pages in full synchronous migration */
                switch (mode) {
                case MIGRATE_SYNC:
                case MIGRATE_SYNC_NO_COPY:
                        break;
                default:
                        return -EBUSY;
                }
                return writeout(mapping, page);
        }

        /*
         * Buffers may be managed in a filesystem specific way.
         * We must have no buffers or drop them.
         */
        if (page_has_private(page) &&
            !try_to_release_page(page, GFP_KERNEL))
                return -EAGAIN;

        return migrate_page(mapping, newpage, page, mode);
}

파일 시스템이 migration 기능을 지원하지 못할 때 default 호출되어 페이지를 migration 한다.

  • 코드 라인 4~14에서 ditty 페이지이면서 MIGRATE_SYNC  또는 MIGRATE_SYNC_NO_COPY 모드로 동작하는 경우에는 페이지를 기록하여 dirty 상태를 클리어한 후 리턴한다. 그 외의 모드의 경우 -EBUSY를 반환한다.
  • 코드 라인 20~22에서 private 페이지인 경우 파일 시스템 등이 생성한 해당 페이지에 대한 메타 데이터를 제거한다.
  • 코드 라인 24에서 page를 새 페이지로 migration한다.

 

D) non-lru 페이지 migration

다음 루틴에서 지원한다.

  • mm/zsmalloc.c – zs_page_migrate()
  • mm/balloon_compaction.c – balloon_page_migrate()
  • drivers/virtio/virtio_balloon.c – virtballoon_migratepage()

 


매핑 이동

migrate_page_move_mapping()

이 함수는 다음 루틴에서 호출되어 사용된다.

  • mm/migrate.c – migrate_page()
  • mm/migrate.c – __buffer_migrate_page()
  • fs/iomap.c – iomap_migrate_page()
  • fs/ubifs/file.c – ubifs_migrate_page()
  • fs/f2fs/data.c – f2fs_migrate_page()
  • fs/aio.c – aio_migratepage()

mm/migrate.c -1/2-

/*
 * Replace the page in the mapping.
 *
 * The number of remaining references must be:
 * 1 for anonymous pages without a mapping
 * 2 for pages with a mapping
 * 3 for pages with a mapping and PagePrivate/PagePrivate2 set.
 */
int migrate_page_move_mapping(struct address_space *mapping,
                struct page *newpage, struct page *page, enum migrate_mode mode,
                int extra_count)
{
        XA_STATE(xas, &mapping->i_pages, page_index(page));
        struct zone *oldzone, *newzone;
        int dirty;
        int expected_count = expected_page_refs(page) + extra_count;

        if (!mapping) {
                /* Anonymous page without mapping */
                if (page_count(page) != expected_count)
                        return -EAGAIN;

                /* No turning back from here */
                newpage->index = page->index;
                newpage->mapping = page->mapping;
                if (PageSwapBacked(page))
                        __SetPageSwapBacked(newpage);

                return MIGRATEPAGE_SUCCESS;
        }

        oldzone = page_zone(page);
        newzone = page_zone(newpage);

        xas_lock_irq(&xas);
        if (page_count(page) != expected_count || xas_load(&xas) != page) {
                xas_unlock_irq(&xas);
                return -EAGAIN;
        }

        if (!page_ref_freeze(page, expected_count)) {
                xas_unlock_irq(&xas);
                return -EAGAIN;
        }

        /*
         * Now we know that no one else is looking at the page:
         * no turning back from here.
         */
        newpage->index = page->index;
        newpage->mapping = page->mapping;
        page_ref_add(newpage, hpage_nr_pages(page)); /* add cache reference */
        if (PageSwapBacked(page)) {
                __SetPageSwapBacked(newpage);
                if (PageSwapCache(page)) {
                        SetPageSwapCache(newpage);
                        set_page_private(newpage, page_private(page));
                }
        } else {
                VM_BUG_ON_PAGE(PageSwapCache(page), page);
        }

페이지의 매핑을 newpage로 migration 한다.

  • 코드 라인 10~22에서 매핑되지 않은 anon 페이지의 경우 새 페이지에 인덱스와 매핑 정보를 옮긴다. 그리고 SwapBacked 플래그가 설정된 경우 제거한다. 만일 참조 카운터가 expected_count와 다른 경우 -EAGAIN을 반환하고 그렇지 않은 경우 success를 반환한다.
  • 코드 라인 27에서 xas array 락을 획득한다.
  • 코드 라인 28~31에서 참조 카운터가 expected_count가 아니면 -EAGAIN을 반환한다.
  • 코드 라인 33~36에서 참조 카운터를 0으로 리셋한다. 리셋 전 값이 expected_count가 아닌 경우 -EAGAIN을 반환한다.
  • 코드 라인 42~44에서 매핑된 페이지의 새 페이지에 인덱스, 매핑, 참조카운터를 옮긴다.
  • 코드 라인 45~53에서 SwapBacked 플래그를 옮긴다. 그리고 SwapCache 플래그도 옮기고 private 데이터에 저장된 값도 옮긴다.

 

mm/migrate.c -2/2-

        /* Move dirty while page refs frozen and newpage not yet exposed */
        dirty = PageDirty(page);
        if (dirty) {
                ClearPageDirty(page);
                SetPageDirty(newpage);
        }

        xas_store(&xas, newpage);
        if (PageTransHuge(page)) {
                int i;

                for (i = 1; i < HPAGE_PMD_NR; i++) {
                        xas_next(&xas);
                        xas_store(&xas, newpage + i);
                }
        }

        /*
         * Drop cache reference from old page by unfreezing
         * to one less reference.
         * We know this isn't the last reference.
         */
        page_ref_unfreeze(page, expected_count - hpage_nr_pages(page));

        xas_unlock(&xas);
        /* Leave irq disabled to prevent preemption while updating stats */

        /*
         * If moved to a different zone then also account
         * the page for that zone. Other VM counters will be
         * taken care of when we establish references to the
         * new page and drop references to the old page.
         *
         * Note that anonymous pages are accounted for
         * via NR_FILE_PAGES and NR_ANON_MAPPED if they
         * are mapped to swap space.
         */
        if (newzone != oldzone) {
                __dec_node_state(oldzone->zone_pgdat, NR_FILE_PAGES);
                __inc_node_state(newzone->zone_pgdat, NR_FILE_PAGES);
                if (PageSwapBacked(page) && !PageSwapCache(page)) {
                        __dec_node_state(oldzone->zone_pgdat, NR_SHMEM);
                        __inc_node_state(newzone->zone_pgdat, NR_SHMEM);
                }
                if (dirty && mapping_cap_account_dirty(mapping)) {
                        __dec_node_state(oldzone->zone_pgdat, NR_FILE_DIRTY);
                        __dec_zone_state(oldzone, NR_ZONE_WRITE_PENDING);
                        __inc_node_state(newzone->zone_pgdat, NR_FILE_DIRTY);
                        __inc_zone_state(newzone, NR_ZONE_WRITE_PENDING);
                }
        }
        local_irq_enable();

        return MIGRATEPAGE_SUCCESS;
}
EXPORT_SYMBOL(migrate_page_move_mapping);
  • 코드 라인 2~6에서 Dirty 플래그가 있는 경우 새 페이지에 옮기고, 기존 페이지는 제거한다.
  • 코드 라인8에서 xas xarray에 새페이지를 저장한다. 기존에는 radix tree를 사용했었는데 xarray 데이터 구조로 변경하였다.
  • 코드 라인 9~16에서 thp인 경우 소속된 각 페이지를 xas xarray에 저장한다.
  • 코드 라인 23에서 페이지의 참조 카운터를 페이지 수 만큼 지정한다.
  • 코드 라인 25에서 xas array 락을 release 한다.
  • 코드 라인 38~51 존이 변경된 경우 관련 카운터들 값을 증감한다.

 

다음 그림은 페이지 유형에 따라 기존 페이지에서 새 페이지로 관련 속성들을 복사하는 모습을 보여준다.

 


Rmap walk를 통한 페이지 migration

remove_migration_ptes()

mm/migrate.c

/*
 * Get rid of all migration entries and replace them by
 * references to the indicated page.
 */
static void remove_migration_ptes(struct page *old, struct page *new)
{
        struct rmap_walk_control rwc = {
                .rmap_one = remove_migration_pte,
                .arg = old,
        };

        if (locked)
                rmap_walk_locked(new, &rwc);
        else
                rmap_walk(new, &rwc);
}

@old 페이지에 연결된 모든 매핑을 @new 페이지로 옮긴다.

  • 코드 라인 3~6에서 기존 페이지를 참고하는 모든 vma를 찾기위해 rmap walk를 사용하도록 준비한다.
  • 코드 라인 8~11에서 rmap walk를 통해 관련 vma에 연결된 기존 매핑을 제거하고 새 페이지에 매핑을 옮긴다.

 

remove_migration_pte()

mm/migrate.c

/*
 * Restore a potential migration pte to a working pte entry
 */
static bool remove_migration_pte(struct page *page, struct vm_area_struct *vma,
                                 unsigned long addr, void *old)
{
        struct page_vma_mapped_walk pvmw = {
                .page = old,
                .vma = vma,
                .address = addr,
                .flags = PVMW_SYNC | PVMW_MIGRATION,
        };
        struct page *new;
        pte_t pte;
        swp_entry_t entry;

        VM_BUG_ON_PAGE(PageTail(page), page);
        while (page_vma_mapped_walk(&pvmw)) {
                if (PageKsm(page))
                        new = page;
                else
                        new = page - pvmw.page->index +
                                linear_page_index(vma, pvmw.address);

#ifdef CONFIG_ARCH_ENABLE_THP_MIGRATION
                /* PMD-mapped THP migration entry */
                if (!pvmw.pte) {
                        VM_BUG_ON_PAGE(PageHuge(page) || !PageTransCompound(page), page);
                        remove_migration_pmd(&pvmw, new);
                        continue;
                }
#endif

                get_page(new);
                pte = pte_mkold(mk_pte(new, READ_ONCE(vma->vm_page_prot)));
                if (pte_swp_soft_dirty(*pvmw.pte))
                        pte = pte_mksoft_dirty(pte);

                /*
                 * Recheck VMA as permissions can change since migration started
                 */
                entry = pte_to_swp_entry(*pvmw.pte);
                if (is_write_migration_entry(entry))
                        pte = maybe_mkwrite(pte, vma);

                if (unlikely(is_zone_device_page(new))) {
                        if (is_device_private_page(new)) {
                                entry = make_device_private_entry(new, pte_write(pte));
                                pte = swp_entry_to_pte(entry);
                        } else if (is_device_public_page(new)) {
                                pte = pte_mkdevmap(pte);
                                flush_dcache_page(new);
                        }
                } else
                        flush_dcache_page(new);

#ifdef CONFIG_HUGETLB_PAGE
                if (PageHuge(new)) {
                        pte = pte_mkhuge(pte);
                        pte = arch_make_huge_pte(pte, vma, new, 0);
                        set_huge_pte_at(vma->vm_mm, pvmw.address, pvmw.pte, pte);
                        if (PageAnon(new))
                                hugepage_add_anon_rmap(new, vma, pvmw.address);
                        else
                                page_dup_rmap(new, true);
                } else
#endif
                {
                        set_pte_at(vma->vm_mm, pvmw.address, pvmw.pte, pte);

                        if (PageAnon(new))
                                page_add_anon_rmap(new, vma, pvmw.address, false);
                        else
                                page_add_file_rmap(new, false);
                }
                if (vma->vm_flags & VM_LOCKED && !PageTransCompound(new))
                        mlock_vma_page(new);

                if (PageTransHuge(page) && PageMlocked(page))
                        clear_page_mlock(page);

                /* No need to invalidate - it was non-present before */
                update_mmu_cache(vma, pvmw.address, pvmw.pte);
        }

        return true;
}

@old 페이지에 연결된 모든 매핑을 @new 페이지로 옮긴다.

  • 코드 라인 4~15에서 old 페이지에 대한 매핑이 모두 제거될 때까지 반복한다.
  • 코드 라인 16~20에서 migration될 새 페이지를 구한다.
  • 코드 라인 24~28에서 pmd에 매핑된 thp 엔트리인 경우 pmd 엔트리로의 migration을 수행한 후 계속한다.
  • 코드 라인 31~34에서 새 페이지를 사용하기로 참조 카운터를 증가시킨다. 그런 후 이 페이지에 대한 pte 엔트리를 준비한다. 기존 pte 엔트리의 soft dirty 상태도 옮긴다.
  • 코드 라인 39~41에서 swap 엔트리가 migration 가능한 swap 엔트리에서 write 속성의 vma를 사용하면 pte 속성에 write 속성을 부가한다.
    • migration이 시작한 이후로 permission이 변경될 수 있는데 VMA를 다시 write 속성을 조사하여 변경된 경우 추가한다.
  • 코드 라인 43~52에서 새 페이지에 대한 데이터 캐시를 flush 한다.
    • private 페이지를 운영하는 존 디바이스인 경우 새 페이지에 대한 pte 엔트리를 가져온다.
    • private 페이지가 아닌 존 디바이스인 경우는 새 페이지에 대한 pte 엔트리를 가져오고 추가로 새 페이지에 대한 데이터 캐시를 flush한다.
  • 코드 라인 55~62에서 새 페이지가 huge 페이지인 경우 정규 매핑을 추가하고, rmap에도 추가한다.
  • 코드 라인 63~72에서 huge 페이지가 아닌 경우 정규 매핑을 추가하고, rmap에도 추가한다.
  • 코드 라인 73~74에서 VM_LOCKED 플래그를 가진 vma에 thp 및 hugetlbfs 페이지가 아닌 경우에만 새 페이지에 mlock 플래그 설정을 한다.
  • 코드 라인 76~77에서 만일 새 thp에 mlocked 플래그가 설정된 경우 클리어한다.
    • 참고로 thp는 mlock 설정을 할 수 없다.
  • 코드 라인 80에서 캐시 flush가 필요한 아키텍처에서 flush를 수행한다.
    • ARMv6 이상에서는 아무런 동작도 수행하지 않고, ARMv6 미만에서 cache coherent를 위한 flush 루틴들이 동작한다.

 

페이지 프레임 복사

migrate_page_copy()

mm/migrate.c

void migrate_page_copy(struct page *newpage, struct page *page)
{
        if (PageHuge(page) || PageTransHuge(page))
                copy_huge_page(newpage, page);
        else
                copy_highpage(newpage, page);

        migrate_page_states(newpage, page);
}
EXPORT_SYMBOL(migrate_page_copy);

기존 페이지 프레임을 새 페이지 프레임으로 복사한다. 또한 관련 페이지 디스크립터도 복사한다.

  • 코드 라인 3~4에서 huge 페이지 또는 thp인 경우의 복사 루틴을 수행한다.
  • 코드 라인 5~6에서 그렇지 않은 일반 페이지의 경우의 복사 루틴을 수행한다.
  • 코드 라인 8에서 페이지 디스크립터도 복사한다.

 

copy_huge_page()

mm/migrate.c

static void copy_huge_page(struct page *dst, struct page *src)
{
        int i;
        int nr_pages;

        if (PageHuge(src)) {
                /* hugetlbfs page */
                struct hstate *h = page_hstate(src);
                nr_pages = pages_per_huge_page(h);

                if (unlikely(nr_pages > MAX_ORDER_NR_PAGES)) {
                        __copy_gigantic_page(dst, src, nr_pages);
                        return;
                }
        } else {
                /* thp page */
                BUG_ON(!PageTransHuge(src));
                nr_pages = hpage_nr_pages(src);
        }

        for (i = 0; i < nr_pages; i++) {
                cond_resched();
                copy_highpage(dst + i, src + i);
        }
}

@src huge 페이지 프레임을 @dst로 복사한다.

  • 코드 라인 6~14에서 hugetlbfs 페이지이면서 최대 order 페이지 수보다 큰 페이지인 경우 단순히 페이지 구조체 포인터를 증가시키는 것으로 정확히 동작하는 것을 보장하지 못한다. 따라서 페이지 디스크립터의 포인터를 정확히 처리하기 위해 별도의 함수에서 처리한다.
  • 코드 라인 15~19에서 thp에 대한 페이지 수를 알아온다.
  • 코드 라인 21~24에서 페이지 수만큼 순회하며 @src+i의 페이지 프레임을 @dst+i로 복사한다.

 

__copy_gigantic_page()

mm/migrate.c

/*
 * Gigantic pages are so large that we do not guarantee that page++ pointer
 * arithmetic will work across the entire page.  We need something more
 * specialized.
 */
static void __copy_gigantic_page(struct page *dst, struct page *src,
                                int nr_pages)
{
        int i;
        struct page *dst_base = dst;
        struct page *src_base = src;

        for (i = 0; i < nr_pages; ) {
                cond_resched();
                copy_highpage(dst, src);

                i++;
                dst = mem_map_next(dst, dst_base, i);
                src = mem_map_next(src, src_base, i);
        }
}

최대 order 페이지 수 보다 큰 @src 페이지 프레임을 @dst로 복사한다.

 

mem_map_next()

mm/internal.h

/*
 * Iterator over all subpages within the maximally aligned gigantic
 * page 'base'.  Handle any discontiguity in the mem_map.
 */
static inline struct page *mem_map_next(struct page *iter,
                                                struct page *base, int offset)
{
        if (unlikely((offset & (MAX_ORDER_NR_PAGES - 1)) == 0)) {
                unsigned long pfn = page_to_pfn(base) + offset;
                if (!pfn_valid(pfn))
                        return NULL;
                return pfn_to_page(pfn);
        }
        return iter + 1;
}

@iter 페이지의 다음 페이지를 구해 반환한다. 최대 order 페이지 수를 초과하는 페이지의 경우 섹션별로 관리되는 mem_map의 경계를 초과할 수 있어 잘못된 주소가 나올 가능성이 있으므로 그럴 때마다 페이지 디스크립터의 주소를 정확히 재산출한다.

  • 코드 라인 4~9에서 낮은 확률로 offset이 최대 order 페이지 수(default: 1024 = 4M(4K 페이지)) 단위로 정렬된 경우에는 page 디스크립터 포인터를 증가시키면 mem_map의 경계를 초과할 수 있으므로 pfn 값을 먼저 구한 후 pfn_to_page() 함수를 사용하여 page 디스크립터를 다시 구한다.
  • 코드 라인 10에서 다음 페이지 디스크립터를 반환한다.

 

copy_highpage()

include/linux/highmem.h

static inline void copy_highpage(struct page *to, struct page *from)
{
        char *vfrom, *vto;

        vfrom = kmap_atomic(from);
        vto = kmap_atomic(to);
        copy_page(vto, vfrom);
        kunmap_atomic(vto);
        kunmap_atomic(vfrom);
}

@from 페이지 프레임을 @to로 복사한다.

  • 코드 라인 5~6에서 32비트 시스템에서 highmem 페이지인 경우 fixmap을 사용하여 임시로 매핑하도록 한다.
  • 코드 라인 7에서 아키텍처가 지원하는 가장 빠른 방법으로 페이지 프레임을 복사한다.
  • 코드 라인 8~9에서 fixmap에 임시 매핑한 페이지의 매핑을 해제한다.

 

페이지 디스크립터 정보 복사

migrate_page_states()

mm/migrate.c

/*
 * Copy the page to its new location
 */
void migrate_page_states(struct page *newpage, struct page *page)
{
        int cpupid;

        if (PageError(page))
                SetPageError(newpage);
        if (PageReferenced(page))
                SetPageReferenced(newpage);
        if (PageUptodate(page))
                SetPageUptodate(newpage);
        if (TestClearPageActive(page)) {
                VM_BUG_ON_PAGE(PageUnevictable(page), page);
                SetPageActive(newpage);
        } else if (TestClearPageUnevictable(page))
                SetPageUnevictable(newpage);
        if (PageWorkingset(page))
                SetPageWorkingset(newpage);
        if (PageChecked(page))
                SetPageChecked(newpage);
        if (PageMappedToDisk(page))
                SetPageMappedToDisk(newpage);

        /* Move dirty on pages not done by migrate_page_move_mapping() */
        if (PageDirty(page))
                SetPageDirty(newpage);

        if (page_is_young(page))
                set_page_young(newpage);
        if (page_is_idle(page))
                set_page_idle(newpage);

        /*
         * Copy NUMA information to the new page, to prevent over-eager
         * future migrations of this same page.
         */
        cpupid = page_cpupid_xchg_last(page, -1);
        page_cpupid_xchg_last(newpage, cpupid);

        ksm_migrate_page(newpage, page);
        /*
         * Please do not reorder this without considering how mm/ksm.c's
         * get_ksm_page() depends upon ksm_migrate_page() and PageSwapCache().
         */
        if (PageSwapCache(page))
                ClearPageSwapCache(page);
        ClearPagePrivate(page);
        set_page_private(page, 0);

        /*
         * If any waiters have accumulated on the new page then
         * wake them up.
         */
        if (PageWriteback(newpage))
                end_page_writeback(newpage);

        copy_page_owner(page, newpage);

        mem_cgroup_migrate(page, newpage);
}
EXPORT_SYMBOL(migrate_page_states);

기존 페이지 디스크립터 내용을 새 페이지의 디스크립터로 옮긴다.

  • 코드 라인 5~6에서 PG_error 플래그를 옮긴다.
  • 코드 라인 7~8에서 PG_referenced 플래그를 옮긴다.
  • 코드 라인 9~10에서 PG_uptodate 플래그를 옮긴다.
  • 코드 라인 11~15에서  PG_active 플래그 또는 PG_unevictable 플래그를 옮기고, 기존 페이지에서는 제거한다.
  • 코드 라인 16~17에서 PG_workingset 플래그를 옮긴다.
  • 코드 라인 18~19에서 PG_checked 플래그를 옮긴다.
  • 코드 라인 20~21에서 PG_mappedtodist 플래그를 옮긴다.
  • 코드 라인 24~25에서 PG_dirty 플래그를 옮긴다.
  • 코드 라인 27~28에서 Young 플래그를 옮긴다.
    • 32bit 시스템의 경우 page 구조체가 아닌 page_ext 구조체에 존재한다.
  • 코드 라인 29~30에서 Idle 플래그를 옮긴다.
    • 32bit 시스템의 경우 page 구조체가 아닌 page_ext 구조체에 존재한다.
  • 코드 라인 36~37에서 cpupid 정보를 옮기고, 기존 페이지에는 -1을 대입한다.
  • 코드 라인 39에서 ksm 페이지이면서, 복사할 대상이 statble 노드 매핑된 경우 stable 노드 매핑 정보를 제거한다.
    • KSM(Kernel Same page Merging)이 application이 주소 공간에서 같은 내용을 가진 페이지를 스캔하여 한 페이지로 merge 시킬 수 있도록 하는 기능이다.
    • 참고: How to use the Kernel Samepage Merging feature | kernel.org
  • 코드 라인 44~45에서 기존 페이지의 SwapCache 정보를 제거한다.
  • 코드 라인 46~47에서 기존 페이지의 Private 플래그를 제거하고, p->private에 0을 대입한다.
  • 코드 라인 53~54에서 새 페이지에 Writeback 플래그가 설정된 경우 lru의 tail로 rotate하고, 관련 태스크를 깨워 즉각 회수가 가능하도록 한다.
  • 코드 라인 56에서 page owner 정보를 옮긴다.
  • 코드 라인 58에서 memcg 정보를 옮긴다.

 

참고

댓글 남기기