Zonned Allocator -6- (Direct Compact-Migration)

 

Migration

migrate_async_suitable()

mm/compaction.c

static inline bool migrate_async_suitable(int migratetype)
{
        return is_migrate_cma(migratetype) || migratetype == MIGRATE_MOVABLE;
}

migrate 타입이 MIGRATE_CMA 또는 MIGRATE_MOVABLE인 경우 true를 반환한다.

 

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_lru_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;

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

 

  • int swapwrite = current->flags & PF_SWAPWRITE;
    • 현재 태스크가 swap writing을 지원하는지 여부
  • if (!swapwrite) current->flags |= PF_SWAPWRITE;
    • 현재 태스크가 swap writing을 지원하지 않는 경우 migration을 하는 동안 현재 태스크에 swap writing을 설정하게 한다.

 

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

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

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

                        switch(rc) {
                        case -ENOMEM:
                                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;
                        }
                }
        }
        rc = nr_failed + retry;
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;
}
  •  for(pass = 0; pass < 10 && retry; pass++) {
    • 최대 반복 횟수를 10번으로 제한을 한다.
  • list_for_each_entry_safe(page, page2, from, lru) {
    • 인수로 전달받은 migrate 리스트의 페이지들 만큼 루프를 돈다.
  • f (PageHuge(page)) rc = unmap_and_move_huge_page(get_new_page, put_new_page, private, page, pass > 2, mode);
    • 페이지가 huge 페이지인 경우 huge 페이지에 대한 unmap과 이동을 수행한다.
  • else rc = unmap_and_move(get_new_page, put_new_page, private, page, pass > 2, mode);
    • 페이지가 huge 페이지가 아닌 경우 일반 페이지에 대한 unmap과 이동을 수행한다.
  • switch(rc) { case -ENOMEM: goto out;
    • migration 결과가 메모리 부족인 경우 처리를 중단한다.
  • case -EAGAIN: retry++; break;
    • migration 결과가 재시도
  • case MIGRATEPAGE_SUCCESS: nr_succeeded++; break;
    • migration 결과가 성공
  • default:  nr_failed++; break; }
    • migration 결과가 실패인 경우 중단한다.
  • if (nr_succeeded) count_vm_events(PGMIGRATE_SUCCESS, nr_succeeded);
    • PGMIGRATE_SUCCESS stat을 nr_succeeded 만큼 증가시킨다.
  • if (nr_failed) count_vm_events(PGMIGRATE_FAIL, nr_failed);
    • PGMIGRATE_FAIL stat을 nr_failed 만큼 증가시킨다.
  • if (!swapwrite) current->flags &= ~PF_SWAPWRITE;
    • 현재 태스크에 설정해둔 swap writing 을 해제한다.

 

unmap_and_move_huge_page()

mm/migrate.c

/*
 * Counterpart of unmap_and_move_page() for hugepage migration.
 *
 * This function doesn't wait the completion of hugepage I/O
 * because there is no race between I/O and migration for hugepage.
 * Note that currently hugepage I/O occurs only in direct I/O
 * where no lock is held and PG_writeback is irrelevant,
 * and writeback status of all subpages are counted in the reference
 * count of the head page (i.e. if all subpages of a 2MB hugepage are
 * under direct I/O, the reference of the head page is 512 and a bit more.)
 * This means that when we try to migrate hugepage whose subpages are
 * doing direct I/O, some references remain after try_to_unmap() and
 * hugepage migration fails without data corruption.
 *
 * There is also no race when direct I/O is issued on the page under migration,
 * because then pte is replaced with migration swap entry and direct I/O code
 * will wait in the page fault for migration to complete.
 */
static int unmap_and_move_huge_page(new_page_t get_new_page,
                                free_page_t put_new_page, unsigned long private,
                                struct page *hpage, int force,
                                enum migrate_mode mode)
{
        int rc = 0;
        int *result = NULL;
        int page_was_mapped = 0;
        struct page *new_hpage;
        struct anon_vma *anon_vma = NULL;

        /*
         * Movability of hugepages depends on architectures and hugepage size.
         * This check is necessary because some callers of hugepage migration
         * like soft offline and memory hotremove don't walk through page
         * tables or check whether the hugepage is pmd-based or not before
         * kicking migration.
         */
        if (!hugepage_migration_supported(page_hstate(hpage))) {
                putback_active_hugepage(hpage);
                return -ENOSYS;
        }

        new_hpage = get_new_page(hpage, private, &result);
        if (!new_hpage)
                return -ENOMEM;

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

 

  • if (!hugepage_migration_supported(page_hstate(hpage))) { putback_active_hugepage(hpage); return -ENOSYS; }
    • huge 페이지 migration이 지원되지 않는 경우 해당 페이지를 다시 원래 huge 페이지 관리 리스트로 되돌리고 함수를 중단한다.
  • new_hpage = get_new_page(hpage, private, &result); if (!new_hpage)  return -ENOMEM;
    • free 스캐너로부터 isolation된 cc->freepages 리스트에서 선두의 free 페이지를 가져오는데 만일 반환할 free 페이지가 없으면 free 스캐너를 가동한다. 실패한 경우 메모리 부족으로 함수를 종료한다.

 

        if (!trylock_page(hpage)) {
                if (!force || mode != MIGRATE_SYNC)
                        goto out;
                lock_page(hpage);
        }

        if (PageAnon(hpage))
                anon_vma = page_get_anon_vma(hpage);

        if (page_mapped(hpage)) {
                try_to_unmap(hpage,
                        TTU_MIGRATION|TTU_IGNORE_MLOCK|TTU_IGNORE_ACCESS);
                page_was_mapped = 1;
        }

        if (!page_mapped(hpage))
                rc = move_to_new_page(new_hpage, hpage, page_was_mapped, mode);

        if (rc != MIGRATEPAGE_SUCCESS && page_was_mapped)
                remove_migration_ptes(hpage, hpage);

        if (anon_vma)
                put_anon_vma(anon_vma);

        if (rc == MIGRATEPAGE_SUCCESS)
                hugetlb_cgroup_migrate(hpage, new_hpage);

        unlock_page(hpage);
out:
        if (rc != -EAGAIN)
                putback_active_hugepage(hpage);

        /*
         * If migration was not successful and there's a freeing callback, use
         * it.  Otherwise, put_page() will drop the reference grabbed during
         * isolation.
         */
        if (rc != MIGRATEPAGE_SUCCESS && put_new_page)
                put_new_page(new_hpage, private);
        else
                put_page(new_hpage);

        if (result) {
                if (rc)
                        *result = rc;
                else
                        *result = page_to_nid(new_hpage);
        }
        return rc;
}
  • if (!trylock_page(hpage)) { if (!force || mode != MIGRATE_SYNC) goto out; lock_page(hpage); }
    • hpage에 대한 lock을 시도해서 실패한 경우 migrate 모드가 MIGRATE_SYNC가 아니거나 force 옵션이 0인 경우 -EAGAIN 결과로 함수를 종료하고 그렇지 않은 경우 lock을 획득한다.
  • if (PageAnon(hpage)) anon_vma = page_get_anon_vma(hpage);
    • huge 페이지가 anon 타입인 경우 anon_vma를 알아온다.
  • if (page_mapped(hpage)) { try_to_unmap(hpage, TTU_MIGRATION|TTU_IGNORE_MLOCK|TTU_IGNORE_ACCESS); page_was_mapped = 1; }
    • huge 페이지가 매핑된 페이지인 경우 unmap을 시도한다.
  • if (!page_mapped(hpage)) rc = move_to_new_page(new_hpage, hpage, page_was_mapped, mode);
    • huge 페이지가 매핑되어 있지 않은 경우 페이지를 free huge 페이지로 옮긴다.
  • if (rc != MIGRATEPAGE_SUCCESS && page_was_mapped) remove_migration_ptes(hpage, hpage);
    • huge page가 원래 매핑되었던 경우이면서 migration이 실패한 경우 hpage의 pte가 swap pte인 경우 vma->vm_page_prot를 기반으로 필요 속성을 추가하여 hpage의 pte를 갱신한다.
  • if (anon_vma) put_anon_vma(anon_vma);
    • anon_vma object를 release 한다.
  • if (rc == MIGRATEPAGE_SUCCESS) hugetlb_cgroup_migrate(hpage, new_hpage);
    • migration이 성공한 경우 old hpage로 부터 h_cg를 new hpage로 옮기고 클리어한다. 그런 후 old hpage가 사용한 hstate[]->hugepage_activelist에 new hpage를 추가한다.
  • out: if (rc != -EAGAIN) putback_active_hugepage(hpage);
    • migration이 실패한 경우 해당 페이지를 다시 원래 huge 페이지 관리 리스트로 되돌린다.
  • if (rc != MIGRATEPAGE_SUCCESS && put_new_page) put_new_page(new_hpage, private); else put_page(new_hpage);
    • migration이 실패한 경우이면서 put_new_page 인수에 함수가 지정된 경우 호출하여 다시 cc->freepages 리스트로 되돌린다.
    • 그렇지 않은 경우 new_hpage를 release한다.
  • if (result) { if (rc) *result = rc; else *result = page_to_nid(new_hpage); } return rc;
    • result 출력 인수가 지정되었으면 reslut에 에러인 경우 rc 값을, 에러가 아닌 경우 노드 id를 대입하고 함수를 종료한다.

 

hugepage_migration_supported()

include/linux/hugetlb.h

static inline int hugepage_migration_supported(struct hstate *h)
{
#ifdef CONFIG_ARCH_ENABLE_HUGEPAGE_MIGRATION
        return huge_page_shift(h) == PMD_SHIFT;
#else
        return 0;
#endif
}

huge 페이지 migration이 지원되는지 여부를 반환한다.

 

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,
                                int page_was_mapped, enum migrate_mode mode)
{
        struct address_space *mapping;
        int rc;

        /*
         * Block others from accessing the page when we get around to
         * establishing additional references. We are the only one
         * holding a reference to the new page at this point.
         */
        if (!trylock_page(newpage))
                BUG();

        /* Prepare mapping for the new page.*/
        newpage->index = page->index;
        newpage->mapping = page->mapping;
        if (PageSwapBacked(page))
                SetPageSwapBacked(newpage);

        mapping = page_mapping(page);
        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);

        if (rc != MIGRATEPAGE_SUCCESS) {
                newpage->mapping = NULL;
        } else {
                mem_cgroup_migrate(page, newpage, false);
                if (page_was_mapped)
                        remove_migration_ptes(page, newpage);
                page->mapping = NULL;
        }

        unlock_page(newpage);

        return rc;
}

page를 newpage로 migration 한다.

  • newpage->index = page->index; newpage->mapping = page->mapping; if (PageSwapBacked(page)) SetPageSwapBacked(newpage);
    • 기존 페이지의 index 및 mapping 값을 새 페이지에 옮기고 swap backed 플래그도 옮긴다.
  • mapping = page_mapping(page); if (!mapping) rc = migrate_page(mapping, newpage, page, mode);
    • 페이지에 대한 매핑을 가져와서 매핑 페이지가 아닌 경우 migration을 한다.
  • else if (mapping->a_ops->migratepage) rc = mapping->a_ops->migratepage(mapping, newpage, page, mode);
    • 매핑 페이지의 경우 매핑 핸들러가 있으면 핸들러 함수를 호출하여 migration을 한다.
  • else rc = fallback_migrate_page(mapping, newpage, page, mode);
    • 파일 시스템이 매핑 핸들러를 지원하지 못하여 default로 호출되는 함수를 사용하여 migration을 한다.
  • if (rc != MIGRATEPAGE_SUCCESS) { newpage->mapping = NULL;
    • migration이 실패한 경우 newpage의 매핑에 null을 대입한다.
  • } else { mem_cgroup_migrate(page, newpage, false);  if (page_was_mapped) remove_migration_ptes(page, newpage); page->mapping = NULL; }
    • migration이 성공한 경우 memcg에 migration 성공을 보고하고 page의 pte가 swap pte인 경우 vma->vm_page_prot를 기반으로 필요 속성을 추가하여 newpage의 pte를 갱신한다. 또한 기존 page의 mapping에 null을 대입한다.

 

migrate_page()

mm/migrate.c

/*
 * Common logic to directly migrate a single 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, NULL, mode, 0);

        if (rc != MIGRATEPAGE_SUCCESS)
                return rc;

        migrate_page_copy(newpage, page);
        return MIGRATEPAGE_SUCCESS;
}
EXPORT_SYMBOL(migrate_page);

page를 newpage로 매핑을 migration하고 copy 한다.

 

migrate_page_move_mapping()

mm/migrate.c

/*
 * 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,
                struct buffer_head *head, enum migrate_mode mode,
                int extra_count)
{
        int expected_count = 1 + extra_count;
        void **pslot;

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

        spin_lock_irq(&mapping->tree_lock);

        pslot = radix_tree_lookup_slot(&mapping->page_tree,
                                        page_index(page));

        expected_count += 1 + page_has_private(page);
        if (page_count(page) != expected_count ||
                radix_tree_deref_slot_protected(pslot, &mapping->tree_lock) != page) {
                spin_unlock_irq(&mapping->tree_lock);
                return -EAGAIN;
        }

        if (!page_freeze_refs(page, expected_count)) {
                spin_unlock_irq(&mapping->tree_lock);
                return -EAGAIN;
        }

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

  • if (!mapping) { if (page_count(page) != expected_count) return -EAGAIN; return MIGRATEPAGE_SUCCESS; }
    • 매핑된 페이지가 아닌 경우 매핑을 옮길 필요가 없으므로 함수를 성공으로 종료하되 참조 카운터가 expected_count가 아니면 -EAGAIN 결과를 반환한다.
  •   pslot = radix_tree_lookup_slot(&mapping->page_tree, page_index(page));
    • 페이지로부터 radix 트리 슬롯을 알아온다.
  • expected_count += 1 + page_has_private(page);
    • expected 카운터를 1 증가시키되 private 플래그 설정된 경우 1을 추가 증가시킨다.
  • if (page_count(page) != expected_count || radix_tree_deref_slot_protected(pslot, &mapping->tree_lock) != page) { spin_unlock_irq(&mapping->tree_lock); return -EAGAIN; }
    • 참조 카운터가 expected_count와 다르거나 pslot에 저장된 페이지와 요청 페이지가 서로 다른 경우 -EAGAIN 결과를 반환한다.
  • if (!page_freeze_refs(page, expected_count)) { spin_unlock_irq(&mapping->tree_lock); return -EAGAIN; }
    • 페이지의 레퍼런스 카운터를 0으로 설정하되 기존 레퍼런스 카운터값과 expected 카운터가 서로 다른 경우 -EAGAIN 결과를 반환한다.

 

        /*
         * In the async migration case of moving a page with buffers, lock the
         * buffers using trylock before the mapping is moved. If the mapping
         * was moved, we later failed to lock the buffers and could not move
         * the mapping back due to an elevated page count, we would have to
         * block waiting on other references to be dropped.
         */
        if (mode == MIGRATE_ASYNC && head &&
                        !buffer_migrate_lock_buffers(head, mode)) {
                page_unfreeze_refs(page, expected_count);
                spin_unlock_irq(&mapping->tree_lock);
                return -EAGAIN;
        }

        /*
         * Now we know that no one else is looking at the page.
         */
        get_page(newpage);      /* add cache reference */
        if (PageSwapCache(page)) {
                SetPageSwapCache(newpage);
                set_page_private(newpage, page_private(page));
        }

        radix_tree_replace_slot(pslot, newpage);

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

        /*
         * 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_PAGES if they
         * are mapped to swap space.
         */
        __dec_zone_page_state(page, NR_FILE_PAGES);
        __inc_zone_page_state(newpage, NR_FILE_PAGES);
        if (!PageSwapCache(page) && PageSwapBacked(page)) {
                __dec_zone_page_state(page, NR_SHMEM);
                __inc_zone_page_state(newpage, NR_SHMEM);
        }
        spin_unlock_irq(&mapping->tree_lock);

        return MIGRATEPAGE_SUCCESS;
}
  •  if (mode == MIGRATE_ASYNC && head && !buffer_migrate_lock_buffers(head, mode)) { page_unfreeze_refs(page, expected_count); spin_unlock_irq(&mapping->tree_lock); return -EAGAIN; }
    • 비동기 migration이면서 버퍼 head가 지정되었고 버퍼 lock을 건다. 만일 lock이 실패하면 이 실패하면  페이지의  레퍼런스 카운터를 expected 카운터로 설정하고 -EAGAIN 결과를 반환한다.
  • get_page(newpage);
    • 페이지의 레퍼런스 카운터를 증가시킨다.
  • if (PageSwapCache(page)) { SetPageSwapCache(newpage); set_page_private(newpage, page_private(page)); }
    • 페이지가 swap 캐시이면 newpage에도 swap 캐시 설정을하고 private 설정도 옮긴다.
  • radix_tree_replace_slot(pslot, newpage);
    • radix 트리의 slot에 newpage를 저장한다.
  • page_unfreeze_refs(page, expected_count – 1);
    • 기존 페이지의 레퍼런스 카운터를 expected 카운터-1 값을 대입한다.
  • __dec_zone_page_state(page, NR_FILE_PAGES); __inc_zone_page_state(newpage, NR_FILE_PAGES);
    • 기존 페이지의 NR_FILE_PAGES stat을 감소시키고, newpage의 NR_FILE_PAGES stat을 증가시킨다.
  • if (!PageSwapCache(page) && PageSwapBacked(page)) { __dec_zone_page_state(page, NR_SHMEM); __inc_zone_page_state(newpage, NR_SHMEM); }
    • 기존 페이지가 swapcache가 아니면서 swapbacked인 경우 기존 페이지의 NR_SHMEM stat을 감소시키고 newpage의 NR_SHMEM stat을 증가시킨다.

 

page_freeze_refs()

include/linux/pagemap.h

static inline int page_freeze_refs(struct page *page, int count)
{
        return likely(atomic_cmpxchg(&page->_count, count, 0) == count);
}

높은 확률로 페이지의 참조 카운터가 요청 카운터와 같은 경우 0을 대입하여 freeze(0) 한다.

 

page_unfreeze_refs()

include/linux/pagemap.h

static inline void page_unfreeze_refs(struct page *page, int count)
{
        VM_BUG_ON_PAGE(page_count(page) != 0, page);
        VM_BUG_ON(count == 0);
 
        atomic_set(&page->_count, count);
}

페이지의 참조 카운터에 요청 카운터를 대입한다.

 

get_page()

include/linux/mm.h

static inline void get_page(struct page *page)
{
        if (unlikely(PageTail(page)))
                if (likely(__get_page_tail(page)))
                        return;
        /*  
         * Getting a normal page or the head of a compound page
         * requires to already have an elevated page->_count. 
         */ 
        VM_BUG_ON_PAGE(atomic_read(&page->_count) <= 0, page);
        atomic_inc(&page->_count);
}

페이지의 참조 카운터를 증가시킨다.

 

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,
        };

        rmap_walk(new, &rwc);
}

old 페이지의 pte가 swap pte인 경우 vma->vm_page_prot를 기반으로 필요 속성을 추가하여 new 페이지의 pte를 갱신한다.

 

remove_migration_pte()

mm/migrate.c

/*
 * Restore a potential migration pte to a working pte entry
 */
static int remove_migration_pte(struct page *new, struct vm_area_struct *vma,
                                 unsigned long addr, void *old)
{
        struct mm_struct *mm = vma->vm_mm;
        swp_entry_t entry;
        pmd_t *pmd;
        pte_t *ptep, pte;
        spinlock_t *ptl;

        if (unlikely(PageHuge(new))) {
                ptep = huge_pte_offset(mm, addr);
                if (!ptep)
                        goto out;
                ptl = huge_pte_lockptr(hstate_vma(vma), mm, ptep);
        } else {
                pmd = mm_find_pmd(mm, addr);
                if (!pmd)
                        goto out;

                ptep = pte_offset_map(pmd, addr);
                
                /*
                 * Peek to check is_swap_pte() before taking ptlock?  No, we
                 * can race mremap's move_ptes(), which skips anon_vma lock.
                 */
      
                ptl = pte_lockptr(mm, pmd);
        }

        spin_lock(ptl);
        pte = *ptep;
        if (!is_swap_pte(pte))
                goto unlock;

        entry = pte_to_swp_entry(pte);

        if (!is_migration_entry(entry) ||
            migration_entry_to_page(entry) != old)
                goto unlock;
        
        get_page(new);
        pte = pte_mkold(mk_pte(new, vma->vm_page_prot));
        if (pte_swp_soft_dirty(*ptep))
                pte = pte_mksoft_dirty(pte);

old 페이지의 pte가 swap pte인 경우 vma->vm_page_prot를 기반으로 필요 속성을 추가하여 new 페이지의 pte를 갱신한다.

 

  • if (unlikely(PageHuge(new))) { ptep = huge_pte_offset(mm, addr); if (!ptep) goto out; ptl = huge_pte_lockptr(hstate_vma(vma), mm, ptep);
    • huge 페이지에서 pte 및 ptl(page table lock) 포인터를 알아온다.
      • huge 페이지에서는 ptl에 대해 mm->page_table_lock 을 사용한다.
  • } else {  pmd = mm_find_pmd(mm, addr); if (!pmd) goto out;  ptep = pte_offset_map(pmd, addr); ptl = pte_lockptr(mm, pmd); }
    • 일반 페이지에서 pte 및 ptl(page table lock) 포인터를 알아온다.
      • 일반 페이지에서는 ptl에 대해 pmd 페이지의 page->ptl을 사용한다.
  • spin_lock(ptl); pte = *ptep; if (!is_swap_pte(pte)) goto unlock;
    • ptl 락을 걸고 swap pte가 아닌 경우 더 이상 처리할 필요 없으므로 함수를 빠져나간다.
      • swap pte의 경우 pte 엔트리 값이 0이 아니지만 L_PTE_PRESENT 비트가 없다.
  • entry = pte_to_swp_entry(pte);
    • pte 값으로 swap 엔트리를 알아온다.
  • if (!is_migration_entry(entry) || migration_entry_to_page(entry) != old) goto unlock;
    • migration 엔트리가 아니거나 migration 엔트리 페이지가 old 페이지가 아닌 경우 함수를 빠져나간다.
  • get_page(new);
    • new 페이지의 참조 카운터를 증가시킨다.
  • pte = pte_mkold(mk_pte(new, vma->vm_page_prot));
    • new 페이지의 주소와 vma->vm_page_prot 속성으로 pte 엔트리 값을 만들고 L_PTE_YOUNG 비트만 제거한다.
  • if (pte_swp_soft_dirty(*ptep)) pte = pte_mksoft_dirty(pte);
    • 기존 pte 엔트리 값에 swp_soft_dirty 속성이 있는 경우 똑같이 설정한다.
      • 아키텍처가 지원해야 사용할 수 있다. (arm: 사용하지 않음)

 

        /* Recheck VMA as permissions can change since migration started  */
        if (is_write_migration_entry(entry))
                pte = maybe_mkwrite(pte, vma);
 
#ifdef CONFIG_HUGETLB_PAGE
        if (PageHuge(new)) {
                pte = pte_mkhuge(pte);
                pte = arch_make_huge_pte(pte, vma, new, 0);
        }
#endif 
        flush_dcache_page(new);
        set_pte_at(mm, addr, ptep, pte);

        if (PageHuge(new)) {
                if (PageAnon(new))
                        hugepage_add_anon_rmap(new, vma, addr);
                else
                        page_dup_rmap(new);
        } else if (PageAnon(new))
                page_add_anon_rmap(new, vma, addr);
        else
                page_add_file_rmap(new);

        /* No need to invalidate - it was non-present before */
        update_mmu_cache(vma, addr, ptep);
unlock:
        pte_unmap_unlock(ptep, ptl);
out:
        return SWAP_AGAIN;
}
  • if (is_write_migration_entry(entry)) pte = maybe_mkwrite(pte, vma);
    • migration이 시작한 이후로 permission이 변경될 수 있는데 VMA를 다시 write 속성을 조사하여 변경된 경우 추가한다.
  • if (PageHuge(new)) { pte = pte_mkhuge(pte); pte = arch_make_huge_pte(pte, vma, new, 0); }
    • new 페이지가 huge 페이지인 경우 PTE_TABLE_BIT를 제거한다. 또한 아키텍처가 지원하는 경우 huge pte 엔트리 값을 추가로 조작할 수 있게 한다.
  • flush_dcache_page(new);
    • new 페이지에 대한 d-cache를 flush 한다.
  • set_pte_at(mm, addr, ptep, pte);
    • mm에서 pte 엔트리를 갱신한다.
  • if (PageHuge(new)) { if (PageAnon(new)) hugepage_add_anon_rmap(new, vma, addr); else page_dup_rmap(new);
    • huge 페이지인 경우 매핑 정보를 다음과 같이 갱신한다.
      • anon huge 페이지 인경우 매핑 카운터를 증가시키고 매핑 카운터가 1(first)이면 vma를 사용하여 page->mapping, page->index를 갱신한다.
      • 그 외 huge 페이지인 경우 매핑 카운터를 증가만 시킨다.
  • } else if (PageAnon(new)) page_add_anon_rmap(new, vma, addr);
    • 일반 anon 페이지이면 매핑 카운터를 증가시키고 매핑 카운터가 1(first)  인 경우 vma를 사용하여 page->mapping, page->index를 갱신한다.
  • else page_add_file_rmap(new);
    • 그 외 일반 file 페이지이면 매핑 카운터를 증가시키고 매핑 카운터가 1(first)인 경우 NR_FILE_MAPPED stat을 증가시킨다.
  • update_mmu_cache(vma, addr, ptep);
    • ARMv6 이상에서는 아무런 동작도 수행하지 않고, ARMv6 미만에서 cache coherent를 위한 flush 루틴들이 동작한다.

 

/*              
 * Ensure cache coherency between kernel mapping and userspace mapping
 * of this page.
 *
 * We have three cases to consider:
 *  - VIPT non-aliasing cache: fully coherent so nothing required.
 *  - VIVT: fully aliasing, so we need to handle every alias in our
 *          current VM view.
 *  - VIPT aliasing: need to handle one alias in our current VM view.
 *      
 * If we need to handle aliasing:
 *  If the page only exists in the page cache and there are no user
 *  space mappings, we can be lazy and remember that we may have dirty
 *  kernel cache lines for later.  Otherwise, we assume we have
 *  aliasing mappings.
 *
 * Note that we disable the lazy flush for SMP configurations where
 * the cache maintenance operations are not automatically broadcasted.
 */
void flush_dcache_page(struct page *page) 
{       
        struct address_space *mapping;

        /* 
         * The zero page is never written to, so never has any dirty
         * cache lines, and therefore never needs to be flushed.
         */
        if (page == ZERO_PAGE(0))
                return;
          
        mapping = page_mapping(page);
        
        if (!cache_ops_need_broadcast() &&
            mapping && !page_mapped(page))
                clear_bit(PG_dcache_clean, &page->flags); 
        else {
                __flush_dcache_page(mapping, page);
                if (mapping && cache_is_vivt())
                        __flush_dcache_aliases(mapping, page);
                else if (mapping)
                        __flush_icache_all();
                set_bit(PG_dcache_clean, &page->flags);
        }
}
EXPORT_SYMBOL(flush_dcache_page);

해당 페이지의 d-cache를 flush 한다.

  • if (page == ZERO_PAGE(0)) return;
    • zero 페이지는 절대 기록되지 않으므로 dirty 캐시라인을 갖지 않으므로 flush될 필요 없다.
  • mapping = page_mapping(page);
    • 매핑 여부를 알아보기 위해 가져온다.
  • if (!cache_ops_need_broadcast() && mapping && !page_mapped(page)) clear_bit(PG_dcache_clean, &page->flags);
    • 캐시 변경 시 boradcast 기능이 필요 없으면서 페이지의 매핑 카운터가 0 미만인 경우 페이지에서 PG_dcache_clean 플래그를 클리어한다.
    •  cache_ops_need_broadcast()
      • SMP를 사용하지 않거나 ARMv7 이상인 경우 false
        • ARMv7 에서는 SMP 코어간 cache coherent가 동작된다.
  • else { __flush_dcache_page(mapping, page);
    • 해당 페이지의 d-cache에 대해 clean & invalidate 한다.
  • if (mapping && cache_is_vivt()) __flush_dcache_aliases(mapping, page);
    • 매핑 페이지이면서 vivt 캐시를 사용하는 경우 해당 페이지를 share하여 사용하는 모든 가상 주소를 알아내어 flush 한다.
  • else if (mapping) __flush_icache_all(); set_bit(PG_dcache_clean, &page->flags); }
    • 매핑 페이지이면서 vivt 캐시가 아닌 경우는 i-cache를 flush하고 페이지에서 PG_dcache_clean 플래그를 클리어한다.

 

migrate_page_copy()

mm/migrate.c

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

        if (PageHuge(page) || PageTransHuge(page))
                copy_huge_page(newpage, page);
        else
                copy_highpage(newpage, page);

        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 (PageChecked(page))
                SetPageChecked(newpage);
        if (PageMappedToDisk(page))
                SetPageMappedToDisk(newpage);

        if (PageDirty(page)) {
                clear_page_dirty_for_io(page);
                /*
                 * Want to mark the page and the radix tree as dirty, and
                 * redo the accounting that clear_page_dirty_for_io undid,
                 * but we can't use set_page_dirty because that function
                 * is actually a signal that all of the page has become dirty.
                 * Whereas only part of our page may be dirty.
                 */
                if (PageSwapBacked(page))
                        SetPageDirty(newpage);
                else
                        __set_page_dirty_nobuffers(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);

        mlock_migrate_page(newpage, page);
        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().
         */
        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);
}

page를 newpage에 복사한다.

  • if (PageHuge(page) || PageTransHuge(page)) copy_huge_page(newpage, page);
    • huge 페이지를 new 페이지에 복사한다.
  • else copy_highpage(newpage, page);
    • 일반 페이지를 new 페이지에 복사한다.
  • if (PageError(page)) SetPageError(newpage);
    • PG_error 플래그 비트를 복사한다.
  • if (PageReferenced(page)) SetPageReferenced(newpage);
    • PG_referenced 플래그 비트를 복사한다.
  • if (PageUptodate(page)) SetPageUptodate(newpage);
    • PG_uptodate 플래그 비트를 복사한다.
  • if (TestClearPageActive(page)) { SetPageActive(newpage);
    • PG_active 플래그 비트가 설정된 경우 클리어하고 new 페이지에는 설정하게 한다.
  • } else if (TestClearPageUnevictable(page)) SetPageUnevictable(newpage);
    • 그렇지 않고 PG_unevictable 플래그 비트가 설정된 경우 클리어하고 new 페이지에는 설정하게 한다.
  • if (PageChecked(page)) SetPageChecked(newpage);
    • PG_checked 플래그 비트를 복사한다.
  • if (PageMappedToDisk(page)) SetPageMappedToDisk(newpage);
    • PG_mappedtodisk 플래그 비트를 복사한다.
  • if (PageDirty(page)) { clear_page_dirty_for_io(page);
    • 기존 page의 PG_dirty  플래그가 설정된 경우 클리어한다.
  • if (PageSwapBacked(page)) SetPageDirty(newpage); else __set_page_dirty_nobuffers(newpage);
    • PG_swapbacked 플래그 비트가 설정된 경우 new 페이지에 PG_dirty를 설정하고, 그렇지 않은 경우 new 페이지에 PG_dirty가클리어 상태면 설정하면서 radix tree에서 dirty 상태로 바꾼다.
  • cpupid = page_cpupid_xchg_last(page, -1); page_cpupid_xchg_last(newpage, cpupid);
    • page에서 cpuid를 읽고 -1(mask)를 저장한 후 newpage에 cpuid를 저장한다.
  • mlock_migrate_page(newpage, page);
    • page의 PG_mlock 플래그 비트를 복사한다.
  • ksm_migrate_page(newpage, page);
    • CONFIG_KSM 커널 옵션을 사용하는 경우 page에서 statble 노드 정보를 가져와서 기존 page 대신 newpage를 가리키게 한다.
      • KSM(Kernel Same page Merging)이 application이 주소 공간에서 같은 내용을 가진 페이지를 스캔하여 한 페이지로 merge 시킬 수 있도록 하는 기능이다.
      • 참고: How to use the Kernel Samepage Merging feature | kernel.org
  • ClearPageSwapCache(page);
    • 기존 page의 PG_swapcache 플래그 비트를 클리어한다.
  • ClearPagePrivate(page);
    • 기존 page의 PG_private 플래그 비트를 클리어한다.
  • set_page_private(page, 0);
    • 기존 page의 private에 0을 대입한다.
  • if (PageWriteback(newpage)) end_page_writeback(newpage);
    • newpage가 PG_writeback 플래그 설정이 되어 있는 경우 대기하고 있는 태스크들이 newpage에 누적된 경우 그들을 깨운다.

 

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 */
                if (mode != MIGRATE_SYNC)
                        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 한다.

  • if (PageDirty(page)) { if (mode != MIGRATE_SYNC) return -EBUSY; return writeout(mapping, page); }
    • dirty page이면서 동기 migration이 아닌 경우 -EBUSY 에러를 리턴하고 동기 migration인 경우 페이지를 기록하여 dirty 상태를 클리어한 후 리턴한다.
  • if (page_has_private(page) && !try_to_release_page(page, GFP_KERNEL)) return -EAGAIN;
    • page가 PG_private 또는 PG_private2 플래그 비트를 가졌으면서 page에 있는 기존  fs 메타데이터를 release 시도하여 실패한 경우 -EAGAIN 에러로 리턴한다.
  • return migrate_page(mapping, newpage, page, mode);
    • page를 newpage로 migration한다.

 

hugetlb_cgroup_migrate()

mm/hugetlb_cgroup.c

/*              
 * hugetlb_lock will make sure a parallel cgroup rmdir won't happen
 * when we migrate hugepages
 */
void hugetlb_cgroup_migrate(struct page *oldhpage, struct page *newhpage)
{
        struct hugetlb_cgroup *h_cg;
        struct hstate *h = page_hstate(oldhpage);
                
        if (hugetlb_cgroup_disabled())
                return;

        VM_BUG_ON_PAGE(!PageHuge(oldhpage), oldhpage);
        spin_lock(&hugetlb_lock);       
        h_cg = hugetlb_cgroup_from_page(oldhpage);
        set_hugetlb_cgroup(oldhpage, NULL);

        /* move the h_cg details to new cgroup */ 
        set_hugetlb_cgroup(newhpage, h_cg);
        list_move(&newhpage->lru, &h->hugepage_activelist);
        spin_unlock(&hugetlb_lock);
        return;
}

huge page가 migration되면 old hpage로 부터 h_cg를 new hpage로 옮기고 클리어한다. 그런 후 old hpage가 사용한 hstate[]->hugepage_activelist에 new hpage를 추가한다.

  • if (hugetlb_cgroup_disabled()) return;
    • h_cg가 disable되어 있으면 함수를 종료한다.
  • h_cg = hugetlb_cgroup_from_page(oldhpage); set_hugetlb_cgroup(oldhpage, NULL);
    • oldhpage로부터 h_cg를 알아오고 oldhpage의 h_cg에 null을 대입한다.
  • set_hugetlb_cgroup(newhpage, h_cg);
    • h_cg를 newhpage로 옮긴다.
  • list_move(&newhpage->lru, &h->hugepage_activelist);
    • 기존 리스트(cc->freepages)에서 oldhpage가 사용중이던 hstate[]->hugepage_activelist로 옮긴다.

 

hugetlb_cgroup_from_page()

include/linux/hugetlb_cgroup.h

static inline struct hugetlb_cgroup *hugetlb_cgroup_from_page(struct page *page)
{               
        VM_BUG_ON_PAGE(!PageHuge(page), page);
 
        if (compound_order(page) < HUGETLB_CGROUP_MIN_ORDER)
                return NULL;
        return (struct hugetlb_cgroup *)page[2].lru.next;
}

두 번째 page의 lru.next에서 hugetlb_cgoup 정보를 반환한다. 만일 compound order가 HUGETLB_CGROUP_MIN_ORDER보다 작은 경우 null을 반환한다.

 

set_hugetlb_cgroup()

include/linux/hugetlb_cgroup.h

static inline
int set_hugetlb_cgroup(struct page *page, struct hugetlb_cgroup *h_cg)
{
        VM_BUG_ON_PAGE(!PageHuge(page), page);
 
        if (compound_order(page) < HUGETLB_CGROUP_MIN_ORDER)
                return -1;
        page[2].lru.next = (void *)h_cg;
        return 0;
}

hpage에 hugetlb_cgoup 정보를 기록한다.

  • 두 번째 page의 lru.next를 사용하여 저장한다.

 

unmap_and_move()

mm/migrate.c

/*
 * Obtain the lock on page, remove all ptes and migrate the page
 * to the newly allocated page in newpage.
 */
static 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)
{
        int rc = 0;
        int *result = NULL;
        struct page *newpage = get_new_page(page, private, &result);

        if (!newpage)
                return -ENOMEM;

        if (page_count(page) == 1) {
                /* page was freed from under us. So we are done. */
                goto out;
        }

        if (unlikely(PageTransHuge(page)))
                if (unlikely(split_huge_page(page)))
                        goto out;

        rc = __unmap_and_move(page, newpage, force, mode);

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);
                dec_zone_page_state(page, NR_ISOLATED_ANON +
                                page_is_file_cache(page));
                putback_lru_page(page);
        }

        /*
         * If migration was not successful and there's a freeing callback, use
         * it.  Otherwise, putback_lru_page() will drop the reference grabbed
         * during isolation.
         */
        if (rc != MIGRATEPAGE_SUCCESS && put_new_page) {
                ClearPageSwapBacked(newpage);
                put_new_page(newpage, private);
        } else if (unlikely(__is_movable_balloon_page(newpage))) {
                /* drop our reference, page already in the balloon */
                put_page(newpage);
        } else
                putback_lru_page(newpage);

        if (result) {
                if (rc)
                        *result = rc;
                else
                        *result = page_to_nid(newpage);
        }
        return rc;
}

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

  • struct page *newpage = get_new_page(page, private, &result); if (!newpage) return -ENOMEM;
    • free 스캐너로부터 isolation된 cc->freepages 리스트에서 선두의 free 페이지를 가져오는데 만일 반환할 free 페이지가 없으면 free 스캐너를 가동한다. 실패한 경우 메모리 부족으로 함수를 종료한다.
  • if (page_count(page) == 1) { goto out; }
    • page 참조 카운터가 1인 경우 함수를 종료한다.
  • if (unlikely(PageTransHuge(page))) if (unlikely(split_huge_page(page))) goto out;
    • 작은 확률로 trans huge page이면서 작은 확률로 split huge page인 경우 함수를 종료한다.
  • rc = __unmap_and_move(page, newpage, force, mode);
    • 매핑을 푼 후 page를 newpage로 migration 한다.
  • out: if (rc != -EAGAIN) { list_del(&page->lru);
    • migration 결과가 -EAGAIN이 아닌 경우 cc->migratepages 리스트에서 page를 제거한다.
  • dec_zone_page_state(page, NR_ISOLATED_ANON + page_is_file_cache(page));
    • 기존 page 타입에 따라 NR_ISOLATED_ANON 또는 NR_ISOLATED_FILE stat을 감소시킨다.
  •  putback_lru_page(page); }
    • page를 isolate되기 전에 관리하던 적절한 LRU 리스트로 되돌린다.
  • if (rc != MIGRATEPAGE_SUCCESS && put_new_page) { ClearPageSwapBacked(newpage); put_new_page(newpage, private);
    • migration이 실패한 경우 new page의 PG_swapbacked를 클리어하고 cc->freepages로 다시 되돌린다.
  • } else if (unlikely(__is_movable_balloon_page(newpage))) { put_page(newpage);
    • 작은 확률로 newpage가 balloon page인 경우 newpage를 release한다.
  • } else putback_lru_page(newpage);
    • newpage를 isolate되기 전에 관리하던 적절한 LRU 리스트로 되돌린다.
  • if (result) { if (rc) *result = rc; else *result = page_to_nid(newpage); } return rc;
    • result 출력 인수가 지정되었으면 reslut에 에러인 경우 rc 값을, 에러가 아닌 경우 노드 id를 대입하고 함수를 종료한다.

 

__unmap_and_move()

mm/migrate.c

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;

        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
                 */
                if (mode != MIGRATE_SYNC) {
                        rc = -EBUSY;
                        goto out_unlock;
                }
                if (!force)
                        goto out_unlock;
                wait_on_page_writeback(page);
        }

page를 unmapping 하고 newpage에 migration한다.

 

  • if (!trylock_page(page)) { if (!force || mode == MIGRATE_ASYNC) goto out;
    • page에 대한 lock을 시도해서 실패한 경우 migrate 모드가 MIGRATE_ASYNC 이거나 force 옵션이 0인 경우 -EAGAIN 에러로 함수를 종료한다.
  • if (current->flags & PF_MEMALLOC) goto out;
    • 태스크가 PF_MEMALLOC이 설정된 경우 -EAGAIN 에러로 함수를 종료한다.
  • if (PageWriteback(page)) {
    • page에 PG_writeback 플래그 비트가 설정된 경우
  • if (mode != MIGRATE_SYNC) { rc = -EBUSY; goto out_unlock; }
    • 만일 sync migration 모드가 아닌 경우 -EBUSY 에러로 함수를 종료한다.
  •  if (!force) goto out_unlock;
    • 인수 force가 요청되지 않은 경우 -EAGAIN 에러로 함수를 종료한다.
  • wait_on_page_writeback(page);
    • page가 writeback이 완료될 때까지 기다린다.

 

        /*
         * 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.
         */
        if (PageAnon(page) && !PageKsm(page)) {
                /*
                 * Only page_lock_anon_vma_read() understands the subtleties of
                 * getting a hold on an anon_vma from outside one of its mms.
                 */
                anon_vma = page_get_anon_vma(page);
                if (anon_vma) {
                        /*
                         * Anon page
                         */
                } else if (PageSwapCache(page)) {
                        /*
                         * We cannot be sure that the anon_vma of an unmapped
                         * swapcache page is safe to use because we don't
                         * know in advance if the VMA that this page belonged
                         * to still exists. If the VMA and others sharing the
                         * data have been freed, then the anon_vma could
                         * already be invalid.
                         *
                         * To avoid this possibility, swapcache pages get
                         * migrated but are not remapped when migration
                         * completes
                         */
                } else {
                        goto out_unlock;
                }
        }

        if (unlikely(isolated_balloon_page(page))) {
                /*
                 * A ballooned page does not need any special attention from
                 * physical to virtual reverse mapping procedures.
                 * Skip any attempt to unmap PTEs or to remap swap cache,
                 * in order to avoid burning cycles at rmap level, and perform
                 * the page migration right away (proteced by page lock).
                 */
                rc = balloon_page_migrate(newpage, page, mode);
                goto out_unlock;
        }
  • if (PageAnon(page) && !PageKsm(page)) {
    • anon page이면서 KSM page가 아닌 경우
      • PAGE_MAPPING_ANON
        • write 보호된 shared pages
      • PAGE_MAPPING_KSM
        • merged pages
  • anon_vma = page_get_anon_vma(page);
    • anon page에서 anon_vma를 알아온다.
  • if (anon_vma) { } else if (PageSwapCache(page)) { } else { goto out_unlock; }
    • anon_vma가 없으면서 page의 PG_swapcache 플래그 비트가 설정된 경우가 아니면 -EAGAIN 에러로 함수를 종료한다.
  •  if (unlikely(isolated_balloon_page(page))) { rc = balloon_page_migrate(newpage, page, mode); goto out_unlock; }
    • 작은 확률로 page가 balloon page인 경우 balloon page를 newpage로 migration한다.

 

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

        /* Establish migration ptes or remove ptes */
        if (page_mapped(page)) {
                try_to_unmap(page,
                        TTU_MIGRATION|TTU_IGNORE_MLOCK|TTU_IGNORE_ACCESS);
                page_was_mapped = 1;
        }

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

        if (rc && page_was_mapped)
                remove_migration_ptes(page, page);

        /* Drop an anon_vma reference if we took one */
        if (anon_vma)
                put_anon_vma(anon_vma);

out_unlock:
        unlock_page(page);
out:
        return rc;
}
  • if (!page->mapping) { if (page_has_private(page)) { try_to_free_buffers(page); goto out_unlock; } goto skip_unmap; }
    • page의 mapping이 지정되지 않은 경우 skip_unmap 레이블로 이동하되, 단 private page인 경우 buffer를 release 시도한 후 함수를 종료한다.
  • if (page_mapped(page)) { try_to_unmap(page, TTU_MIGRATION|TTU_IGNORE_MLOCK|TTU_IGNORE_ACCESS); page_was_mapped = 1; }
    • page가 매핑된 경우 unmap을 시도한다.
  • skip_unmap: if (!page_mapped(page)) rc = move_to_new_page(newpage, page, page_was_mapped, mode);
    • page가 매핑되지 않은 경우 page를 newpage로 migration한다.
  • if (rc && page_was_mapped) remove_migration_ptes(page, page);
    • page가 매핑되었었던 경우 page의 pte가 swap pte인 경우 vma->vm_page_prot를 기반으로 필요 속성을 추가하여 new 페이지의 pte를 갱신한다.
  • if (anon_vma) put_anon_vma(anon_vma);
    • anon_vma object를 release 한다.

 

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이 완료될 때까지 기다린다.

 

balloon_page_migrate()

mm/balloon_compaction.c

/* move_to_new_page() counterpart for a ballooned page */
int balloon_page_migrate(struct page *newpage,
                         struct page *page, enum migrate_mode mode)
{
        struct balloon_dev_info *balloon = balloon_page_device(page);
        int rc = -EAGAIN;

        /*
         * Block others from accessing the 'newpage' when we get around to
         * establishing additional references. We should be the only one
         * holding a reference to the 'newpage' at this point.
         */
        BUG_ON(!trylock_page(newpage));

        if (WARN_ON(!__is_movable_balloon_page(page))) {
                dump_page(page, "not movable balloon page");
                unlock_page(newpage);
                return rc;
        }

        if (balloon && balloon->migratepage)
                rc = balloon->migratepage(balloon, newpage, page, mode);

        unlock_page(newpage);
        return rc;
}

balloon page page를 newpage로 migration 한다

  • struct balloon_dev_info *balloon = balloon_page_device(page);
    • page->private에 저장된 balloon 디바이스 정보를 가져온다.
  • if (balloon && balloon->migratepage) rc = balloon->migratepage(balloon, newpage, page, mode);
    • balloon 디바이스 드라이버에 등록된 migratepage 핸들러를 호출하여 page를 newpage로 migration 한다.
    • 참고: virtballoon_migratepage() – drivers/virtio/virtio_balloon.c

 

참고

답글 남기기

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