* [PATCH v4 0/7] sched/numa: stop VMA scan filters from gating promotion
@ 2026-09-30 11:21 Gregory Price
2026-09-30 11:22 ` [PATCH v4 1/7] mm: support promotion-only NUMA hinting scans Gregory Price
` (6 more replies)
0 siblings, 7 replies; 14+ messages in thread
From: Gregory Price @ 2026-09-30 11:21 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
surenb, mhocko, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, ziy, baolin.wang, nico.pache, ryan.roberts,
dev.jain, baohua, lance.yang, usama.arif, kas, gourry,
joshua.hahnjy, rakie.kim, ying.huang, matthew.brost, byungchul,
apopple, jannh, pfalcato, hannes, shy828301, osalvador,
raghavendra.kt
NUMA balancing uses hinting faults for both task placement and
memory-tier promotion. Several filters designed to avoid unproductive
socket-placement faults also prevent promotion.
In a production deployment using kernel.numa_balancing=2 on a host
with 768 GB of DRAM and 256 GB of CXL memory running two ~430 GB
database workloads with large shmem VMAs - we discovered poor NUMA
balancing behavior:
Before patch series:
- 150-200 GB/s DRAM bandwidth
- 40-45 GB/s CXL bandwidth, saturating the device
- request latency above 5 ms with longer tails
After patch series:
- 250+ GB/s sustained DRAM bandwidth
- approximately 5-10 GB/s sustained CXL bandwidth
- request latency between 800 us and 2 ms
The core issue is that the global balancing mode says which mechanisms
are enabled, but it cannot describe the intent of an individual
VMA/PTE walk (placement vs promotion).
This plumbs the scan reasoning into the prot_none injection while
retaining the placement-scan filtering.
Patches 1-5 form the backportable fix set:
1. Add promotion-only NUMA protection walks and derive private VMA
state in the folio eligibility check.
2. Permit eligible shared folios to be promoted to a fast tier and
rename the promotion predicate to folio_numab_promotable().
3. Scan mappings covered by the legacy file placement filter using
promotion-only scans.
4. Separate VMA placement eligibility from partial-scan continuation.
5. Scan PID-inactive VMAs for promotion and account for placement scans
separately.
Patches 4 and 5 are technically part of the same fix, but are separated
to make it easier to review - they must be backported together.
Patches 6-7 are independently requested cleanups.
6. Use BIT() for the change_protection() flags.
7. Use the VMA flag API in the touched NUMA-balancing code.
Changes in v4:
- 1/7: commit message fix (David).
Hoist balancing mode check into variables.
- 2/7: Rename folio_in_lowtier() to folio_numab_promotable()
Typo fix.
- 3/7: Compute placement_scan once per VMA.
Avoid complex boolean logic (David)
Fix indentation (David).
- 4/7: Avoid complex boolean logic (David)
Store the in-progress state as numab_state->promo_only.
- 5/7: Avoid complex boolean logic (David).
Fix "sticky bit" issue where resumed scans would cause
valid VMAs to drop out of the scanning-eligible set.
v3: https://lore.kernel.org/r/20260922182928.2199090-1-gourry@gourry.net
Gregory Price (Meta) (7):
mm: support promotion-only NUMA hinting scans
mm: allow shared folios to be promoted to a fast tier
sched/numa: scan read-only file mappings in tiering mode
sched/numa: separate VMA placement from scan continuation
sched/numa: scan PID-inactive VMAs for promotion
mm: use BIT() for change_protection() flags
mm: use VMA flag helpers in NUMA balancing
include/linux/mm.h | 23 +++++----
include/linux/mm_types.h | 13 +++++
kernel/sched/fair.c | 100 ++++++++++++++++++++++++++++-----------
mm/huge_memory.c | 3 +-
mm/internal.h | 6 +--
mm/memory-tiers.c | 9 ++--
mm/memory.c | 2 +-
mm/mempolicy.c | 37 ++++++++-------
mm/migrate.c | 13 +++--
mm/mprotect.c | 7 +--
10 files changed, 140 insertions(+), 73 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 1/7] mm: support promotion-only NUMA hinting scans
2026-09-30 11:21 [PATCH v4 0/7] sched/numa: stop VMA scan filters from gating promotion Gregory Price
@ 2026-09-30 11:22 ` Gregory Price
2026-09-30 11:22 ` [PATCH v4 2/7] mm: allow shared folios to be promoted to a fast tier Gregory Price
` (5 subsequent siblings)
6 siblings, 0 replies; 14+ messages in thread
From: Gregory Price @ 2026-09-30 11:22 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
surenb, mhocko, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, ziy, baolin.wang, nico.pache, ryan.roberts,
dev.jain, baohua, lance.yang, usama.arif, kas, gourry,
joshua.hahnjy, rakie.kim, ying.huang, matthew.brost, byungchul,
apopple, jannh, pfalcato, hannes, shy828301, osalvador,
raghavendra.kt, stable
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 VMAs to become
permanently stranded on lower tiers.
- 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 from a single read of the balancing mode. 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>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
---
include/linux/mm.h | 6 ++++--
kernel/sched/fair.c | 9 ++++++++-
mm/huge_memory.c | 3 +--
mm/internal.h | 4 ++--
mm/mempolicy.c | 26 ++++++++++++--------------
mm/mprotect.c | 7 +------
6 files changed, 28 insertions(+), 27 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index c038d06825c3..623ae61e52cb 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3609,6 +3609,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);
@@ -4984,8 +4986,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 3fe1d35b17dc..c3dfa398d0bd 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -4127,11 +4127,14 @@ static bool vma_is_accessed(struct mm_struct *mm, struct vm_area_struct *vma)
*/
static void task_numa_work(struct callback_head *work)
{
+ const unsigned int numab_mode = READ_ONCE(sysctl_numa_balancing_mode);
+ const bool balancing = numab_mode & NUMA_BALANCING_NORMAL;
unsigned long migrate, next_scan, now = jiffies;
struct task_struct *p = current;
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;
@@ -4139,6 +4142,9 @@ static void task_numa_work(struct callback_head *work)
bool vma_pids_skipped;
bool vma_pids_forced = false;
+ if (!balancing)
+ cp_flags |= MM_CP_PROT_NUMA_PROMO_ONLY;
+
WARN_ON_ONCE(p != container_of(work, struct task_struct, numa_work));
work->next = work;
@@ -4315,7 +4321,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 ddc631a388b9..b844b63549a6 100644
--- a/mm/huge_memory.c
+++ b/mm/huge_memory.c
@@ -2768,8 +2768,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 0434dfcfc36f..cbeaf1fc38cf 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 4c0b8ff1a7e6..8ff37f60b710 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,20 +880,16 @@ 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))
@@ -907,19 +903,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 e59c69cb5a23..7a247ed4cd55 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.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 2/7] mm: allow shared folios to be promoted to a fast tier
2026-09-30 11:21 [PATCH v4 0/7] sched/numa: stop VMA scan filters from gating promotion Gregory Price
2026-09-30 11:22 ` [PATCH v4 1/7] mm: support promotion-only NUMA hinting scans Gregory Price
@ 2026-09-30 11:22 ` Gregory Price
2026-10-01 10:34 ` David Hildenbrand (Arm)
2026-09-30 11:22 ` [PATCH v4 3/7] sched/numa: scan read-only file mappings in tiering mode Gregory Price
` (4 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Gregory Price @ 2026-09-30 11:22 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
surenb, mhocko, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, ziy, baolin.wang, nico.pache, ryan.roberts,
dev.jain, baohua, lance.yang, usama.arif, kas, gourry,
joshua.hahnjy, rakie.kim, ying.huang, matthew.brost, byungchul,
apopple, jannh, pfalcato, hannes, shy828301, osalvador,
raghavendra.kt, stable
From: "Gregory Price (Meta)" <gourry@gourry.net>
NUMA balancing rejects shared copy-on-write folios and executable
file folios mapped by multiple processes to avoid east-west
migration bouncing. These checks break promotion from slow memory.
Allow such folios to participate when the folio being migrated is
a low-tier folio and the destination is top-tier (south->north).
This allows promotion, but prevents east-west or north->south
migrations (north->south is handled by reclaim demotion).
Keep the existing restrictions for ordinary placement and for
migrations that are not slow-to-top-tier promotions.
Rename folio_use_access_time() to folio_numab_promotable() so the name
captures the combined memory-tiering and source-tier test. The helper
retains its existing behavior.
Fixes: c574bbe91703 ("NUMA balancing: optimize page placement for memory tiering system")
Cc: stable@vger.kernel.org
Suggested-by: Zi Yan <ziy@nvidia.com>
Link: https://lore.kernel.org/r/DLHS4KFPQ86I.1J4LN3352RI71@nvidia.com
Assisted-by: LLM
Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
include/linux/mm.h | 5 +++--
kernel/sched/fair.c | 2 +-
mm/memory-tiers.c | 9 ++++++---
mm/memory.c | 2 +-
mm/mempolicy.c | 11 ++++++++---
mm/migrate.c | 13 +++++++++----
6 files changed, 28 insertions(+), 14 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 623ae61e52cb..69107e877fd1 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2672,7 +2672,7 @@ static inline void vma_set_access_pid_bit(struct vm_area_struct *vma)
}
}
-bool folio_use_access_time(struct folio *folio);
+bool folio_numab_promotable(struct folio *folio);
#else /* !CONFIG_NUMA_BALANCING */
static inline int folio_xchg_last_cpupid(struct folio *folio, int cpupid)
{
@@ -2726,7 +2726,8 @@ static inline bool cpupid_match_pid(struct task_struct *task, int cpupid)
static inline void vma_set_access_pid_bit(struct vm_area_struct *vma)
{
}
-static inline bool folio_use_access_time(struct folio *folio)
+
+static inline bool folio_numab_promotable(struct folio *folio)
{
return false;
}
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index c3dfa398d0bd..3be18cf10eca 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -2742,7 +2742,7 @@ bool should_numa_migrate_memory(struct task_struct *p, struct folio *folio,
* The pages in slow memory node should be migrated according
* to hot/cold instead of private/shared.
*/
- if (folio_use_access_time(folio)) {
+ if (folio_numab_promotable(folio)) {
struct pglist_data *pgdat;
unsigned long rate_limit;
unsigned int latency, th, def_th;
diff --git a/mm/memory-tiers.c b/mm/memory-tiers.c
index 25e121851b58..086d5695e656 100644
--- a/mm/memory-tiers.c
+++ b/mm/memory-tiers.c
@@ -53,16 +53,19 @@ static const struct bus_type memory_tier_subsys = {
#ifdef CONFIG_NUMA_BALANCING
/**
- * folio_use_access_time - check if a folio reuses cpupid for page access time
+ * folio_numab_promotable - check if NUMA balancing can promote a folio
* @folio: folio to check
*
* folio's _last_cpupid field is repurposed by memory tiering. In memory
* tiering mode, cpupid of slow memory folio (not toptier memory) is used to
* record page access time.
*
- * Return: the folio _last_cpupid is used to record page access time
+ * If memory tiering is disabled, then lowtier has no appreciable meaning,
+ * so we return false (the folio should not be migrated on this distinction).
+ *
+ * Return: true if memory tiering can promote the folio.
*/
-bool folio_use_access_time(struct folio *folio)
+bool folio_numab_promotable(struct folio *folio)
{
return (sysctl_numa_balancing_mode & NUMA_BALANCING_MEMORY_TIERING) &&
!node_is_toptier(folio_nid(folio));
diff --git a/mm/memory.c b/mm/memory.c
index 330cde31bf8b..4ab6db22ad4c 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -6240,7 +6240,7 @@ int numa_migrate_check(struct folio *folio, struct vm_fault *vmf,
* For memory tiering mode, cpupid of slow memory page is used
* to record page access time. So use default value.
*/
- if (folio_use_access_time(folio))
+ if (folio_numab_promotable(folio))
*last_cpupid = (-1 & LAST_CPUPID_MASK);
else
*last_cpupid = folio_last_cpupid(folio);
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 8ff37f60b710..f70f4f795840 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -864,8 +864,13 @@ bool folio_can_map_prot_numa(struct folio *folio, struct vm_area_struct *vma,
if (!folio || folio_is_zone_device(folio) || folio_test_ksm(folio))
return false;
- /* Also skip shared copy-on-write folios */
- if (vma_is_cow_mapping(vma) && folio_maybe_mapped_shared(folio))
+ /*
+ * Shared copy-on-write folios are poor east-west placement candidates.
+ * When tiering is enabled, folio_numab_promotable() identifies a
+ * low-tier folio that needs a hint fault for promotion.
+ */
+ if (vma_is_cow_mapping(vma) && folio_maybe_mapped_shared(folio) &&
+ !folio_numab_promotable(folio))
return false;
/* Folios are pinned and can't be migrated */
@@ -892,7 +897,7 @@ bool folio_can_map_prot_numa(struct folio *folio, struct vm_area_struct *vma,
if (vma_is_single_threaded_private(vma) && nid == numa_node_id())
return false;
- if (folio_use_access_time(folio))
+ if (folio_numab_promotable(folio))
folio_xchg_access_time(folio, jiffies_to_msecs(jiffies));
return true;
diff --git a/mm/migrate.c b/mm/migrate.c
index 7bdcdb57652f..c0f5f3ac71fc 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -2694,14 +2694,19 @@ int migrate_misplaced_folio_prepare(struct folio *folio,
if (folio_is_file_lru(folio)) {
/*
- * Do not migrate file folios that are mapped in multiple
- * processes with execute permissions as they are probably
- * shared libraries.
+ * Limit east-west migration of file folios mapped in
+ * multiple processes with execute permissions as they
+ * are probably shared libraries (limits bouncing).
+ *
+ * If this is a low-tier folio, only migrate if the target
+ * node is toptier (this allows south->north migration while
+ * disallowing east-west migration between slow tiers).
*
* See folio_maybe_mapped_shared() on possible imprecision
* when we cannot easily detect if a folio is shared.
*/
- if ((vma->vm_flags & VM_EXEC) && folio_maybe_mapped_shared(folio))
+ if ((vma->vm_flags & VM_EXEC) && folio_maybe_mapped_shared(folio) &&
+ (!folio_numab_promotable(folio) || !node_is_toptier(node)))
return -EACCES;
/*
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 3/7] sched/numa: scan read-only file mappings in tiering mode
2026-09-30 11:21 [PATCH v4 0/7] sched/numa: stop VMA scan filters from gating promotion Gregory Price
2026-09-30 11:22 ` [PATCH v4 1/7] mm: support promotion-only NUMA hinting scans Gregory Price
2026-09-30 11:22 ` [PATCH v4 2/7] mm: allow shared folios to be promoted to a fast tier Gregory Price
@ 2026-09-30 11:22 ` Gregory Price
2026-10-01 10:34 ` David Hildenbrand (Arm)
2026-09-30 11:22 ` [PATCH v4 4/7] sched/numa: separate VMA placement from scan continuation Gregory Price
` (3 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Gregory Price @ 2026-09-30 11:22 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
surenb, mhocko, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, ziy, baolin.wang, nico.pache, ryan.roberts,
dev.jain, baohua, lance.yang, usama.arif, kas, gourry,
joshua.hahnjy, rakie.kim, ying.huang, matthew.brost, byungchul,
apopple, jannh, pfalcato, hannes, shy828301, osalvador,
raghavendra.kt, stable
From: "Gregory Price (Meta)" <gourry@gourry.net>
Commit 4591ce4f2d22 ("sched/numa: Do not trap hinting faults for
shared libraries") excludes file-backed read-only VMAs from NUMA
hint faulting to prevent east-west placement bouncing.
This filter hides hot file folios on slow memory from promotion.
Scan those VMAs when tiering is enabled, but make their scans promotion
only to retain the existing restriction. Keep the historical VMA
predicate unchanged for backport-ability.
Decide the walk type for each VMA from the balancing-mode snapshot
taken in task_numa_work(). A VMA gets a placement scan only when normal
balancing is enabled and it is not a read-only file mapping. Otherwise
the walk is promotion-only, and without tiering the VMA is skipped as
before.
On a host with 768 GB of DRAM and 256 GB of CXL memory running two
database services using ~430GB each, 169 MB of their shared 185 MB
main binary accumulated on CXL and was permanently stuck there.
With the change, the binary tier residency tracks its runtime hotness.
Fixes: c574bbe91703 ("NUMA balancing: optimize page placement for memory tiering system")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
kernel/sched/fair.c | 32 ++++++++++++++++++++++----------
1 file changed, 22 insertions(+), 10 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 3be18cf10eca..ab9afd3ad49b 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -4128,22 +4128,21 @@ static bool vma_is_accessed(struct mm_struct *mm, struct vm_area_struct *vma)
static void task_numa_work(struct callback_head *work)
{
const unsigned int numab_mode = READ_ONCE(sysctl_numa_balancing_mode);
+ const bool tiering = numab_mode & NUMA_BALANCING_MEMORY_TIERING;
const bool balancing = numab_mode & NUMA_BALANCING_NORMAL;
unsigned long migrate, next_scan, now = jiffies;
struct task_struct *p = current;
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 cp_flags;
unsigned long start, end;
unsigned long nr_pte_updates = 0;
long pages, virtpages;
struct vma_iterator vmi;
bool vma_pids_skipped;
bool vma_pids_forced = false;
-
- if (!balancing)
- cp_flags |= MM_CP_PROT_NUMA_PROMO_ONLY;
+ bool placement_scan;
WARN_ON_ONCE(p != container_of(work, struct task_struct, numa_work));
@@ -4231,13 +4230,22 @@ static void task_numa_work(struct callback_head *work)
}
/*
- * Shared library pages mapped by multiple processes are not
- * migrated as it is expected they are cache replicated. Avoid
- * hinting faults in read-only file-backed mappings or the vDSO
- * as migrating the pages will be of marginal benefit.
+ * Shared library pages mapped by multiple processes are limited
+ * to south->north migrations as it is expected they are cache
+ * replicated. The benefit of east-west migration in this case
+ * is at best marginal and may be harmful due to TLB/cache
+ * invalidation.
+ *
+ * Allow promotion as a cold page incurring many cache-misses
+ * under cache pressure can drive considerable bandwidth.
*/
- if (!vma->vm_mm ||
- (vma->vm_file && (vma->vm_flags & (VM_READ|VM_WRITE)) == (VM_READ))) {
+ placement_scan = balancing;
+ if (placement_scan && vma->vm_file) {
+ if ((vma->vm_flags & (VM_READ | VM_WRITE)) == VM_READ)
+ placement_scan = false;
+ }
+
+ if (!vma->vm_mm || (!placement_scan && !tiering)) {
trace_sched_skip_vma_numa(mm, vma, NUMAB_SKIP_SHARED_RO);
continue;
}
@@ -4317,6 +4325,10 @@ static void task_numa_work(struct callback_head *work)
continue;
}
+ cp_flags = MM_CP_PROT_NUMA;
+ if (!placement_scan)
+ cp_flags |= MM_CP_PROT_NUMA_PROMO_ONLY;
+
do {
start = max(start, vma->vm_start);
end = ALIGN(start + (pages << PAGE_SHIFT), HPAGE_SIZE);
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 4/7] sched/numa: separate VMA placement from scan continuation
2026-09-30 11:21 [PATCH v4 0/7] sched/numa: stop VMA scan filters from gating promotion Gregory Price
` (2 preceding siblings ...)
2026-09-30 11:22 ` [PATCH v4 3/7] sched/numa: scan read-only file mappings in tiering mode Gregory Price
@ 2026-09-30 11:22 ` Gregory Price
2026-10-01 10:44 ` David Hildenbrand (Arm)
2026-09-30 11:22 ` [PATCH v4 5/7] sched/numa: scan PID-inactive VMAs for promotion Gregory Price
` (2 subsequent siblings)
6 siblings, 1 reply; 14+ messages in thread
From: Gregory Price @ 2026-09-30 11:22 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
surenb, mhocko, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, ziy, baolin.wang, nico.pache, ryan.roberts,
dev.jain, baohua, lance.yang, usama.arif, kas, gourry,
joshua.hahnjy, rakie.kim, ying.huang, matthew.brost, byungchul,
apopple, jannh, pfalcato, hannes, shy828301, osalvador,
raghavendra.kt, stable
From: "Gregory Price (Meta)" <gourry@gourry.net>
vma_is_accessed() returns true both when a VMA needs placement
(east-west) sampling and when a VMA is already mid-scan across
multiple scan windows.
Callers cannot distinguish placement eligibility from scan progress.
Rename the helper to vma_needs_placement_scan() and handle continuation
state in task_numa_work(). Record whether an in-progress VMA scan is
promotion-only so a scan resumed in a later scan window finishes with
the policy it started with. The zeroed initial state places no
restriction, so a resumed scan that finds freshly allocated NUMAB state
falls back to the default policy.
This separation allows PID-inactive VMAs to be scanned for promotion
without treating scan continuation as evidence of placement eligibility.
It is a prerequisite for the following fix and must accompany it when
backported.
Fixes: fc137c0ddab2 ("sched/numa: enhance vma scanning logic")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
include/linux/mm_types.h | 6 ++++++
kernel/sched/fair.c | 40 ++++++++++++++++++++++++----------------
2 files changed, 30 insertions(+), 16 deletions(-)
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 1bd3ca7bfff2..d0ac21d02c47 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -803,6 +803,12 @@ struct vma_numab_state {
* A VMA is not eligible for scanning if prev_scan_seq == numa_scan_seq
*/
int prev_scan_seq;
+
+ /*
+ * The in-progress scan of this VMA is promotion-only.
+ * Resumed scans finish with the policy they started with.
+ */
+ bool promo_only;
};
#ifdef __HAVE_PFNMAP_TRACKING
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index ab9afd3ad49b..3b30786ded3b 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -4082,7 +4082,8 @@ static void reset_ptenuma_scan(struct task_struct *p)
p->mm->numa_scan_offset = 0;
}
-static bool vma_is_accessed(struct mm_struct *mm, struct vm_area_struct *vma)
+static bool vma_needs_placement_scan(struct mm_struct *mm,
+ struct vm_area_struct *vma)
{
unsigned long pids;
/*
@@ -4098,15 +4099,6 @@ static bool vma_is_accessed(struct mm_struct *mm, struct vm_area_struct *vma)
if (test_bit(hash_32(current->pid, ilog2(BITS_PER_LONG)), &pids))
return true;
- /*
- * Complete a scan that has already started regardless of PID access, or
- * some VMAs may never be scanned in multi-threaded applications:
- */
- if (mm->numa_scan_offset > vma->vm_start) {
- trace_sched_skip_vma_numa(mm, vma, NUMAB_SKIP_IGNORE_PID);
- return true;
- }
-
/*
* This vma has not been accessed for a while, and if the number
* the threads in the same process is low, which means no other
@@ -4130,6 +4122,7 @@ static void task_numa_work(struct callback_head *work)
const unsigned int numab_mode = READ_ONCE(sysctl_numa_balancing_mode);
const bool tiering = numab_mode & NUMA_BALANCING_MEMORY_TIERING;
const bool balancing = numab_mode & NUMA_BALANCING_NORMAL;
+ bool pid_scan_allowed, placement_due, scan_started;
unsigned long migrate, next_scan, now = jiffies;
struct task_struct *p = current;
struct mm_struct *mm = p->mm;
@@ -4316,15 +4309,29 @@ static void task_numa_work(struct callback_head *work)
}
/*
- * Do not scan the VMA if task has not accessed it, unless no other
- * VMA candidate exists.
+ * Do not scan the VMA if a task has not accessed it, unless no other
+ * VMA candidate exists. If a scan is already in-progress, finish it,
+ * but track continuation separately from starting a new one.
*/
- if (!vma_pids_forced && !vma_is_accessed(mm, vma)) {
- vma_pids_skipped = true;
- trace_sched_skip_vma_numa(mm, vma, NUMAB_SKIP_PID_INACTIVE);
- continue;
+ placement_due = vma_needs_placement_scan(mm, vma);
+ scan_started = mm->numa_scan_offset > vma->vm_start;
+ pid_scan_allowed = vma_pids_forced || placement_due;
+
+ if (!pid_scan_allowed) {
+ if (scan_started) {
+ trace_sched_skip_vma_numa(mm, vma, NUMAB_SKIP_IGNORE_PID);
+ } else {
+ vma_pids_skipped = true;
+ trace_sched_skip_vma_numa(mm, vma, NUMAB_SKIP_PID_INACTIVE);
+ continue;
+ }
}
+ /* Keep scan policy stable while processing a VMA in chunks. */
+ if (scan_started && vma->numab_state->promo_only)
+ placement_scan = false;
+
+ vma->numab_state->promo_only = !placement_scan;
cp_flags = MM_CP_PROT_NUMA;
if (!placement_scan)
cp_flags |= MM_CP_PROT_NUMA_PROMO_ONLY;
@@ -4357,6 +4364,7 @@ static void task_numa_work(struct callback_head *work)
/* VMA scan is complete, do not scan until next sequence. */
vma->numab_state->prev_scan_seq = mm->numa_scan_seq;
+ vma->numab_state->promo_only = false;
/*
* Only force scan within one VMA at a time, to limit the
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 5/7] sched/numa: scan PID-inactive VMAs for promotion
2026-09-30 11:21 [PATCH v4 0/7] sched/numa: stop VMA scan filters from gating promotion Gregory Price
` (3 preceding siblings ...)
2026-09-30 11:22 ` [PATCH v4 4/7] sched/numa: separate VMA placement from scan continuation Gregory Price
@ 2026-09-30 11:22 ` Gregory Price
2026-10-01 10:45 ` David Hildenbrand (Arm)
2026-09-30 11:22 ` [PATCH v4 6/7] mm: use BIT() for change_protection() flags Gregory Price
2026-09-30 11:22 ` [PATCH v4 7/7] mm: use VMA flag helpers in NUMA balancing Gregory Price
6 siblings, 1 reply; 14+ messages in thread
From: Gregory Price @ 2026-09-30 11:22 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
surenb, mhocko, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, ziy, baolin.wang, nico.pache, ryan.roberts,
dev.jain, baohua, lance.yang, usama.arif, kas, gourry,
joshua.hahnjy, rakie.kim, ying.huang, matthew.brost, byungchul,
apopple, jannh, pfalcato, hannes, shy828301, osalvador,
raghavendra.kt, stable
From: "Gregory Price (Meta)" <gourry@gourry.net>
Commit fc137c0ddab2 ("sched/numa: enhance vma scanning logic") skips
VMAs without recent PID activity. Since only NUMA hint faults record
that activity, the filter can suppress the fault needed to promote hot
slow-tier memory.
Let memory tiering bypass the PID scan filter. In combined mode, use the
placement decision to restrict top-tier sampling to VMAs that need it,
while inactive VMAs still receive promotion-only scans.
Make this decision only when a VMA scan starts. placement_due is
evaluated for the current task, and a partially scanned VMA is usually
resumed by a different thread, so re-evaluating it for each chunk would
demote in-progress placement scans and starve large VMAs of placement
sampling in multi-threaded processes.
Track the last completed placement scan separately from scans of any
kind. Promotion-only scans still update prev_scan_seq, but do not
advance the placement-starvation horizon.
On a host with 768 GB of DRAM and 256 GB of CXL memory, one large shmem
VMA consumed most scanning activity, while 2,537 other VMAs covering
84 GB were skipped as inactive. One stand-out result: a hot 20 GB hash
table ended up trapped entirely on CXL and drove CXL bandwidth
utilization beyond sustainable levels - resulting in a large regression.
With this series, the hot hash table ends up split evenly between DRAM
and CXL, tier residency tracks runtime load, and CXL bandwidth
utilization drops from 45GB/s (maxed) to 5-10GB/s, while DRAM bandwidth
utilization increases from ~200GB/s to 250GB/s+, resulting in major
throughput improvements for the database workload.
Fixes: fc137c0ddab2 ("sched/numa: enhance vma scanning logic")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
include/linux/mm_types.h | 7 +++++++
kernel/sched/fair.c | 31 +++++++++++++++++++++++++------
2 files changed, 32 insertions(+), 6 deletions(-)
diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index d0ac21d02c47..86cd12a61755 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -804,6 +804,13 @@ struct vma_numab_state {
*/
int prev_scan_seq;
+ /*
+ * MM scan sequence ID when the VMA was last scanned for placement.
+ * The starvation horizon in vma_needs_placement_scan() counts against
+ * this, so promotion-only scans cannot postpone placement indefinitely.
+ */
+ int prev_placement_scan_seq;
+
/*
* The in-progress scan of this VMA is promotion-only.
* Resumed scans finish with the policy they started with.
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 3b30786ded3b..6002882b9d30 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -4105,7 +4105,7 @@ static bool vma_needs_placement_scan(struct mm_struct *mm,
* threads can help scan this vma, force a vma scan.
*/
if (READ_ONCE(mm->numa_scan_seq) >
- (vma->numab_state->prev_scan_seq + get_nr_threads(current)))
+ (vma->numab_state->prev_placement_scan_seq + get_nr_threads(current)))
return true;
return false;
@@ -4279,7 +4279,8 @@ static void task_numa_work(struct callback_head *work)
* to prevent VMAs being skipped prematurely on the
* first scan:
*/
- vma->numab_state->prev_scan_seq = mm->numa_scan_seq - 1;
+ vma->numab_state->prev_scan_seq = mm->numa_scan_seq - 1;
+ vma->numab_state->prev_placement_scan_seq = mm->numa_scan_seq - 1;
}
/*
@@ -4312,10 +4313,13 @@ static void task_numa_work(struct callback_head *work)
* Do not scan the VMA if a task has not accessed it, unless no other
* VMA candidate exists. If a scan is already in-progress, finish it,
* but track continuation separately from starting a new one.
+ *
+ * The PID filter must not gate promotion. Allow PID-inactive VMAs
+ * to proceed when memory tiering is enabled.
*/
placement_due = vma_needs_placement_scan(mm, vma);
scan_started = mm->numa_scan_offset > vma->vm_start;
- pid_scan_allowed = vma_pids_forced || placement_due;
+ pid_scan_allowed = tiering || vma_pids_forced || placement_due;
if (!pid_scan_allowed) {
if (scan_started) {
@@ -4327,9 +4331,18 @@ static void task_numa_work(struct callback_head *work)
}
}
- /* Keep scan policy stable while processing a VMA in chunks. */
- if (scan_started && vma->numab_state->promo_only)
+ /*
+ * Keep scan policy stable while processing a VMA in chunks.
+ * placement_due is evaluated for the current task, and a
+ * different thread will usually resume the scan. Finish an
+ * in-progress scan with the policy it started with.
+ */
+ if (scan_started) {
+ if (vma->numab_state->promo_only)
+ placement_scan = false;
+ } else if (tiering && !placement_due) {
placement_scan = false;
+ }
vma->numab_state->promo_only = !placement_scan;
cp_flags = MM_CP_PROT_NUMA;
@@ -4362,8 +4375,14 @@ static void task_numa_work(struct callback_head *work)
cond_resched();
} while (end != vma->vm_end);
- /* VMA scan is complete, do not scan until next sequence. */
+ /*
+ * VMA scan is complete, do not scan until next sequence.
+ * A promotion-only scan did not cover top-tier folios, so it
+ * does not count towards the placement-scan starvation check.
+ */
vma->numab_state->prev_scan_seq = mm->numa_scan_seq;
+ if (placement_scan)
+ vma->numab_state->prev_placement_scan_seq = mm->numa_scan_seq;
vma->numab_state->promo_only = false;
/*
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 6/7] mm: use BIT() for change_protection() flags
2026-09-30 11:21 [PATCH v4 0/7] sched/numa: stop VMA scan filters from gating promotion Gregory Price
` (4 preceding siblings ...)
2026-09-30 11:22 ` [PATCH v4 5/7] sched/numa: scan PID-inactive VMAs for promotion Gregory Price
@ 2026-09-30 11:22 ` Gregory Price
2026-09-30 11:22 ` [PATCH v4 7/7] mm: use VMA flag helpers in NUMA balancing Gregory Price
6 siblings, 0 replies; 14+ messages in thread
From: Gregory Price @ 2026-09-30 11:22 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
surenb, mhocko, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, ziy, baolin.wang, nico.pache, ryan.roberts,
dev.jain, baohua, lance.yang, usama.arif, kas, gourry,
joshua.hahnjy, rakie.kim, ying.huang, matthew.brost, byungchul,
apopple, jannh, pfalcato, hannes, shy828301, osalvador,
raghavendra.kt
From: "Gregory Price (Meta)" <gourry@gourry.net>
The MM_CP_* flags are individual bits expressed as open-coded shifts.
Use BIT() consistently for the complete block.
No functional change intended.
Suggested-by: David Hildenbrand <david@kernel.org>
Link: https://lore.kernel.org/r/9b5e1573-6fd8-48a5-a274-a6e7ff83d2fe@kernel.org
Assisted-by: LLM
Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
---
include/linux/mm.h | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 69107e877fd1..b68fb9041777 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3597,21 +3597,21 @@ int get_cmdline(struct task_struct *task, char *buffer, int buflen);
* because something (e.g., COW, uffd-wp) blocks that from happening for all
* PTEs automatically in a writable mapping.
*/
-#define MM_CP_TRY_CHANGE_WRITABLE (1UL << 0)
+#define MM_CP_TRY_CHANGE_WRITABLE BIT(0)
/* Whether this protection change is for NUMA hints */
-#define MM_CP_PROT_NUMA (1UL << 1)
+#define MM_CP_PROT_NUMA BIT(1)
/* Whether this change is for write protecting */
-#define MM_CP_UFFD_WP (1UL << 2) /* do wp */
-#define MM_CP_UFFD_WP_RESOLVE (1UL << 3) /* Resolve wp */
+#define MM_CP_UFFD_WP BIT(2) /* do wp */
+#define MM_CP_UFFD_WP_RESOLVE BIT(3) /* Resolve wp */
#define MM_CP_UFFD_WP_ALL (MM_CP_UFFD_WP | \
MM_CP_UFFD_WP_RESOLVE)
/* Whether this change is for uffd RWP */
-#define MM_CP_UFFD_RWP (1UL << 4) /* do rwp */
-#define MM_CP_UFFD_RWP_RESOLVE (1UL << 5) /* resolve rwp */
+#define MM_CP_UFFD_RWP BIT(4) /* do rwp */
+#define MM_CP_UFFD_RWP_RESOLVE BIT(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)
+#define MM_CP_PROT_NUMA_PROMO_ONLY BIT(6)
bool can_change_pte_writable(struct vm_area_struct *vma, unsigned long addr,
pte_t pte);
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 7/7] mm: use VMA flag helpers in NUMA balancing
2026-09-30 11:21 [PATCH v4 0/7] sched/numa: stop VMA scan filters from gating promotion Gregory Price
` (5 preceding siblings ...)
2026-09-30 11:22 ` [PATCH v4 6/7] mm: use BIT() for change_protection() flags Gregory Price
@ 2026-09-30 11:22 ` Gregory Price
2026-10-01 10:45 ` David Hildenbrand (Arm)
6 siblings, 1 reply; 14+ messages in thread
From: Gregory Price @ 2026-09-30 11:22 UTC (permalink / raw)
To: linux-mm
Cc: linux-kernel, kernel-team, akpm, david, ljs, liam, vbabka, rppt,
surenb, mhocko, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, ziy, baolin.wang, nico.pache, ryan.roberts,
dev.jain, baohua, lance.yang, usama.arif, kas, gourry,
joshua.hahnjy, rakie.kim, ying.huang, matthew.brost, byungchul,
apopple, jannh, pfalcato, hannes, shy828301, osalvador,
raghavendra.kt
From: "Gregory Price (Meta)" <gourry@gourry.net>
Use vma_test() instead of accessing vm_flags directly in the NUMA
balancing code touched by the preceding fixes. Keep the existing flag
predicates unchanged.
No functional change intended.
Suggested-by: Lorenzo Stoakes <ljs@kernel.org>
Link: https://lore.kernel.org/r/aq1FDhy00epXXtgd@gremlin
Assisted-by: LLM
Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
---
kernel/sched/fair.c | 2 +-
mm/internal.h | 2 +-
mm/migrate.c | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 6002882b9d30..448c36791d33 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -4234,7 +4234,7 @@ static void task_numa_work(struct callback_head *work)
*/
placement_scan = balancing;
if (placement_scan && vma->vm_file) {
- if ((vma->vm_flags & (VM_READ | VM_WRITE)) == VM_READ)
+ if (vma_test(vma, VMA_READ_BIT) && !vma_test(vma, VMA_WRITE_BIT))
placement_scan = false;
}
diff --git a/mm/internal.h b/mm/internal.h
index cbeaf1fc38cf..560406c36a75 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -1240,7 +1240,7 @@ size_t splice_folio_into_pipe(struct pipe_inode_info *pipe,
static inline bool vma_is_single_threaded_private(struct vm_area_struct *vma)
{
- if (vma->vm_flags & VM_SHARED)
+ if (vma_test(vma, VMA_SHARED_BIT))
return false;
return atomic_read(&vma->vm_mm->mm_users) == 1;
diff --git a/mm/migrate.c b/mm/migrate.c
index c0f5f3ac71fc..fb4099f245a0 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -2705,7 +2705,7 @@ int migrate_misplaced_folio_prepare(struct folio *folio,
* See folio_maybe_mapped_shared() on possible imprecision
* when we cannot easily detect if a folio is shared.
*/
- if ((vma->vm_flags & VM_EXEC) && folio_maybe_mapped_shared(folio) &&
+ if (vma_test(vma, VMA_EXEC_BIT) && folio_maybe_mapped_shared(folio) &&
(!folio_numab_promotable(folio) || !node_is_toptier(node)))
return -EACCES;
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 2/7] mm: allow shared folios to be promoted to a fast tier
2026-09-30 11:22 ` [PATCH v4 2/7] mm: allow shared folios to be promoted to a fast tier Gregory Price
@ 2026-10-01 10:34 ` David Hildenbrand (Arm)
0 siblings, 0 replies; 14+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-01 10:34 UTC (permalink / raw)
To: Gregory Price, linux-mm
Cc: linux-kernel, kernel-team, akpm, ljs, liam, vbabka, rppt, surenb,
mhocko, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, ziy, baolin.wang, nico.pache, ryan.roberts,
dev.jain, baohua, lance.yang, usama.arif, kas, joshua.hahnjy,
rakie.kim, ying.huang, matthew.brost, byungchul, apopple, jannh,
pfalcato, hannes, shy828301, osalvador, raghavendra.kt, stable
On 9/30/26 13:22, Gregory Price wrote:
> From: "Gregory Price (Meta)" <gourry@gourry.net>
>
> NUMA balancing rejects shared copy-on-write folios and executable
> file folios mapped by multiple processes to avoid east-west
> migration bouncing. These checks break promotion from slow memory.
>
> Allow such folios to participate when the folio being migrated is
> a low-tier folio and the destination is top-tier (south->north).
> This allows promotion, but prevents east-west or north->south
> migrations (north->south is handled by reclaim demotion).
>
> Keep the existing restrictions for ordinary placement and for
> migrations that are not slow-to-top-tier promotions.
>
> Rename folio_use_access_time() to folio_numab_promotable() so the name
> captures the combined memory-tiering and source-tier test. The helper
> retains its existing behavior.
>
> Fixes: c574bbe91703 ("NUMA balancing: optimize page placement for memory tiering system")
> Cc: stable@vger.kernel.org
> Suggested-by: Zi Yan <ziy@nvidia.com>
> Link: https://lore.kernel.org/r/DLHS4KFPQ86I.1J4LN3352RI71@nvidia.com
> Assisted-by: LLM
> Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
> ---
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 3/7] sched/numa: scan read-only file mappings in tiering mode
2026-09-30 11:22 ` [PATCH v4 3/7] sched/numa: scan read-only file mappings in tiering mode Gregory Price
@ 2026-10-01 10:34 ` David Hildenbrand (Arm)
0 siblings, 0 replies; 14+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-01 10:34 UTC (permalink / raw)
To: Gregory Price, linux-mm
Cc: linux-kernel, kernel-team, akpm, ljs, liam, vbabka, rppt, surenb,
mhocko, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, ziy, baolin.wang, nico.pache, ryan.roberts,
dev.jain, baohua, lance.yang, usama.arif, kas, joshua.hahnjy,
rakie.kim, ying.huang, matthew.brost, byungchul, apopple, jannh,
pfalcato, hannes, shy828301, osalvador, raghavendra.kt, stable
On 9/30/26 13:22, Gregory Price wrote:
> From: "Gregory Price (Meta)" <gourry@gourry.net>
>
> Commit 4591ce4f2d22 ("sched/numa: Do not trap hinting faults for
> shared libraries") excludes file-backed read-only VMAs from NUMA
> hint faulting to prevent east-west placement bouncing.
>
> This filter hides hot file folios on slow memory from promotion.
>
> Scan those VMAs when tiering is enabled, but make their scans promotion
> only to retain the existing restriction. Keep the historical VMA
> predicate unchanged for backport-ability.
>
> Decide the walk type for each VMA from the balancing-mode snapshot
> taken in task_numa_work(). A VMA gets a placement scan only when normal
> balancing is enabled and it is not a read-only file mapping. Otherwise
> the walk is promotion-only, and without tiering the VMA is skipped as
> before.
>
> On a host with 768 GB of DRAM and 256 GB of CXL memory running two
> database services using ~430GB each, 169 MB of their shared 185 MB
> main binary accumulated on CXL and was permanently stuck there.
>
> With the change, the binary tier residency tracks its runtime hotness.
>
> Fixes: c574bbe91703 ("NUMA balancing: optimize page placement for memory tiering system")
> Cc: stable@vger.kernel.org
> Assisted-by: LLM
> Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
> ---
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 4/7] sched/numa: separate VMA placement from scan continuation
2026-09-30 11:22 ` [PATCH v4 4/7] sched/numa: separate VMA placement from scan continuation Gregory Price
@ 2026-10-01 10:44 ` David Hildenbrand (Arm)
2026-10-01 13:29 ` Gregory Price
0 siblings, 1 reply; 14+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-01 10:44 UTC (permalink / raw)
To: Gregory Price, linux-mm
Cc: linux-kernel, kernel-team, akpm, ljs, liam, vbabka, rppt, surenb,
mhocko, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, ziy, baolin.wang, nico.pache, ryan.roberts,
dev.jain, baohua, lance.yang, usama.arif, kas, joshua.hahnjy,
rakie.kim, ying.huang, matthew.brost, byungchul, apopple, jannh,
pfalcato, hannes, shy828301, osalvador, raghavendra.kt, stable
On 9/30/26 13:22, Gregory Price wrote:
> From: "Gregory Price (Meta)" <gourry@gourry.net>
>
> vma_is_accessed() returns true both when a VMA needs placement
> (east-west) sampling and when a VMA is already mid-scan across
> multiple scan windows.
>
> Callers cannot distinguish placement eligibility from scan progress.
>
> Rename the helper to vma_needs_placement_scan() and handle continuation
> state in task_numa_work(). Record whether an in-progress VMA scan is
> promotion-only so a scan resumed in a later scan window finishes with
> the policy it started with. The zeroed initial state places no
> restriction, so a resumed scan that finds freshly allocated NUMAB state
> falls back to the default policy.
>
> This separation allows PID-inactive VMAs to be scanned for promotion
> without treating scan continuation as evidence of placement eligibility.
> It is a prerequisite for the following fix and must accompany it when
> backported.
>
> Fixes: fc137c0ddab2 ("sched/numa: enhance vma scanning logic")
> Cc: stable@vger.kernel.org
> Assisted-by: LLM
> Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
> ---
> include/linux/mm_types.h | 6 ++++++
> kernel/sched/fair.c | 40 ++++++++++++++++++++++++----------------
> 2 files changed, 30 insertions(+), 16 deletions(-)
>
> diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
> index 1bd3ca7bfff2..d0ac21d02c47 100644
> --- a/include/linux/mm_types.h
> +++ b/include/linux/mm_types.h
> @@ -803,6 +803,12 @@ struct vma_numab_state {
> * A VMA is not eligible for scanning if prev_scan_seq == numa_scan_seq
> */
> int prev_scan_seq;
> +
> + /*
> + * The in-progress scan of this VMA is promotion-only.
> + * Resumed scans finish with the policy they started with.
> + */
> + bool promo_only;
> };
>
> #ifdef __HAVE_PFNMAP_TRACKING
> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
> index ab9afd3ad49b..3b30786ded3b 100644
> --- a/kernel/sched/fair.c
> +++ b/kernel/sched/fair.c
> @@ -4082,7 +4082,8 @@ static void reset_ptenuma_scan(struct task_struct *p)
> p->mm->numa_scan_offset = 0;
> }
>
> -static bool vma_is_accessed(struct mm_struct *mm, struct vm_area_struct *vma)
> +static bool vma_needs_placement_scan(struct mm_struct *mm,
> + struct vm_area_struct *vma)
mm can be had from vma->vm_mm, so likely sufficient to only pass the vma?
Apart from that this looks much clearer to me
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 5/7] sched/numa: scan PID-inactive VMAs for promotion
2026-09-30 11:22 ` [PATCH v4 5/7] sched/numa: scan PID-inactive VMAs for promotion Gregory Price
@ 2026-10-01 10:45 ` David Hildenbrand (Arm)
0 siblings, 0 replies; 14+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-01 10:45 UTC (permalink / raw)
To: Gregory Price, linux-mm
Cc: linux-kernel, kernel-team, akpm, ljs, liam, vbabka, rppt, surenb,
mhocko, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, ziy, baolin.wang, nico.pache, ryan.roberts,
dev.jain, baohua, lance.yang, usama.arif, kas, joshua.hahnjy,
rakie.kim, ying.huang, matthew.brost, byungchul, apopple, jannh,
pfalcato, hannes, shy828301, osalvador, raghavendra.kt, stable
On 9/30/26 13:22, Gregory Price wrote:
> From: "Gregory Price (Meta)" <gourry@gourry.net>
>
> Commit fc137c0ddab2 ("sched/numa: enhance vma scanning logic") skips
> VMAs without recent PID activity. Since only NUMA hint faults record
> that activity, the filter can suppress the fault needed to promote hot
> slow-tier memory.
>
> Let memory tiering bypass the PID scan filter. In combined mode, use the
> placement decision to restrict top-tier sampling to VMAs that need it,
> while inactive VMAs still receive promotion-only scans.
>
> Make this decision only when a VMA scan starts. placement_due is
> evaluated for the current task, and a partially scanned VMA is usually
> resumed by a different thread, so re-evaluating it for each chunk would
> demote in-progress placement scans and starve large VMAs of placement
> sampling in multi-threaded processes.
>
> Track the last completed placement scan separately from scans of any
> kind. Promotion-only scans still update prev_scan_seq, but do not
> advance the placement-starvation horizon.
>
> On a host with 768 GB of DRAM and 256 GB of CXL memory, one large shmem
> VMA consumed most scanning activity, while 2,537 other VMAs covering
> 84 GB were skipped as inactive. One stand-out result: a hot 20 GB hash
> table ended up trapped entirely on CXL and drove CXL bandwidth
> utilization beyond sustainable levels - resulting in a large regression.
>
> With this series, the hot hash table ends up split evenly between DRAM
> and CXL, tier residency tracks runtime load, and CXL bandwidth
> utilization drops from 45GB/s (maxed) to 5-10GB/s, while DRAM bandwidth
> utilization increases from ~200GB/s to 250GB/s+, resulting in major
> throughput improvements for the database workload.
>
> Fixes: fc137c0ddab2 ("sched/numa: enhance vma scanning logic")
> Cc: stable@vger.kernel.org
> Assisted-by: LLM
> Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
> ---
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 7/7] mm: use VMA flag helpers in NUMA balancing
2026-09-30 11:22 ` [PATCH v4 7/7] mm: use VMA flag helpers in NUMA balancing Gregory Price
@ 2026-10-01 10:45 ` David Hildenbrand (Arm)
0 siblings, 0 replies; 14+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-01 10:45 UTC (permalink / raw)
To: Gregory Price, linux-mm
Cc: linux-kernel, kernel-team, akpm, ljs, liam, vbabka, rppt, surenb,
mhocko, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, ziy, baolin.wang, nico.pache, ryan.roberts,
dev.jain, baohua, lance.yang, usama.arif, kas, joshua.hahnjy,
rakie.kim, ying.huang, matthew.brost, byungchul, apopple, jannh,
pfalcato, hannes, shy828301, osalvador, raghavendra.kt
On 9/30/26 13:22, Gregory Price wrote:
> From: "Gregory Price (Meta)" <gourry@gourry.net>
>
> Use vma_test() instead of accessing vm_flags directly in the NUMA
> balancing code touched by the preceding fixes. Keep the existing flag
> predicates unchanged.
>
> No functional change intended.
>
> Suggested-by: Lorenzo Stoakes <ljs@kernel.org>
> Link: https://lore.kernel.org/r/aq1FDhy00epXXtgd@gremlin
> Assisted-by: LLM
> Signed-off-by: Gregory Price (Meta) <gourry@gourry.net>
> ---
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
--
Cheers,
David
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 4/7] sched/numa: separate VMA placement from scan continuation
2026-10-01 10:44 ` David Hildenbrand (Arm)
@ 2026-10-01 13:29 ` Gregory Price
0 siblings, 0 replies; 14+ messages in thread
From: Gregory Price @ 2026-10-01 13:29 UTC (permalink / raw)
To: david, linux-mm
Cc: linux-kernel, kernel-team, akpm, ljs, liam, vbabka, rppt, surenb,
mhocko, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, ziy, baolin.wang, nico.pache, ryan.roberts,
dev.jain, baohua, lance.yang, usama.arif, kas, joshua.hahnjy,
rakie.kim, ying.huang, matthew.brost, byungchul, apopple, jannh,
pfalcato, hannes, shy828301, osalvador, raghavendra.kt, stable
On Thu, Oct 01, 2026 at 12:44:50PM +0200, David Hildenbrand (Arm) wrote:
> On 9/30/26 13:22, Gregory Price wrote:
> > -static bool vma_is_accessed(struct mm_struct *mm, struct vm_area_struct *vma)
> > +static bool vma_needs_placement_scan(struct mm_struct *mm,
> > + struct vm_area_struct *vma)
>
> mm can be had from vma->vm_mm, so likely sufficient to only pass the vma?
>
> Apart from that this looks much clearer to me
>
ack. agreed.
If nothing else comes up on v4, could you please fold the fixlet below?
Otherwise i can spin a v4.
should be conflict-free with a git am -3
~Gregory
--- >8 ---
Subject: sched/numa: drop mm argument from vma_needs_placement_scan()
The mm can be had from vma->vm_mm.
---
kernel/sched/fair.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 3b30786ded3b..095289d3da3c 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -4082,9 +4082,9 @@ static void reset_ptenuma_scan(struct task_struct *p)
p->mm->numa_scan_offset = 0;
}
-static bool vma_needs_placement_scan(struct mm_struct *mm,
- struct vm_area_struct *vma)
+static bool vma_needs_placement_scan(struct vm_area_struct *vma)
{
+ struct mm_struct *mm = vma->vm_mm;
unsigned long pids;
/*
* Allow unconditional access first two times, so that all the (pages)
@@ -4313,7 +4313,7 @@ static void task_numa_work(struct callback_head *work)
* VMA candidate exists. If a scan is already in-progress, finish it,
* but track continuation separately from starting a new one.
*/
- placement_due = vma_needs_placement_scan(mm, vma);
+ placement_due = vma_needs_placement_scan(vma);
scan_started = mm->numa_scan_offset > vma->vm_start;
pid_scan_allowed = vma_pids_forced || placement_due;
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-10-01 13:29 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-30 11:21 [PATCH v4 0/7] sched/numa: stop VMA scan filters from gating promotion Gregory Price
2026-09-30 11:22 ` [PATCH v4 1/7] mm: support promotion-only NUMA hinting scans Gregory Price
2026-09-30 11:22 ` [PATCH v4 2/7] mm: allow shared folios to be promoted to a fast tier Gregory Price
2026-10-01 10:34 ` David Hildenbrand (Arm)
2026-09-30 11:22 ` [PATCH v4 3/7] sched/numa: scan read-only file mappings in tiering mode Gregory Price
2026-10-01 10:34 ` David Hildenbrand (Arm)
2026-09-30 11:22 ` [PATCH v4 4/7] sched/numa: separate VMA placement from scan continuation Gregory Price
2026-10-01 10:44 ` David Hildenbrand (Arm)
2026-10-01 13:29 ` Gregory Price
2026-09-30 11:22 ` [PATCH v4 5/7] sched/numa: scan PID-inactive VMAs for promotion Gregory Price
2026-10-01 10:45 ` David Hildenbrand (Arm)
2026-09-30 11:22 ` [PATCH v4 6/7] mm: use BIT() for change_protection() flags Gregory Price
2026-09-30 11:22 ` [PATCH v4 7/7] mm: use VMA flag helpers in NUMA balancing Gregory Price
2026-10-01 10:45 ` David Hildenbrand (Arm)
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®