[PATCH 4/9] mm/mglru: invalidate empty-walk skip on page fault and migration

Baoquan He <[email protected]>
Newsgroups org.kvack.linux-mm
Message-ID <[email protected]>
empty_map skips an mm on a node for up to K generations after an empty walk.
Notify MGLRU when a page of the mm appears: set the node's bitmap
bit and clear its empty_map bit, so the next aging pass walks it again.

Major paths that map a page into the mm:
- mm/memory.c: do_anonymous_page()/finish_fault() (anon and file/COW faults),
  wp_page_copy() (anon COW), do_swap_page() (swap-in).
- mm/huge_memory.c: __do_huge_pmd_anonymous_page() (THP anon faults).
- mm/migrate.c: remove_migration_pte() (folio destination node, incl.
  NUMA-balancing migration).

The transitions are rare (the bitmap bit is usually already set and the
empty-marking bit usually clear), so each is guarded by test_bit() to avoid a
locked RMW bouncing the mm cache line on every fault; the periodic re-scan
covers any path not listed here.

Signed-off-by: Baoquan He <[email protected]>
---
 include/linux/mm_types.h | 19 +++++++++++++++++++
 mm/huge_memory.c         |  3 +++
 mm/memory.c              | 15 +++++++++++++++
 mm/migrate.c             |  4 ++++
 4 files changed, 41 insertions(+)

diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 3738e8877b73..94c1d1fedc9c 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -1521,6 +1521,21 @@ static inline void lru_gen_use_mm(struct mm_struct *mm)
 	WRITE_ONCE(mm->lru_gen.bitmap, -1);
 }
 
+/*
+ * A page of this mm appeared on node @nid (fault or migration): set the
+ * node's bitmap bit and clear the empty-walk skip. Probe first - the
+ * transitions are rare and unconditional locked RMWs would bounce the
+ * mm cache line.
+ */
+static inline void lru_gen_mm_accessed(struct mm_struct *mm, int nid)
+{
+	unsigned long key = nid % BITS_PER_TYPE(mm->lru_gen.bitmap);
+
+	if (!test_bit(key, &mm->lru_gen.bitmap))
+		set_bit(key, &mm->lru_gen.bitmap);
+	if (test_bit(key, &mm->lru_gen.empty_map))
+		clear_bit(key, &mm->lru_gen.empty_map);
+}
 #else /* !CONFIG_LRU_GEN_WALKS_MMU */
 
 static inline void lru_gen_add_mm(struct mm_struct *mm)
@@ -1543,6 +1558,10 @@ static inline void lru_gen_use_mm(struct mm_struct *mm)
 {
 }
 
+static inline void lru_gen_mm_accessed(struct mm_struct *mm, int nid)
+{
+}
+
 #endif /* CONFIG_LRU_GEN_WALKS_MMU */
 
 struct vma_iterator {
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index afbb5974bd22..23ae4626a980 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -1418,6 +1418,9 @@ static vm_fault_t __do_huge_pmd_anonymous_page(struct vm_fault *vmf)
 		map_anon_folio_pmd_pf(folio, vmf->pmd, vma, haddr);
 		mm_inc_nr_ptes(vma->vm_mm);
 		spin_unlock(vmf->ptl);
+		/* a new THP of this mm lands on this node */
+		if (lru_gen_enabled())
+			lru_gen_mm_accessed(vma->vm_mm, folio_nid(folio));
 	}
 
 	return 0;
diff --git a/mm/memory.c b/mm/memory.c
index c54943302553..1d7ec0110ef2 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -4076,6 +4076,9 @@ static vm_fault_t wp_page_copy(struct vm_fault *vmf)
 		BUG_ON(unshare && pte_write(entry));
 		set_pte_at(mm, vmf->address, vmf->pte, entry);
 		update_mmu_cache_range(vmf, vma, vmf->address, vmf->pte, 1);
+		/* COW mapped a fresh folio of this mm on this node */
+		if (lru_gen_enabled())
+			lru_gen_mm_accessed(vma->vm_mm, folio_nid(new_folio));
 		if (old_folio) {
 			/*
 			 * Only after switching the pte to the new page may
@@ -5224,6 +5227,9 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
 	set_ptes(vma->vm_mm, address, ptep, pte, nr_pages);
 	arch_do_swap_page_nr(vma->vm_mm, vma, address,
 			pte, pte, nr_pages);
+	/* a swapped-in page of this mm lands on this node */
+	if (lru_gen_enabled())
+		lru_gen_mm_accessed(vma->vm_mm, folio_nid(folio));
 
 	/*
 	 * Remove the swap entry and conditionally try to free up the swapcache.
@@ -5516,6 +5522,10 @@ static vm_fault_t do_anonymous_page(struct vm_fault *vmf)
 		folio_put(folio);
 		return handle_userfault(vmf, VM_UFFD_MISSING);
 	}
+	/* a new page of this mm lands on this node: invalidate any empty skip */
+	if (lru_gen_enabled())
+		lru_gen_mm_accessed(vma->vm_mm, folio_nid(folio));
+
 	map_anon_folio_pte_pf(folio, vmf->pte, vma, addr,
 			      vmf_orig_pte_uffd_wp(vmf));
 unlock:
@@ -5776,6 +5786,11 @@ vm_fault_t finish_fault(struct vm_fault *vmf)
 		page = vmf->page;
 
 	folio = page_folio(page);
+
+	/* mapping a page of this mm on this node: invalidate any empty skip */
+	if (lru_gen_enabled())
+		lru_gen_mm_accessed(vma->vm_mm, folio_nid(folio));
+
 	/*
 	 * check even for read faults because we might have lost our CoWed
 	 * page
diff --git a/mm/migrate.c b/mm/migrate.c
index 15b45832bcfa..26b8b34988d2 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -354,6 +354,10 @@ static bool remove_migration_pte(struct folio *folio,
 	struct rmap_walk_arg *rmap_walk_arg = arg;
 	DEFINE_FOLIO_VMA_WALK(pvmw, rmap_walk_arg->folio, vma, addr, PVMW_SYNC | PVMW_MIGRATION);
 
+	/* the folio ends up on folio_nid(): notify MGLRU for this mm */
+	if (lru_gen_enabled())
+		lru_gen_mm_accessed(vma->vm_mm, folio_nid(folio));
+
 	while (page_vma_mapped_walk(&pvmw)) {
 		rmap_t rmap_flags = RMAP_NONE;
 		unsigned long idx = 0;
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.