From: Gregory Price <gourry@gourry.net>
To: linux-mm@kvack.org
Cc: linux-kernel@vger.kernel.org, kernel-team@meta.com,
akpm@linux-foundation.org, david@kernel.org, ljs@kernel.org,
liam@infradead.org, vbabka@kernel.org, rppt@kernel.org,
surenb@google.com, mhocko@suse.com, mingo@redhat.com,
peterz@infradead.org, juri.lelli@redhat.com,
vincent.guittot@linaro.org, dietmar.eggemann@arm.com,
rostedt@goodmis.org, bsegall@google.com, mgorman@suse.de,
vschneid@redhat.com, kprateek.nayak@amd.com, ziy@nvidia.com,
baolin.wang@linux.alibaba.com, nico.pache@linux.dev,
ryan.roberts@arm.com, dev.jain@arm.com, baohua@kernel.org,
lance.yang@linux.dev, usama.arif@linux.dev, kas@kernel.org,
matthew.brost@intel.com, joshua.hahnjy@gmail.com,
rakie.kim@sk.com, byungchul@sk.com, gourry@gourry.net,
ying.huang@linux.alibaba.com, apopple@nvidia.com,
jannh@google.com, pfalcato@suse.de, hannes@cmpxchg.org,
shy828301@gmail.com, raghavendra.kt@amd.com,
stable@vger.kernel.org
Subject: [PATCH v3 1/7] mm: support promotion-only NUMA hinting scans
Date: Tue, 22 Sep 2026 14:29:22 -0400 [thread overview]
Message-ID: <20260922182928.2199090-2-gourry@gourry.net> (raw)
In-Reply-To: <20260922182928.2199090-1-gourry@gourry.net>
From: "Gregory Price (Meta)" <gourry@gourry.net>
folio_can_map_prot_numa() derives folio eligibility from the global
balancing mode. The mode (normal, tiering, or combined) describes
which balancing mechanisms are enabled (placement vs promotion).
The global setting itself cannot describe the intent of an individual
protection walk because normal-mode tempers its scanning activity based
on a number of heuristics (read-only VMAs, activeness of VMA, etc).
In tiering or combined mode, applying these normal-mode optimizations
to an entire VMA is incorrect and breaks tiering.
- Opting VMAs out of scanning because they became inactive obviously
breaks tiering - because the intent is to identify when a VMA
becomes active. Applying it in tiering modes causes
- Opting Read-only file VMAs out of scanning is just incorrect for
tiering modes because its intent is to prevent bouncing between
sockets (east-west), while tiering controls tier migration
(north-south). The result is hot read-only files can overload
lower tier bandwidth.
Add MM_CP_PROT_NUMA_PROMO_ONLY and let task_numa_work() select the type
of walk. Build the change-protection flags there and pass them unchanged
through change_prot_numa() so its PTE/PMD paths use the same decision.
Have folio_can_map_prot_numa() derive the single-threaded private state at
the point of use instead of adding another precomputed boolean to the
protection-walk interface. This keeps the interface focused on scan intent
and avoids plumbing VMA-derived state beside cp_flags.
The tradeoff for the cleaner interface is an atomic mm_users read for each
folio, rather than once per PTE range. Check the promotion-only top-tier
exclusion bit first as a mild optimization.
The scan-intent interface is required by the following memory-tiering
fixes and must accompany them when backported.
Fixes: c574bbe91703 ("NUMA balancing: optimize page placement for memory tiering system")
Cc: stable@vger.kernel.org
Suggested-by: David Hildenbrand <david@kernel.org>
Link: https://lore.kernel.org/r/4d2853c9-edf4-4685-b186-7214ed6abd84@kernel.org
Assisted-by: LLM
Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
include/linux/mm.h | 6 ++++--
kernel/sched/fair.c | 7 ++++++-
mm/huge_memory.c | 3 +--
mm/internal.h | 4 ++--
mm/mempolicy.c | 27 ++++++++++++---------------
mm/mprotect.c | 7 +------
6 files changed, 26 insertions(+), 28 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index e3d29f87567f8..beab621e6f88d 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3608,6 +3608,8 @@ int get_cmdline(struct task_struct *task, char *buffer, int buflen);
#define MM_CP_UFFD_RWP_RESOLVE (1UL << 5) /* resolve rwp */
#define MM_CP_UFFD_RWP_ALL (MM_CP_UFFD_RWP | \
MM_CP_UFFD_RWP_RESOLVE)
+/* Whether a MM_CP_PROT_NUMA change is for promotion only */
+#define MM_CP_PROT_NUMA_PROMO_ONLY (1UL << 6)
bool can_change_pte_writable(struct vm_area_struct *vma, unsigned long addr,
pte_t pte);
@@ -4980,8 +4982,8 @@ static inline void vma_set_page_prot(struct vm_area_struct *vma)
void vma_set_file(struct vm_area_struct *vma, struct file *file);
#ifdef CONFIG_NUMA_BALANCING
-unsigned long change_prot_numa(struct vm_area_struct *vma,
- unsigned long start, unsigned long end);
+unsigned long change_prot_numa(struct vm_area_struct *vma, unsigned long start,
+ unsigned long end, unsigned long cp_flags);
#endif
struct vm_area_struct *find_extend_vma_locked(struct mm_struct *,
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index ae6c1a606eb5d..9f544e3df9490 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -4124,6 +4124,7 @@ static void task_numa_work(struct callback_head *work)
struct mm_struct *mm = p->mm;
u64 runtime = p->se.sum_exec_runtime;
struct vm_area_struct *vma;
+ unsigned long cp_flags = MM_CP_PROT_NUMA;
unsigned long start, end;
unsigned long nr_pte_updates = 0;
long pages, virtpages;
@@ -4131,6 +4132,9 @@ static void task_numa_work(struct callback_head *work)
bool vma_pids_skipped;
bool vma_pids_forced = false;
+ if (!(READ_ONCE(sysctl_numa_balancing_mode) & NUMA_BALANCING_NORMAL))
+ cp_flags |= MM_CP_PROT_NUMA_PROMO_ONLY;
+
WARN_ON_ONCE(p != container_of(work, struct task_struct, numa_work));
work->next = work;
@@ -4307,7 +4311,8 @@ static void task_numa_work(struct callback_head *work)
start = max(start, vma->vm_start);
end = ALIGN(start + (pages << PAGE_SHIFT), HPAGE_SIZE);
end = min(end, vma->vm_end);
- nr_pte_updates = change_prot_numa(vma, start, end);
+ nr_pte_updates = change_prot_numa(vma, start, end,
+ cp_flags);
/*
* Try to scan sysctl_numa_balancing_size worth of
diff --git a/mm/huge_memory.c b/mm/huge_memory.c
index 493b29cabee59..da9cf87903a55 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -2769,8 +2769,7 @@ int change_huge_pmd(struct mmu_gather *tlb, struct vm_area_struct *vma,
if (is_huge_zero_pmd(*pmd))
goto unlock;
- if (!folio_can_map_prot_numa(pmd_folio(*pmd), vma,
- vma_is_single_threaded_private(vma)))
+ if (!folio_can_map_prot_numa(pmd_folio(*pmd), vma, cp_flags))
goto unlock;
}
/*
diff --git a/mm/internal.h b/mm/internal.h
index 0434dfcfc36f1..cbeaf1fc38cfc 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -1248,11 +1248,11 @@ static inline bool vma_is_single_threaded_private(struct vm_area_struct *vma)
#ifdef CONFIG_NUMA_BALANCING
bool folio_can_map_prot_numa(struct folio *folio, struct vm_area_struct *vma,
- bool is_private_single_threaded);
+ unsigned long cp_flags);
#else
static inline bool folio_can_map_prot_numa(struct folio *folio,
- struct vm_area_struct *vma, bool is_private_single_threaded)
+ struct vm_area_struct *vma, unsigned long cp_flags)
{
return false;
}
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 4c0b8ff1a7e66..1427e1b6213b6 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -847,7 +847,7 @@ static int queue_folios_hugetlb(pte_t *pte, unsigned long hmask,
* folio_can_map_prot_numa() - check whether the folio can map prot numa
* @folio: The folio whose mapping considered for being made NUMA hintable
* @vma: The VMA that the folio belongs to.
- * @is_private_single_threaded: Is this a single-threaded private VMA or not
+ * @cp_flags: Flags describing the protection change
*
* This function checks to see if the folio actually indicates that
* we need to make the mapping one which causes a NUMA hinting fault,
@@ -857,7 +857,7 @@ static int queue_folios_hugetlb(pte_t *pte, unsigned long hmask,
* Return: True if the mapping of the folio needs to be changed, false otherwise.
*/
bool folio_can_map_prot_numa(struct folio *folio, struct vm_area_struct *vma,
- bool is_private_single_threaded)
+ unsigned long cp_flags)
{
int nid;
@@ -880,22 +880,17 @@ bool folio_can_map_prot_numa(struct folio *folio, struct vm_area_struct *vma,
if (folio_is_file_lru(folio) && folio_test_dirty(folio))
return false;
- /*
- * Don't mess with PTEs if folio is already on the node
- * a single-threaded process is running on.
- */
nid = folio_nid(folio);
- if (is_private_single_threaded && (nid == numa_node_id()))
+ /* Promotion-only scans do not mark top-tier folios. */
+ if ((cp_flags & MM_CP_PROT_NUMA_PROMO_ONLY) && node_is_toptier(nid))
return false;
/*
- * Skip scanning top tier node if normal numa
- * balancing is disabled
+ * Don't mess with PTEs if folio is already on the node
+ * a single-threaded process is running on.
*/
- if (!(sysctl_numa_balancing_mode & NUMA_BALANCING_NORMAL) &&
- node_is_toptier(nid))
+ if (vma_is_single_threaded_private(vma) && nid == numa_node_id())
return false;
-
if (folio_use_access_time(folio))
folio_xchg_access_time(folio, jiffies_to_msecs(jiffies));
@@ -907,19 +902,21 @@ bool folio_can_map_prot_numa(struct folio *folio, struct vm_area_struct *vma,
* These are later cleared by a NUMA hinting fault. Depending on these
* faults, pages may be migrated for better NUMA placement.
*
+ * @cp_flags carries the NUMA scan policy through the protection walk.
+ *
* This is assuming that NUMA faults are handled using PROT_NONE. If
* an architecture makes a different choice, it will need further
* changes to the core.
*/
-unsigned long change_prot_numa(struct vm_area_struct *vma,
- unsigned long addr, unsigned long end)
+unsigned long change_prot_numa(struct vm_area_struct *vma, unsigned long addr,
+ unsigned long end, unsigned long cp_flags)
{
struct mmu_gather tlb;
long nr_updated;
tlb_gather_mmu(&tlb, vma->vm_mm);
- nr_updated = change_protection(&tlb, vma, addr, end, MM_CP_PROT_NUMA);
+ nr_updated = change_protection(&tlb, vma, addr, end, cp_flags);
if (nr_updated > 0) {
count_vm_numa_events(NUMA_PTE_UPDATES, nr_updated);
count_memcg_events_mm(vma->vm_mm, NUMA_PTE_UPDATES, nr_updated);
diff --git a/mm/mprotect.c b/mm/mprotect.c
index e59c69cb5a238..7a247ed4cd55f 100644
--- a/mm/mprotect.c
+++ b/mm/mprotect.c
@@ -335,7 +335,6 @@ static long change_pte_range(struct mmu_gather *tlb,
pte_t *pte, oldpte;
spinlock_t *ptl;
long pages = 0;
- bool is_private_single_threaded;
bool prot_numa = cp_flags & MM_CP_PROT_NUMA;
bool uffd_rwp = cp_flags & MM_CP_UFFD_RWP;
bool uffd_wp = cp_flags & MM_CP_UFFD_WP;
@@ -346,9 +345,6 @@ static long change_pte_range(struct mmu_gather *tlb,
if (!pte)
return -EAGAIN;
- if (prot_numa)
- is_private_single_threaded = vma_is_single_threaded_private(vma);
-
flush_tlb_batched_pending(vma->vm_mm);
lazy_mmu_mode_enable();
do {
@@ -383,8 +379,7 @@ static long change_pte_range(struct mmu_gather *tlb,
* must set protnone regardless of NUMA placement.
*/
if (prot_numa &&
- !folio_can_map_prot_numa(folio, vma,
- is_private_single_threaded)) {
+ !folio_can_map_prot_numa(folio, vma, cp_flags)) {
/* determine batch to skip */
nr_ptes = mprotect_folio_pte_batch(folio,
--
2.53.0-Meta
next prev parent reply other threads:[~2026-09-22 18:29 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-22 18:29 [PATCH v3 0/7] sched/numa: stop VMA scan filters from gating promotion Gregory Price
2026-09-22 18:29 ` Gregory Price [this message]
2026-09-24 11:45 ` [PATCH v3 1/7] mm: support promotion-only NUMA hinting scans David Hildenbrand (Arm)
2026-09-24 14:08 ` Gregory Price
2026-09-22 18:29 ` [PATCH v3 2/7] mm: allow shared folios to be promoted to a fast tier Gregory Price
2026-09-24 11:59 ` David Hildenbrand (Arm)
2026-09-24 14:07 ` Gregory Price
2026-09-24 15:32 ` David Hildenbrand (Arm)
2026-09-24 15:37 ` Gregory Price
2026-09-24 15:42 ` Zi Yan
2026-09-22 18:29 ` [PATCH v3 3/7] sched/numa: scan read-only file mappings in tiering mode Gregory Price
2026-09-24 20:53 ` David Hildenbrand (Arm)
2026-09-25 0:35 ` Gregory Price
2026-09-22 18:29 ` [PATCH v3 4/7] sched/numa: separate VMA placement from scan continuation Gregory Price
2026-09-22 18:29 ` [PATCH v3 5/7] sched/numa: scan PID-inactive VMAs for promotion Gregory Price
2026-09-22 18:29 ` [PATCH v3 6/7] mm: use BIT() for change_protection() flags Gregory Price
2026-09-24 20:38 ` David Hildenbrand (Arm)
2026-09-22 18:29 ` [PATCH v3 7/7] mm: use VMA flag helpers in NUMA balancing Gregory Price
2026-09-24 20:39 ` David Hildenbrand (Arm)
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260922182928.2199090-2-gourry@gourry.net \
--to=gourry@gourry.net \
--cc=akpm@linux-foundation.org \
--cc=apopple@nvidia.com \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=bsegall@google.com \
--cc=byungchul@sk.com \
--cc=david@kernel.org \
--cc=dev.jain@arm.com \
--cc=dietmar.eggemann@arm.com \
--cc=hannes@cmpxchg.org \
--cc=jannh@google.com \
--cc=joshua.hahnjy@gmail.com \
--cc=juri.lelli@redhat.com \
--cc=kas@kernel.org \
--cc=kernel-team@meta.com \
--cc=kprateek.nayak@amd.com \
--cc=lance.yang@linux.dev \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=matthew.brost@intel.com \
--cc=mgorman@suse.de \
--cc=mhocko@suse.com \
--cc=mingo@redhat.com \
--cc=nico.pache@linux.dev \
--cc=peterz@infradead.org \
--cc=pfalcato@suse.de \
--cc=raghavendra.kt@amd.com \
--cc=rakie.kim@sk.com \
--cc=rostedt@goodmis.org \
--cc=rppt@kernel.org \
--cc=ryan.roberts@arm.com \
--cc=shy828301@gmail.com \
--cc=stable@vger.kernel.org \
--cc=surenb@google.com \
--cc=usama.arif@linux.dev \
--cc=vbabka@kernel.org \
--cc=vincent.guittot@linaro.org \
--cc=vschneid@redhat.com \
--cc=ying.huang@linux.alibaba.com \
--cc=ziy@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®