Linux Documentation
 help / color / mirror / Atom feed
* [PATCH 0/4] sched/numa: Add per-process automatic NUMA balancing control
@ 2026-09-08 12:24 Li Zhe
  2026-09-08 12:24 ` [PATCH 1/4] sched/numa: Track per-process automatic NUMA balancing mode Li Zhe
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Li Zhe @ 2026-09-08 12:24 UTC (permalink / raw)
  To: mingo, peterz, juri.lelli, vincent.guittot, dietmar.eggemann,
	rostedt, bsegall, mgorman, vschneid, kprateek.nayak
  Cc: linux-kernel, linux-fsdevel, linux-doc, lizhe.67

Automatic NUMA balancing is controlled globally through the
kernel.numa_balancing sysctl and can be influenced indirectly through
memory policy. That works well as a system default, but it is too coarse
for workloads where a launcher wants most processes to use the default
policy while a selected process opts out because it already manages NUMA
placement or cannot afford the sampling overhead.

A recent per-cgroup proposal tried to address this by adding a
cgroup-local enable knob plus a NUMA_BALANCING_CGROUP mode where
balancing was disabled by default and then enabled for selected cgroups
[1]. The discussion also noted that automatic NUMA balancing already
operates at task/process granularity, that the knob is not a resource
control attribute, and that task/process controls such as sched_setattr()
or prctl() may be a better fit while cgroups and cpusets continue to
describe workload grouping and placement domains.

There is also prior art from 2023 prctl() proposals [2][3]. Those
versions used a per-mm mode with disabled/enabled/default states, added
NumaB_mode to /proc/<pid>/status, and changed the global static key
handling so a per-process enable could override the global sysctl.

This series takes a more conservative variant to preserve existing
administrator and workload expectations. Keeping the global sysctl as a
hard off switch means an administrator can still disable all automatic
NUMA balancing activity with one knob. Using a weak two-state
per-process opt-out/allow ABI avoids exposing a "default" state whose
meaning depends on global policy and is therefore easy to misinterpret.
Storing the configured mode in signal_struct gives the control explicit
thread-group semantics and avoids making unrelated non-thread CLONE_VM
users share the policy merely because they share an mm.

The series implements a per-process interface: a thread group may use
prctl() to set or get its configured automatic NUMA balancing mode. A
process can opt out of new periodic NUMA scan scheduling and NUMA
scheduler accounting, or opt back in. Existing hinting faults and queued
work are allowed to drain naturally as the disabled state takes effect.
When kernel.numa_balancing is 0, a process-local enable request does not
override it.

A separate task_struct scheduler snapshot is used by enqueue/dequeue
accounting so the scheduler can consistently account the state that was
in effect when a task entered the runqueue. This avoids changing
historical task accounting while still allowing a process mode change to
take effect through the normal dequeue/update/enqueue path.

The series also exposes the process NUMA balancing mode in
/proc/<pid>/status and documents the new prctl() ABI and its interaction
with the global sysctl.

[1]: https://lore.kernel.org/all/20250625102337.3128193-1-yu.c.chen@intel.com/
[2]: https://lore.kernel.org/all/20230412140701.58337-1-ligang.bdlg@bytedance.com/
[3]: https://lore.kernel.org/all/20230412141127.59741-1-ligang.bdlg@bytedance.com/

Li Zhe (4):
  sched/numa: Track per-process automatic NUMA balancing mode
  sched/numa: Add prctl controls for process mode
  proc: Report process NUMA balancing mode
  Documentation: Describe per-process NUMA balancing control

 Documentation/admin-guide/sysctl/kernel.rst | 16 ++++++++
 Documentation/filesystems/proc.rst          |  6 +++
 fs/proc/array.c                             | 16 ++++++++
 include/linux/sched.h                       |  6 +++
 include/linux/sched/numa_balancing.h        | 42 +++++++++++++++++++++
 include/linux/sched/signal.h                |  8 ++++
 include/uapi/linux/prctl.h                  |  6 +++
 init/init_task.c                            |  4 ++
 kernel/fork.c                               |  8 ++++
 kernel/sched/core.c                         | 39 +++++++++++++++++++
 kernel/sched/fair.c                         | 36 ++++++++++++++++--
 kernel/sys.c                                | 18 +++++++++
 12 files changed, 201 insertions(+), 4 deletions(-)

-- 
2.20.1

^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/4] sched/numa: Track per-process automatic NUMA balancing mode
  2026-09-08 12:24 [PATCH 0/4] sched/numa: Add per-process automatic NUMA balancing control Li Zhe
@ 2026-09-08 12:24 ` Li Zhe
  2026-09-08 12:24 ` [PATCH 2/4] sched/numa: Add prctl controls for process mode Li Zhe
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Li Zhe @ 2026-09-08 12:24 UTC (permalink / raw)
  To: mingo, peterz, juri.lelli, vincent.guittot, dietmar.eggemann,
	rostedt, bsegall, mgorman, vschneid, kprateek.nayak
  Cc: linux-kernel, linux-fsdevel, linux-doc, lizhe.67

Add the internal process mode and scheduler hooks for per-process
automatic NUMA balancing control. Any thread in a thread group can change
the mode, and the change applies to the whole thread group. Fork
inherits the parent process mode, CLONE_THREAD shares it, and exec
preserves it. A process mode of enable only allows the process to
participate when the global sysctl/static key is enabled and memory
policy allows NUMA balancing.

Disabling the process mode stops new periodic NUMA scan scheduling and
excludes the process from NUMA locality scheduler accounting.
Already-installed NUMA hinting PTEs, queued scan work, and other NUMA
state are not actively cleared; they drain or age out naturally as the
disabled state takes effect. This keeps the slow-path ABI simple and
avoids expensive address-space surgery.

The configured process mode lives in signal_struct. A separate
numa_balancing_sched_enabled field in task_struct is only a scheduler
snapshot used by NUMA hot paths and runqueue accounting. It is not part
of the userspace-visible process mode. The fork path initializes both
pieces of state under current->sighand->siglock before the new task is
published. The mode update path serializes concurrent updates, changes
the signal mode under siglock, updates the calling task immediately, and
then walks the remaining thread list under tasklist_lock to update the
rest under sched_change. This keeps rq->nr_numa_running and
rq->nr_preferred_running in sync without adding extra hot-path locking.

Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
 include/linux/sched.h                |  6 +++++
 include/linux/sched/numa_balancing.h | 37 ++++++++++++++++++++++++++
 include/linux/sched/signal.h         |  8 ++++++
 init/init_task.c                     |  4 +++
 kernel/fork.c                        |  8 ++++++
 kernel/sched/core.c                  | 39 ++++++++++++++++++++++++++++
 kernel/sched/fair.c                  | 36 ++++++++++++++++++++++---
 7 files changed, 134 insertions(+), 4 deletions(-)

diff --git a/include/linux/sched.h b/include/linux/sched.h
index 8b3d47a325cc..7f1932658d24 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1382,6 +1382,12 @@ struct task_struct {
 	short				pref_node_fork;
 #endif
 #ifdef CONFIG_NUMA_BALANCING
+	/*
+	 * Scheduler snapshot of signal_struct::numa_balancing_enabled. It is
+	 * updated with sched_change for runqueue NUMA accounting; the
+	 * user-visible process mode lives in signal_struct.
+	 */
+	bool				numa_balancing_sched_enabled;
 	int				numa_scan_seq;
 	unsigned int			numa_scan_period;
 	unsigned int			numa_scan_period_max;
diff --git a/include/linux/sched/numa_balancing.h b/include/linux/sched/numa_balancing.h
index 52b22c5c396d..bc413aae5915 100644
--- a/include/linux/sched/numa_balancing.h
+++ b/include/linux/sched/numa_balancing.h
@@ -8,6 +8,7 @@
  */
 
 #include <linux/sched.h>
+#include <linux/sched/signal.h>
 
 #define TNF_MIGRATED	0x01
 #define TNF_NO_GROUP	0x02
@@ -30,6 +31,22 @@ extern void task_numa_fault(int last_node, int node, int pages, int flags);
 extern pid_t task_numa_group_id(struct task_struct *p);
 extern void set_numabalancing_state(bool enabled);
 extern void task_numa_free(struct task_struct *p, bool final);
+static inline bool task_numa_sched_snapshot_enabled(struct task_struct *p)
+{
+	return READ_ONCE(p->numa_balancing_sched_enabled);
+}
+
+static inline bool task_numa_process_mode_enabled(struct task_struct *p)
+{
+	return READ_ONCE(p->signal->numa_balancing_enabled);
+}
+
+int task_numa_balancing_set_current(bool enabled);
+
+static inline int task_numa_balancing_get_current(void)
+{
+	return task_numa_process_mode_enabled(current);
+}
 bool should_numa_migrate_memory(struct task_struct *p, struct folio *folio,
 				int src_nid, int dst_cpu);
 #else
@@ -47,6 +64,26 @@ static inline void set_numabalancing_state(bool enabled)
 static inline void task_numa_free(struct task_struct *p, bool final)
 {
 }
+
+static inline bool task_numa_sched_snapshot_enabled(struct task_struct *p)
+{
+	return false;
+}
+
+static inline bool task_numa_process_mode_enabled(struct task_struct *p)
+{
+	return false;
+}
+
+static inline int task_numa_balancing_set_current(bool enabled)
+{
+	return -EINVAL;
+}
+
+static inline int task_numa_balancing_get_current(void)
+{
+	return -EINVAL;
+}
 static inline bool should_numa_migrate_memory(struct task_struct *p,
 				struct folio *folio, int src_nid, int dst_cpu)
 {
diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h
index 584ae88b435e..fdc03c016cfe 100644
--- a/include/linux/sched/signal.h
+++ b/include/linux/sched/signal.h
@@ -179,6 +179,14 @@ struct signal_struct {
 
 #ifdef CONFIG_SCHED_AUTOGROUP
 	struct autogroup *autogroup;
+#endif
+#ifdef CONFIG_NUMA_BALANCING
+	/*
+	 * Thread-group automatic NUMA balancing mode configured through prctl().
+	 * Scheduler hot paths use task_struct::numa_balancing_sched_enabled as
+	 * their per-task runqueue accounting snapshot.
+	 */
+	bool numa_balancing_enabled;
 #endif
 	/*
 	 * Cumulative resource counters for dead threads in the group,
diff --git a/init/init_task.c b/init/init_task.c
index adb207cd987c..835bb3f7a17e 100644
--- a/init/init_task.c
+++ b/init/init_task.c
@@ -40,6 +40,9 @@ static struct signal_struct init_signals = {
 	.cputimer		= {
 		.cputime_atomic	= INIT_CPUTIME_ATOMIC,
 	},
+#endif
+#ifdef CONFIG_NUMA_BALANCING
+	.numa_balancing_enabled = true,
 #endif
 	INIT_CPU_TIMERS(init_signals)
 	.pids = {
@@ -224,6 +227,7 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = {
 	.vtime.state	= VTIME_SYS,
 #endif
 #ifdef CONFIG_NUMA_BALANCING
+	.numa_balancing_sched_enabled = true,
 	.numa_preferred_nid = NUMA_NO_NODE,
 	.numa_group	= NULL,
 	.numa_faults	= NULL,
diff --git a/kernel/fork.c b/kernel/fork.c
index 416758c8a3d4..aff64d75c77f 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -2496,6 +2496,14 @@ __latent_entropy struct task_struct *copy_process(
 
 	/* No more failure paths after this point. */
 
+#ifdef CONFIG_NUMA_BALANCING
+	p->numa_balancing_sched_enabled =
+		READ_ONCE(current->signal->numa_balancing_enabled);
+	if (!(clone_flags & CLONE_THREAD))
+		p->signal->numa_balancing_enabled =
+			p->numa_balancing_sched_enabled;
+#endif
+
 	/*
 	 * Copy seccomp details explicitly here, in case they were changed
 	 * before holding sighand lock.
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index f78275192036..ba8b22f3ffa5 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -36,6 +36,7 @@
 #include <linux/sched/isolation.h>
 #include <linux/sched/loadavg.h>
 #include <linux/sched/mm.h>
+#include <linux/sched/numa_balancing.h>
 #include <linux/sched/nohz.h>
 #include <linux/sched/rseq_api.h>
 #include <linux/sched/rt.h>
@@ -603,6 +604,8 @@ int task_llc(const struct task_struct *p)
  *				p->se.load, p->rt_priority,
  *				p->dl.dl_{runtime, deadline, period, flags, bw, density}
  *  - sched_setnuma():		p->numa_preferred_nid
+ *  - task_numa_balancing_set_current():	p->signal->numa_balancing_enabled,
+ *					p->numa_balancing_sched_enabled
  *  - sched_move_task():	p->sched_task_group
  *  - uclamp_update_active()	p->uclamp*
  *
@@ -8407,6 +8410,42 @@ void sched_setnuma(struct task_struct *p, int nid)
 	scoped_guard (sched_change, p, DEQUEUE_SAVE)
 		p->numa_preferred_nid = nid;
 }
+
+static void sched_numa_balancing_change_task(struct task_struct *p, bool enabled)
+{
+	guard(task_rq_lock)(p);
+	scoped_guard (sched_change, p, DEQUEUE_SAVE)
+		WRITE_ONCE(p->numa_balancing_sched_enabled, enabled);
+}
+
+int task_numa_balancing_set_current(bool enabled)
+{
+	static DEFINE_MUTEX(task_numa_balancing_mutex);
+	struct task_struct *t;
+	unsigned long flags;
+	bool old_enabled;
+
+	guard(mutex)(&task_numa_balancing_mutex);
+
+	if (WARN_ON_ONCE(!lock_task_sighand(current, &flags)))
+		return -ESRCH;
+
+	old_enabled = current->signal->numa_balancing_enabled;
+	if (old_enabled != enabled)
+		WRITE_ONCE(current->signal->numa_balancing_enabled, enabled);
+
+	unlock_task_sighand(current, &flags);
+
+	if (old_enabled != enabled) {
+		sched_numa_balancing_change_task(current, enabled);
+		read_lock(&tasklist_lock);
+		for_other_threads(current, t)
+			sched_numa_balancing_change_task(t, enabled);
+		read_unlock(&tasklist_lock);
+	}
+
+	return 0;
+}
 #endif /* CONFIG_NUMA_BALANCING */
 
 #ifdef CONFIG_HOTPLUG_CPU
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 8dff37059faf..7dfe275a4bc4 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -38,6 +38,7 @@
 #include <linux/sched/cond_resched.h>
 #include <linux/sched/cputime.h>
 #include <linux/sched/isolation.h>
+#include <linux/sched/numa_balancing.h>
 #include <linux/sched/nohz.h>
 #include <linux/sched/prio.h>
 #include <linux/static_call.h>
@@ -1712,6 +1713,7 @@ static int get_pref_llc(struct task_struct *p, struct mm_struct *mm)
 		 * conflict only exists for a short period of time.
 		 */
 		if (static_branch_likely(&sched_numa_balancing) &&
+		    task_numa_sched_snapshot_enabled(p) &&
 		    p->numa_preferred_nid >= 0 &&
 		    cpu_to_node(mm_sched_cpu) != p->numa_preferred_nid)
 			mm_sched_llc = -1;
@@ -1808,6 +1810,9 @@ static void get_scan_cpumasks(cpumask_var_t cpus, struct task_struct *p)
 	if (!static_branch_likely(&sched_numa_balancing))
 		goto out;
 
+	if (!task_numa_sched_snapshot_enabled(p))
+		goto out;
+
 	cpu = READ_ONCE(p->mm->sc_stat.cpu);
 	if (cpu != -1)
 		nid = cpu_to_node(cpu);
@@ -2387,14 +2392,22 @@ static unsigned int task_scan_max(struct task_struct *p)
 
 static void account_numa_enqueue(struct rq *rq, struct task_struct *p)
 {
-	rq->nr_numa_running += (p->numa_preferred_nid != NUMA_NO_NODE);
-	rq->nr_preferred_running += (p->numa_preferred_nid == task_node(p));
+	bool enabled = task_numa_sched_snapshot_enabled(p);
+
+	rq->nr_numa_running += enabled &&
+				 p->numa_preferred_nid != NUMA_NO_NODE;
+	rq->nr_preferred_running += enabled &&
+				     p->numa_preferred_nid == task_node(p);
 }
 
 static void account_numa_dequeue(struct rq *rq, struct task_struct *p)
 {
-	rq->nr_numa_running -= (p->numa_preferred_nid != NUMA_NO_NODE);
-	rq->nr_preferred_running -= (p->numa_preferred_nid == task_node(p));
+	bool enabled = task_numa_sched_snapshot_enabled(p);
+
+	rq->nr_numa_running -= enabled &&
+				 p->numa_preferred_nid != NUMA_NO_NODE;
+	rq->nr_preferred_running -= enabled &&
+				     p->numa_preferred_nid == task_node(p);
 }
 
 /* Shared or private faults. */
@@ -3085,6 +3098,9 @@ static bool task_numa_compare(struct task_numa_env *env,
 			goto unlock;
 	}
 
+	if (!task_numa_sched_snapshot_enabled(cur))
+		goto unlock;
+
 	/* Skip this swap candidate if cannot move to the source cpu. */
 	if (!cpumask_test_cpu(env->src_cpu, cur->cpus_ptr))
 		goto unlock;
@@ -3992,6 +4008,9 @@ void task_numa_fault(int last_cpupid, int mem_node, int pages, int flags)
 	if (!static_branch_likely(&sched_numa_balancing))
 		return;
 
+	if (!task_numa_sched_snapshot_enabled(p))
+		return;
+
 	/* for example, ksmd faulting in a user's mm */
 	if (!p->mm)
 		return;
@@ -4436,6 +4455,9 @@ static void task_tick_numa(struct rq *rq, struct task_struct *curr)
 	if (!curr->mm || (curr->flags & (PF_EXITING | PF_KTHREAD)) || work->next != work)
 		return;
 
+	if (!task_numa_sched_snapshot_enabled(curr))
+		return;
+
 	/*
 	 * Using runtime rather than walltime has the dual advantage that
 	 * we (mostly) drive the selection from busy threads and that the
@@ -4463,6 +4485,9 @@ static void update_scan_period(struct task_struct *p, int new_cpu)
 	if (!static_branch_likely(&sched_numa_balancing))
 		return;
 
+	if (!task_numa_sched_snapshot_enabled(p))
+		return;
+
 	if (!p->mm || !p->numa_faults || (p->flags & PF_EXITING))
 		return;
 
@@ -10475,6 +10500,9 @@ static long migrate_degrades_locality(struct task_struct *p, struct lb_env *env)
 	if (!static_branch_likely(&sched_numa_balancing))
 		return 0;
 
+	if (!task_numa_sched_snapshot_enabled(p))
+		return 0;
+
 	if (!p->numa_faults || !(env->sd->flags & SD_NUMA))
 		return 0;
 
-- 
2.20.1

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/4] sched/numa: Add prctl controls for process mode
  2026-09-08 12:24 [PATCH 0/4] sched/numa: Add per-process automatic NUMA balancing control Li Zhe
  2026-09-08 12:24 ` [PATCH 1/4] sched/numa: Track per-process automatic NUMA balancing mode Li Zhe
@ 2026-09-08 12:24 ` Li Zhe
  2026-09-08 12:24 ` [PATCH 3/4] proc: Report process NUMA balancing mode Li Zhe
  2026-09-08 12:24 ` [PATCH 4/4] Documentation: Describe per-process NUMA balancing control Li Zhe
  3 siblings, 0 replies; 5+ messages in thread
From: Li Zhe @ 2026-09-08 12:24 UTC (permalink / raw)
  To: mingo, peterz, juri.lelli, vincent.guittot, dietmar.eggemann,
	rostedt, bsegall, mgorman, vschneid, kprateek.nayak
  Cc: linux-kernel, linux-fsdevel, linux-doc, lizhe.67

Expose the process automatic NUMA balancing mode through prctl().
PR_SET_NUMA_BALANCING accepts PR_NUMA_BALANCING_DISABLE to opt the whole
thread group out of future NUMA scanning and PR_NUMA_BALANCING_ENABLE to
allow it to participate again when global NUMA balancing and memory
policy permit it.

PR_GET_NUMA_BALANCING returns the configured process mode, not the
effective state after combining the global sysctl/static key and memory
policy restrictions. This keeps the ABI weak and predictable: a process
can observe and restore the value it configured, while administrators
retain kernel.numa_balancing as the top-level hard-off switch.

Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
 include/uapi/linux/prctl.h |  6 ++++++
 kernel/sys.c               | 18 ++++++++++++++++++
 2 files changed, 24 insertions(+)

diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h
index b6ec6f693719..5bc0a2a2a930 100644
--- a/include/uapi/linux/prctl.h
+++ b/include/uapi/linux/prctl.h
@@ -416,4 +416,10 @@ struct prctl_mm_map {
 # define PR_CFI_DISABLE		_BITUL(1)
 # define PR_CFI_LOCK		_BITUL(2)
 
+/* Per-process automatic NUMA balancing control */
+#define PR_SET_NUMA_BALANCING	82
+#define PR_GET_NUMA_BALANCING	83
+# define PR_NUMA_BALANCING_DISABLE	0
+# define PR_NUMA_BALANCING_ENABLE	1
+
 #endif /* _LINUX_PRCTL_H */
diff --git a/kernel/sys.c b/kernel/sys.c
index 35b538ba843c..fb01841c0ce9 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -60,6 +60,7 @@
 #include <linux/sched/stat.h>
 #include <linux/sched/mm.h>
 #include <linux/sched/coredump.h>
+#include <linux/sched/numa_balancing.h>
 #include <linux/sched/task.h>
 #include <linux/sched/cputime.h>
 #include <linux/rcupdate.h>
@@ -2907,6 +2908,23 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
 		if (arg3 & PR_CFI_LOCK && !(arg3 & PR_CFI_DISABLE))
 			error = arch_prctl_lock_branch_landing_pad_state(me);
 		break;
+	case PR_SET_NUMA_BALANCING:
+		if (arg3 || arg4 || arg5)
+			return -EINVAL;
+		if (arg2 != PR_NUMA_BALANCING_DISABLE &&
+		    arg2 != PR_NUMA_BALANCING_ENABLE)
+			return -EINVAL;
+		error = task_numa_balancing_set_current(arg2 ==
+							 PR_NUMA_BALANCING_ENABLE);
+		break;
+	case PR_GET_NUMA_BALANCING:
+		if (arg2 || arg3 || arg4 || arg5)
+			return -EINVAL;
+		error = task_numa_balancing_get_current();
+		if (error >= 0)
+			error = error ? PR_NUMA_BALANCING_ENABLE :
+				PR_NUMA_BALANCING_DISABLE;
+		break;
 	default:
 		trace_task_prctl_unknown(option, arg2, arg3, arg4, arg5);
 		error = -EINVAL;
-- 
2.20.1

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 3/4] proc: Report process NUMA balancing mode
  2026-09-08 12:24 [PATCH 0/4] sched/numa: Add per-process automatic NUMA balancing control Li Zhe
  2026-09-08 12:24 ` [PATCH 1/4] sched/numa: Track per-process automatic NUMA balancing mode Li Zhe
  2026-09-08 12:24 ` [PATCH 2/4] sched/numa: Add prctl controls for process mode Li Zhe
@ 2026-09-08 12:24 ` Li Zhe
  2026-09-08 12:24 ` [PATCH 4/4] Documentation: Describe per-process NUMA balancing control Li Zhe
  3 siblings, 0 replies; 5+ messages in thread
From: Li Zhe @ 2026-09-08 12:24 UTC (permalink / raw)
  To: mingo, peterz, juri.lelli, vincent.guittot, dietmar.eggemann,
	rostedt, bsegall, mgorman, vschneid, kprateek.nayak
  Cc: linux-kernel, linux-fsdevel, linux-doc, lizhe.67

Add NumaB_mode to /proc/<pid>/status so users can inspect the configured
process automatic NUMA balancing mode without issuing a prctl() from the
target process.

The field reports the process mode as enabled or disabled when automatic
NUMA balancing is supported, and unsupported when the kernel is built
without CONFIG_NUMA_BALANCING. Effective behavior still depends on the
global numa_balancing sysctl and memory policy.

Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
 fs/proc/array.c                      | 16 ++++++++++++++++
 include/linux/sched/numa_balancing.h |  5 +++++
 2 files changed, 21 insertions(+)

diff --git a/fs/proc/array.c b/fs/proc/array.c
index f6f75d206762..a27a6a57e8b2 100644
--- a/fs/proc/array.c
+++ b/fs/proc/array.c
@@ -425,6 +425,21 @@ static inline void task_thp_status(struct seq_file *m, struct mm_struct *mm)
 	seq_printf(m, "THP_enabled:\t%d\n", thp_enabled);
 }
 
+#ifdef CONFIG_NUMA_BALANCING
+static inline void task_numa_balancing_status(struct seq_file *m,
+					      struct task_struct *task)
+{
+	seq_printf(m, "NumaB_mode:\t%s\n",
+		   task_numa_balancing_mode_name(task));
+}
+#else
+static inline void task_numa_balancing_status(struct seq_file *m,
+					      struct task_struct *task)
+{
+	seq_puts(m, "NumaB_mode:\tunsupported\n");
+}
+#endif
+
 static inline void task_untag_mask(struct seq_file *m, struct mm_struct *mm)
 {
 	seq_printf(m, "untag_mask:\t%#lx\n", mm_untag_mask(mm));
@@ -453,6 +468,7 @@ int proc_pid_status(struct seq_file *m, struct pid_namespace *ns,
 		task_untag_mask(m, mm);
 		mmput(mm);
 	}
+	task_numa_balancing_status(m, task);
 	task_sig(m, task);
 	task_cap(m, task);
 	task_seccomp(m, task);
diff --git a/include/linux/sched/numa_balancing.h b/include/linux/sched/numa_balancing.h
index bc413aae5915..69e79359fa22 100644
--- a/include/linux/sched/numa_balancing.h
+++ b/include/linux/sched/numa_balancing.h
@@ -47,6 +47,11 @@ static inline int task_numa_balancing_get_current(void)
 {
 	return task_numa_process_mode_enabled(current);
 }
+
+static inline const char *task_numa_balancing_mode_name(struct task_struct *p)
+{
+	return task_numa_process_mode_enabled(p) ? "enabled" : "disabled";
+}
 bool should_numa_migrate_memory(struct task_struct *p, struct folio *folio,
 				int src_nid, int dst_cpu);
 #else
-- 
2.20.1

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 4/4] Documentation: Describe per-process NUMA balancing control
  2026-09-08 12:24 [PATCH 0/4] sched/numa: Add per-process automatic NUMA balancing control Li Zhe
                   ` (2 preceding siblings ...)
  2026-09-08 12:24 ` [PATCH 3/4] proc: Report process NUMA balancing mode Li Zhe
@ 2026-09-08 12:24 ` Li Zhe
  3 siblings, 0 replies; 5+ messages in thread
From: Li Zhe @ 2026-09-08 12:24 UTC (permalink / raw)
  To: mingo, peterz, juri.lelli, vincent.guittot, dietmar.eggemann,
	rostedt, bsegall, mgorman, vschneid, kprateek.nayak
  Cc: linux-kernel, linux-fsdevel, linux-doc, lizhe.67

Document the PR_SET_NUMA_BALANCING and PR_GET_NUMA_BALANCING operations
near the kernel.numa_balancing sysctl documentation, and describe the
NumaB_mode field in /proc/<pid>/status.

Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
 Documentation/admin-guide/sysctl/kernel.rst | 16 ++++++++++++++++
 Documentation/filesystems/proc.rst          |  6 ++++++
 2 files changed, 22 insertions(+)

diff --git a/Documentation/admin-guide/sysctl/kernel.rst b/Documentation/admin-guide/sysctl/kernel.rst
index b6328cd0f43e..7f665c28828a 100644
--- a/Documentation/admin-guide/sysctl/kernel.rst
+++ b/Documentation/admin-guide/sysctl/kernel.rst
@@ -754,6 +754,22 @@ different types of memory (represented as different NUMA nodes) to
 place the hot pages in the fast memory.  This is implemented based on
 unmapping and page fault too.
 
+The ``PR_SET_NUMA_BALANCING`` and ``PR_GET_NUMA_BALANCING`` prctl(2)
+operations can be used by a process to opt out of automatic NUMA balancing
+or re-enable participation for the whole thread group.  Any thread in the
+thread group may change the process setting.  The setting is inherited by
+fork(2), is shared by threads created with clone(2) ``CLONE_THREAD`` and is
+preserved across execve(2).  The global ``numa_balancing`` sysctl remains
+the top-level control: a process-level enable does not turn automatic NUMA
+balancing on when it is disabled system-wide.  Memory policy still applies,
+so the process setting does not make VMAs participate when their policy
+prevents automatic NUMA balancing.
+
+Disabling the process setting stops scheduling new periodic NUMA scans for
+that process. Existing NUMA hinting PTEs and already queued work are not
+actively cleared and may still drain naturally as the disabled state takes
+effect.
+
 numa_balancing_promote_rate_limit_MBps
 ======================================
 
diff --git a/Documentation/filesystems/proc.rst b/Documentation/filesystems/proc.rst
index c102b62023cd..a4e9037a3161 100644
--- a/Documentation/filesystems/proc.rst
+++ b/Documentation/filesystems/proc.rst
@@ -188,6 +188,7 @@ read the file /proc/PID/status::
   HugetlbPages:          0 kB
   CoreDumping:    0
   THP_enabled:	  1
+  NumaB_mode:     enabled
   Threads:        1
   SigQ:   0/28578
   SigPnd: 0000000000000000
@@ -274,6 +275,11 @@ It's slow but very precise.
  THP_enabled                 process is allowed to use THP (returns 0 when
                              PR_SET_THP_DISABLE is set on the process to disable
                              THP completely, not just partially)
+ NumaB_mode                  process automatic NUMA balancing mode:
+                             enabled or disabled when supported, otherwise
+                             unsupported; the effective behavior also depends
+                             on the global numa_balancing sysctl and memory
+                             policy
  Threads                     number of threads
  SigQ                        number of signals queued/max. number for queue
  SigPnd                      bitmap of pending signals for the thread
-- 
2.20.1

^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-09-08 12:27 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-08 12:24 [PATCH 0/4] sched/numa: Add per-process automatic NUMA balancing control Li Zhe
2026-09-08 12:24 ` [PATCH 1/4] sched/numa: Track per-process automatic NUMA balancing mode Li Zhe
2026-09-08 12:24 ` [PATCH 2/4] sched/numa: Add prctl controls for process mode Li Zhe
2026-09-08 12:24 ` [PATCH 3/4] proc: Report process NUMA balancing mode Li Zhe
2026-09-08 12:24 ` [PATCH 4/4] Documentation: Describe per-process NUMA balancing control Li Zhe

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox