2782 lines · 76.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Memory Migration functionality - linux/mm/migrate.c * * Copyright (C) 2006 Silicon Graphics, Inc., Christoph Lameter * * Page migration was first developed in the context of the memory hotplug * project. The main authors of the migration code are: * * IWAMOTO Toshihiro <iwamoto@valinux.co.jp> * Hirokazu Takahashi <taka@valinux.co.jp> * Dave Hansen <haveblue@us.ibm.com> * Christoph Lameter */ #include <linux/migrate.h>#include <linux/export.h>#include <linux/swap.h>#include <linux/leafops.h>#include <linux/pagemap.h>#include <linux/buffer_head.h>#include <linux/mm_inline.h>#include <linux/ksm.h>#include <linux/rmap.h>#include <linux/topology.h>#include <linux/cpu.h>#include <linux/cpuset.h>#include <linux/writeback.h>#include <linux/mempolicy.h>#include <linux/vmalloc.h>#include <linux/security.h>#include <linux/backing-dev.h>#include <linux/compaction.h>#include <linux/syscalls.h>#include <linux/compat.h>#include <linux/hugetlb.h>#include <linux/gfp.h>#include <linux/page_idle.h>#include <linux/page_owner.h>#include <linux/sched/mm.h>#include <linux/ptrace.h>#include <linux/memory.h>#include <linux/sched/sysctl.h>#include <linux/memory-tiers.h>#include <linux/pagewalk.h> #include <asm/tlbflush.h> #include <trace/events/migrate.h> #include "internal.h"#include "page_alloc.h"#include "swap.h" static const struct movable_operations *offline_movable_ops;static const struct movable_operations *zsmalloc_movable_ops; int set_movable_ops(const struct movable_operations *ops, enum pagetype type){	/*	 * We only allow for selected types and don't handle concurrent	 * registration attempts yet.	 */	switch (type) {	case PGTY_offline:		if (offline_movable_ops && ops)			return -EBUSY;		offline_movable_ops = ops;		break;	case PGTY_zsmalloc:		if (zsmalloc_movable_ops && ops)			return -EBUSY;		zsmalloc_movable_ops = ops;		break;	default:		return -EINVAL;	}	return 0;}EXPORT_SYMBOL_GPL(set_movable_ops); static const struct movable_operations *page_movable_ops(struct page *page){	VM_WARN_ON_ONCE_PAGE(!page_has_movable_ops(page), page); 	/*	 * If we enable page migration for a page of a certain type by marking	 * it as movable, the page type must be sticky until the page gets freed	 * back to the buddy.	 */	if (PageOffline(page))		/* Only balloon page migration sets PageOffline pages movable. */		return offline_movable_ops;	if (PageZsmalloc(page))		return zsmalloc_movable_ops; 	return NULL;} /** * isolate_movable_ops_page - isolate a movable_ops page for migration * @page: The page. * @mode: The isolation mode. * * Try to isolate a movable_ops page for migration. Will fail if the page is * not a movable_ops page, if the page is already isolated for migration * or if the page was just was released by its owner. * * Once isolated, the page cannot get freed until it is either putback * or migrated. * * Returns true if isolation succeeded, otherwise false. */bool isolate_movable_ops_page(struct page *page, isolate_mode_t mode){	/*	 * TODO: these pages will not be folios in the future. All	 * folio dependencies will have to be removed.	 */	struct folio *folio = folio_get_nontail_page(page);	const struct movable_operations *mops; 	/*	 * Avoid burning cycles with pages that are yet under __free_pages(),	 * or just got freed under us.	 *	 * In case we 'win' a race for a movable page being freed under us and	 * raise its refcount preventing __free_pages() from doing its job	 * the put_page() at the end of this block will take care of	 * release this page, thus avoiding a nasty leakage.	 */	if (!folio)		goto out; 	/*	 * Check for movable_ops pages before taking the page lock because	 * we use non-atomic bitops on newly allocated page flags so	 * unconditionally grabbing the lock ruins page's owner side.	 *	 * Note that once a page has movable_ops, it will stay that way	 * until the page was freed.	 */	if (unlikely(!page_has_movable_ops(page)))		goto out_putfolio; 	/*	 * As movable pages are not isolated from LRU lists, concurrent	 * compaction threads can race against page migration functions	 * as well as race against the releasing a page.	 *	 * In order to avoid having an already isolated movable page	 * being (wrongly) re-isolated while it is under migration,	 * or to avoid attempting to isolate pages being released,	 * lets be sure we have the page lock	 * before proceeding with the movable page isolation steps.	 */	if (unlikely(!folio_trylock(folio)))		goto out_putfolio; 	VM_WARN_ON_ONCE_PAGE(!page_has_movable_ops(page), page);	if (PageMovableOpsIsolated(page))		goto out_no_isolated; 	mops = page_movable_ops(page);	if (WARN_ON_ONCE(!mops))		goto out_no_isolated; 	if (!mops->isolate_page(page, mode))		goto out_no_isolated; 	/* Driver shouldn't use the isolated flag */	VM_WARN_ON_ONCE_PAGE(PageMovableOpsIsolated(page), page);	SetPageMovableOpsIsolated(page);	folio_unlock(folio); 	return true; out_no_isolated:	folio_unlock(folio);out_putfolio:	folio_put(folio);out:	return false;} /** * putback_movable_ops_page - putback an isolated movable_ops page * @page: The isolated page. * * Putback an isolated movable_ops page. * * After the page was putback, it might get freed instantly. */static void putback_movable_ops_page(struct page *page){	/*	 * TODO: these pages will not be folios in the future. All	 * folio dependencies will have to be removed.	 */	struct folio *folio = page_folio(page); 	VM_WARN_ON_ONCE_PAGE(!page_has_movable_ops(page), page);	VM_WARN_ON_ONCE_PAGE(!PageMovableOpsIsolated(page), page);	folio_lock(folio);	page_movable_ops(page)->putback_page(page);	ClearPageMovableOpsIsolated(page);	folio_unlock(folio);	folio_put(folio);} /** * migrate_movable_ops_page - migrate an isolated movable_ops page * @dst: The destination page. * @src: The source page. * @mode: The migration mode. * * Migrate an isolated movable_ops page. * * If the src page was already released by its owner, the src page is * un-isolated (putback) and migration succeeds; the migration core will be the * owner of both pages. * * If the src page was not released by its owner and the migration was * successful, the owner of the src page and the dst page are swapped and * the src page is un-isolated. * * If migration fails, the ownership stays unmodified and the src page * remains isolated: migration may be retried later or the page can be putback. * * TODO: migration core will treat both pages as folios and lock them before * this call to unlock them after this call. Further, the folio refcounts on * src and dst are also released by migration core. These pages will not be * folios in the future, so that must be reworked. * * Returns 0 on success, otherwise a negative error code. */static int migrate_movable_ops_page(struct page *dst, struct page *src,		enum migrate_mode mode){	int rc; 	VM_WARN_ON_ONCE_PAGE(!page_has_movable_ops(src), src);	VM_WARN_ON_ONCE_PAGE(!PageMovableOpsIsolated(src), src);	rc = page_movable_ops(src)->migrate_page(dst, src, mode);	if (!rc)		ClearPageMovableOpsIsolated(src);	return rc;} /* * Put previously isolated pages back onto the appropriate lists * from where they were once taken off for compaction/migration. * * This function shall be used whenever the isolated pageset has been * built from lru, balloon, hugetlbfs page. See isolate_migratepages_range() * and folio_isolate_hugetlb(). */void putback_movable_pages(struct list_head *l){	struct folio *folio;	struct folio *folio2; 	list_for_each_entry_safe(folio, folio2, l, lru) {		if (unlikely(folio_test_hugetlb(folio))) {			folio_putback_hugetlb(folio);			continue;		}		list_del(&folio->lru);		if (unlikely(page_has_movable_ops(&folio->page))) {			putback_movable_ops_page(&folio->page);		} else {			node_stat_mod_folio(folio, NR_ISOLATED_ANON +					folio_is_file_lru(folio), -folio_nr_pages(folio));			folio_putback_lru(folio);		}	}} /* Must be called with an elevated refcount on the non-hugetlb folio */bool isolate_folio_to_list(struct folio *folio, struct list_head *list){	if (folio_test_hugetlb(folio))		return folio_isolate_hugetlb(folio, list); 	if (page_has_movable_ops(&folio->page)) {		if (!isolate_movable_ops_page(&folio->page,					      ISOLATE_UNEVICTABLE))			return false;	} else {		if (!folio_isolate_lru(folio))			return false;		node_stat_add_folio(folio, NR_ISOLATED_ANON +				    folio_is_file_lru(folio));	}	list_add(&folio->lru, list);	return true;} static bool try_to_map_unused_to_zeropage(struct page_vma_mapped_walk *pvmw,		struct folio *folio, pte_t old_pte, unsigned long idx){	struct page *page = folio_page(folio, idx);	pte_t newpte; 	if (PageCompound(page) || PageHWPoison(page))		return false; 	VM_BUG_ON_PAGE(!PageAnon(page), page);	VM_BUG_ON_PAGE(!PageLocked(page), page);	VM_BUG_ON_PAGE(pte_present(old_pte), page);	VM_WARN_ON_ONCE_FOLIO(folio_is_device_private(folio), folio); 	if (folio_test_mlocked(folio) || (pvmw->vma->vm_flags & VM_LOCKED) ||	    mm_forbids_zeropage(pvmw->vma->vm_mm))		return false; 	/*	 * The pmd entry mapping the old thp was flushed and the pte mapping	 * this subpage has been non present. If the subpage is only zero-filled	 * then map it to the shared zeropage.	 */	if (!pages_identical(page, ZERO_PAGE(0)))		return false; 	newpte = pte_mkspecial(pfn_pte(zero_pfn(pvmw->address),					pvmw->vma->vm_page_prot)); 	if (pte_swp_soft_dirty(old_pte))		newpte = pte_mksoft_dirty(newpte);	if (pte_swp_uffd(old_pte))		newpte = pte_mkuffd(newpte); 	/* See remove_migration_pte(): restore PAGE_NONE for RWP */	if (pte_swp_uffd(old_pte) && userfaultfd_rwp(pvmw->vma))		newpte = pte_modify(newpte, PAGE_NONE); 	set_pte_at(pvmw->vma->vm_mm, pvmw->address, pvmw->pte, newpte); 	dec_mm_counter(pvmw->vma->vm_mm, mm_counter(folio));	return true;} struct rmap_walk_arg {	struct folio *folio;	bool map_unused_to_zeropage;}; /* * Restore a potential migration pte to a working pte entry */static bool remove_migration_pte(struct folio *folio,		struct vm_area_struct *vma, unsigned long addr, void *arg){	struct rmap_walk_arg *rmap_walk_arg = arg;	DEFINE_FOLIO_VMA_WALK(pvmw, rmap_walk_arg->folio, vma, addr, PVMW_SYNC | PVMW_MIGRATION); 	while (page_vma_mapped_walk(&pvmw)) {		rmap_t rmap_flags = RMAP_NONE;		unsigned long idx = 0;		softleaf_t entry;		struct page *new;		pte_t old_pte;		pte_t pte; #ifdef CONFIG_ARCH_HAS_PMD_SOFTLEAVES		/* PMD-mapped THP migration entry */		if (!pvmw.pte) {			VM_BUG_ON_FOLIO(folio_test_hugetlb(folio) ||					!folio_test_pmd_mappable(folio), folio);			remove_migration_pmd(&pvmw, folio);			continue;		}#endif		if (folio_test_hugetlb(folio))			old_pte = huge_ptep_get(vma->vm_mm, pvmw.address,						pvmw.pte);		else			old_pte = ptep_get(pvmw.pte); 		entry = softleaf_from_pte(old_pte);		if (folio_test_large(folio) && !folio_test_hugetlb(folio))			idx = softleaf_to_pfn(entry) - pvmw.pfn; 		if (rmap_walk_arg->map_unused_to_zeropage &&		    try_to_map_unused_to_zeropage(&pvmw, folio, old_pte, idx))			continue; 		folio_get(folio);		new = folio_page(folio, idx);		pte = mk_pte(new, READ_ONCE(vma->vm_page_prot));		if (!softleaf_is_migration_young(entry))			pte = pte_mkold(pte);		if (folio_test_dirty(folio) && softleaf_is_migration_dirty(entry))			pte = pte_mkdirty(pte);		if (pte_swp_soft_dirty(old_pte))			pte = pte_mksoft_dirty(pte);		else			pte = pte_clear_soft_dirty(pte); 		if (softleaf_is_migration_write(entry))			pte = pte_mkwrite(pte, vma);		else if (pte_swp_uffd(old_pte))			pte = pte_mkuffd(pte); 		/* See do_swap_page(): restore PAGE_NONE for RWP */		if (pte_swp_uffd(old_pte) && userfaultfd_rwp(vma))			pte = pte_modify(pte, PAGE_NONE); 		if (folio_test_anon(folio) && !softleaf_is_migration_read(entry))			rmap_flags |= RMAP_EXCLUSIVE; 		if (unlikely(is_device_private_page(new))) {			if (pte_write(pte))				entry = make_writable_device_private_entry(							page_to_pfn(new));			else				entry = make_readable_device_private_entry(							page_to_pfn(new));			pte = softleaf_to_pte(entry);			if (pte_swp_soft_dirty(old_pte))				pte = pte_swp_mksoft_dirty(pte);			if (pte_swp_uffd(old_pte))				pte = pte_swp_mkuffd(pte);		} #ifdef CONFIG_HUGETLB_PAGE		if (folio_test_hugetlb(folio)) {			struct hstate *h = hstate_vma(vma);			unsigned int shift = huge_page_shift(h);			unsigned long psize = huge_page_size(h); 			pte = arch_make_huge_pte(pte, shift, vma->vm_flags);			if (folio_test_anon(folio))				hugetlb_add_anon_rmap(folio, vma, pvmw.address,						      rmap_flags);			else				hugetlb_add_file_rmap(folio);			set_huge_pte_at(vma->vm_mm, pvmw.address, pvmw.pte, pte,					psize);		} else#endif		{			if (folio_test_anon(folio))				folio_add_anon_rmap_pte(folio, new, vma,							pvmw.address, rmap_flags);			else				folio_add_file_rmap_pte(folio, new, vma);			set_pte_at(vma->vm_mm, pvmw.address, pvmw.pte, pte);		}		if (READ_ONCE(vma->vm_flags) & VM_LOCKED)			mlock_drain_local(); 		trace_remove_migration_pte(pvmw.address, pte_val(pte),					   compound_order(new)); 		/* No need to invalidate - it was non-present before */		update_mmu_cache(vma, pvmw.address, pvmw.pte);	} 	return true;} /* * Get rid of all migration entries and replace them by * references to the indicated page. */void remove_migration_ptes(struct folio *src, struct folio *dst,		enum ttu_flags flags){	struct rmap_walk_arg rmap_walk_arg = {		.folio = src,		.map_unused_to_zeropage = flags & TTU_USE_SHARED_ZEROPAGE,	}; 	struct rmap_walk_control rwc = {		.rmap_one = remove_migration_pte,		.arg = &rmap_walk_arg,	}; 	VM_BUG_ON_FOLIO((flags & TTU_USE_SHARED_ZEROPAGE) && (src != dst), src); 	if (flags & TTU_RMAP_LOCKED)		rmap_walk_locked(dst, &rwc);	else		rmap_walk(dst, &rwc);} /* * Something used the pte of a page under migration. We need to * get to the page and wait until migration is finished. * When we return from this function the fault will be retried. */void migration_entry_wait(struct mm_struct *mm, pmd_t *pmd,			  unsigned long address){	spinlock_t *ptl;	pte_t *ptep;	pte_t pte;	softleaf_t entry; 

Lines 1–500 of 2782.