[PATCH 2/6] sched/numa: fix scan-period ratio denominator checks
From: Li Zhe
Date: Wed Oct 14 2026 - 05:48:11 EST
update_task_scan_period() scales the scan period with two ratios:
local/(local + remote) and private/(private + shared).
The existing guard only checks local + shared. That normally implies
that both ratio denominators are non-zero because the locality and type
counters are updated from the same hint faults. Memory hotplug can make
that assumption fragile because task_numa_placement() folds the
private/shared buffers by walking online nodes, while the locality counters
were already charged when the fault was recorded.
The divide-by-zero was reproduced on v7.3-rc1 in a QEMU guest by
hot-removing a NUMA node after NUMA balancing had collected fault feedback.
The next NUMA hint fault enters task_numa_placement() with locality
feedback that was charged before node1 was removed. The per-node
private/shared buffers for node1 are skipped by for_each_online_node(), so
update_task_scan_period() can see local > 0 while private and shared are
both 0. The old local + shared guard lets that state pass, and the
subsequent private/(private + shared) calculation divides by zero.
Keep the slow-scan fallback for empty or unusable samples, but test the
actual denominators before calculating the ratios.
Fixes: 37ec97deb3a8 ("sched/numa: Slow down scan rate if shared faults dominate")
Signed-off-by: Li Zhe <lizhe.67@xxxxxxxxxxxxx>
---
kernel/sched/fair.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 1053437933ee..76532b8202dc 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -3698,6 +3698,8 @@ static void update_task_scan_period(struct task_struct *p,
unsigned long remote = p->numa_faults_locality[0];
unsigned long local = p->numa_faults_locality[1];
+ unsigned long locality_faults = local + remote;
+ unsigned long type_faults = private + shared;
/*
* If there were no record hinting faults then either the task is
@@ -3706,7 +3708,7 @@ static void update_task_scan_period(struct task_struct *p,
* migration then it implies we are migrating too quickly or the local
* node is overloaded. In either case, scan slower
*/
- if (local + shared == 0 || p->numa_faults_locality[2]) {
+ if (!locality_faults || !type_faults || p->numa_faults_locality[2]) {
p->numa_scan_period = min(p->numa_scan_period_max,
p->numa_scan_period << 1);
@@ -3723,8 +3725,8 @@ static void update_task_scan_period(struct task_struct *p,
* >= NUMA_PERIOD_THRESHOLD scan period increases (scan slower)
*/
period_slot = DIV_ROUND_UP(p->numa_scan_period, NUMA_PERIOD_SLOTS);
- lr_ratio = (local * NUMA_PERIOD_SLOTS) / (local + remote);
- ps_ratio = (private * NUMA_PERIOD_SLOTS) / (private + shared);
+ lr_ratio = (local * NUMA_PERIOD_SLOTS) / locality_faults;
+ ps_ratio = (private * NUMA_PERIOD_SLOTS) / type_faults;
if (ps_ratio >= NUMA_PERIOD_THRESHOLD) {
/*
--
2.20.1