From: Tim Chen <tim.c.chen@linux.intel.com>
To: Peter Zijlstra <peterz@infradead.org>, Ingo Molnar <mingo@redhat.com>
Cc: Tim Chen <tim.c.chen@linux.intel.com>,
Vincent Guittot <vincent.guittot@linaro.org>,
Qais Yousef <qyousef@layalina.io>,
K Prateek Nayak <kprateek.nayak@amd.com>,
Juri Lelli <juri.lelli@redhat.com>,
Dietmar Eggemann <dietmar.eggemann@arm.com>,
Valentin Schneider <vschneid@redhat.com>,
Madadi Vineeth Reddy <vineethr@linux.ibm.com>,
Shrikanth Hegde <sshegde@linux.ibm.com>,
Jianyong Wu <jianyong.wu@outlook.com>,
Yangyu Chen <cyy@cyyself.name>,
Tingyin Duan <tingyin.duan@gmail.com>,
Vern Hao <vernhao@tencent.com>, Vern Hao <haoxing990@gmail.com>,
Len Brown <len.brown@intel.com>, Aubrey Li <aubrey.li@intel.com>,
Zhao Liu <zhao1.liu@intel.com>, Chen Yu <yu.chen.surf@gmail.com>,
Chen Yu <yu.c.chen@intel.com>,
Adam Li <adamli@os.amperecomputing.com>,
Aaron Lu <ziqianlu@bytedance.com>,
Tim Chen <tim.c.chen@intel.com>, Josh Don <joshdon@google.com>,
Luo Gengkun <luogengkun2@huawei.com>,
Gavin Guo <gavinguo@igalia.com>, Yi Lai <yi1.lai@intel.com>,
Ricardo Neri <ricardo.neri@intel.com>,
linux-kernel@vger.kernel.org, linux-api@vger.kernel.org
Subject: [RFC PATCH 4/7] sched/cache: Add prctl to manage per process cache scheduling groups
Date: Fri, 28 Aug 2026 15:29:11 -0700 [thread overview]
Message-ID: <50fe2db1a62ea2376a87d0c14778b1ff456d11ec.1787955777.git.tim.c.chen@linux.intel.com> (raw)
In-Reply-To: <cover.1787955777.git.tim.c.chen@linux.intel.com>
Derived from core scheduling's prctl interface.
Implement the controls for cache aware scheduling on a process:
int prctl(PR_SCHED_CACHE, unsigned long subop, pid_t pid,
unsigned long arg4, unsigned long type);
pid argument: the PID of the task the operation applies to, that is the
destination of the group assignment. 0 means "the calling task."
pid_type : PIDTYPE_PID targets the single thread, PIDTYPE_TGID the whole
thread group and PIDTYPE_PGID the process group of that task.
PR_SCHED_CACHE_GET only accepts PIDTYPE_PID.
arg4 argument: the address to store the cookie for PR_SCHED_CACHE_GET,
the pid to take the group from for PR_SCHED_CACHE_SHARE_FROM, where 0
means "the calling task".
The second argument (subop) selects one of the following operations:
1. PR_SCHED_CACHE_GET
Retrieve the cache aware scheduling cookie of a task.
u64 cookie;
prctl(PR_SCHED_CACHE, PR_SCHED_CACHE_GET, pid, (unsigned long)&cookie,
PIDTYPE_PID);
2. PR_SCHED_CACHE_CREATE
Create a new cache scheduling group and assign it to the target pid,
prctl(PR_SCHED_CACHE, PR_SCHED_CACHE_CREATE, pid, 0, PIDTYPE_TGID);
3. PR_SCHED_CACHE_SHARE_FROM
Copy the group of the task given in arg4 to the target task.
/* pull 1234's group onto the caller */
prctl(PR_SCHED_CACHE, PR_SCHED_CACHE_SHARE_FROM, 0, 1234, PIDTYPE_PID);
/* push the caller's group onto the thread group of 1234 */
prctl(PR_SCHED_CACHE, PR_SCHED_CACHE_SHARE_FROM, 1234, 0, PIDTYPE_TGID);
/* let the thread group of 1234 share 5678's group */
prctl(PR_SCHED_CACHE, PR_SCHED_CACHE_SHARE_FROM, 1234, 5678, PIDTYPE_TGID);
Unlike the core scheduling's PR_SCHED_CORE_SHARE_FROM and
PR_SCHED_CORE_SHARE_TO, there is no need to involve the current task: a
daemon(scheqos, eg) can handle two tasks without joining their group,
which would pollute the group's per LLC occupancy statistics.
Co-developed-by: Chen Yu <yu.c.chen@intel.com>
Signed-off-by: Chen Yu <yu.c.chen@intel.com>
Signed-off-by: Tim Chen <tim.c.chen@linux.intel.com>
---
fs/exec.c | 18 ++-
include/linux/sched.h | 4 +
include/uapi/linux/prctl.h | 7 +
kernel/exit.c | 13 +-
kernel/fork.c | 25 +++-
kernel/sched/cache_sched.c | 274 +++++++++++++++++++++++++++++++++++++
kernel/sched/fair.c | 8 +-
kernel/sys.c | 5 +
8 files changed, 341 insertions(+), 13 deletions(-)
diff --git a/fs/exec.c b/fs/exec.c
index a501e2ec84a0..4448ea123481 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -884,12 +884,22 @@ static int exec_mmap(struct linux_binprm *bprm)
{
struct sched_cache_group *old_grp, *new_grp;
- old_grp = rcu_dereference_protected(tsk->sched_cache_grp, true);
-
- /* Acquire the reference before publishing the pointer. */
+ /*
+ * Acquire the reference before publishing the pointer: once
+ * tsk->sched_cache_grp is visible, a concurrent
+ * prctl(PR_SCHED_CACHE) writer may pick the group up as its
+ * old_grp and drop a reference we have not taken yet.
+ *
+ * pi_lock serializes the exchange against such a writer. IRQs
+ * are already disabled here, so a plain raw_spin_lock()
+ * suffices.
+ */
new_grp = sched_cache_group_get(mm->sched_cache_grp);
- rcu_assign_pointer(tsk->sched_cache_grp, new_grp);
+ raw_spin_lock(&tsk->pi_lock);
+ old_grp = sched_cache_grp_replace(tsk, new_grp);
+ raw_spin_unlock(&tsk->pi_lock);
+
if (old_grp)
sched_cache_group_put(old_grp);
}
diff --git a/include/linux/sched.h b/include/linux/sched.h
index e25347aa2cc5..79f0079c3aa1 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -2407,7 +2407,11 @@ void sched_cache_group_put(struct sched_cache_group *grp);
struct sched_cache_group *sched_cache_group_get(struct sched_cache_group *grp);
struct sched_cache_group *task_cache_group_get(struct task_struct *p);
struct sched_cache_group *
+sched_cache_grp_replace(struct task_struct *p, struct sched_cache_group *grp);
+struct sched_cache_group *
sched_cache_alloc_group(struct sched_cache_time __percpu *pcpu_sched);
+int sched_cache_prctl(int option, unsigned long arg2, unsigned long arg3,
+ unsigned long arg4, unsigned long arg5);
#else
diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h
index b6ec6f693719..fed7bb028f9a 100644
--- a/include/uapi/linux/prctl.h
+++ b/include/uapi/linux/prctl.h
@@ -416,4 +416,11 @@ struct prctl_mm_map {
# define PR_CFI_DISABLE _BITUL(1)
# define PR_CFI_LOCK _BITUL(2)
+/* Cache-aware scheduling */
+#define PR_SCHED_CACHE 82
+# define PR_SCHED_CACHE_GET 0
+# define PR_SCHED_CACHE_CREATE 1
+# define PR_SCHED_CACHE_SHARE_FROM 2
+# define PR_SCHED_CACHE_MAX 3
+
#endif /* _LINUX_PRCTL_H */
diff --git a/kernel/exit.c b/kernel/exit.c
index 83fa28b3416b..d140429046d2 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -592,10 +592,17 @@ static void exit_mm(void)
#ifdef CONFIG_SCHED_CACHE
{
- struct sched_cache_group *grp =
- rcu_dereference_protected(current->sched_cache_grp, true);
+ struct sched_cache_group *grp;
+ unsigned long flags;
- rcu_assign_pointer(current->sched_cache_grp, NULL);
+ /*
+ * pi_lock serializes the clear against a concurrent
+ * prctl(PR_SCHED_CACHE) writer targeting this task, so the
+ * reference is dropped exactly once.
+ */
+ raw_spin_lock_irqsave(¤t->pi_lock, flags);
+ grp = sched_cache_grp_replace(current, NULL);
+ raw_spin_unlock_irqrestore(¤t->pi_lock, flags);
if (grp)
sched_cache_group_put(grp);
diff --git a/kernel/fork.c b/kernel/fork.c
index 195b7807ddbb..9399f5e2070e 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1601,13 +1601,28 @@ static int copy_mm(u64 clone_flags, struct task_struct *tsk)
tsk->active_mm = mm;
#ifdef CONFIG_SCHED_CACHE
{
+ struct sched_cache_group *grp;
+
/*
- * A task holds its own reference on the group, separate from
- * the reference held by its mm_struct. Acquire it before
- * publishing the pointer.
+ * For CLONE_VM (threads): inherit the parent's
+ * sched_cache_grp, which may be a cookie-based group different
+ * from mm->sched_cache_grp. For kernel thread, don't assign
+ * it a sched cache group.
+ *
+ * For new processes (own mm): use the new mm's group. The mm
+ * has just been created and is not visible to anyone else, so
+ * there is no race against a concurrent writer and the mm's
+ * own reference keeps the group alive. Take the refcount
+ * directly, without RCU.
*/
- struct sched_cache_group *grp =
- sched_cache_group_get(mm->sched_cache_grp);
+ if (clone_flags & CLONE_VM) {
+ if (tsk->flags & PF_KTHREAD)
+ grp = NULL;
+ else
+ grp = task_cache_group_get(current);
+ } else {
+ grp = sched_cache_group_get(mm->sched_cache_grp);
+ }
rcu_assign_pointer(tsk->sched_cache_grp, grp);
}
diff --git a/kernel/sched/cache_sched.c b/kernel/sched/cache_sched.c
index 860209a47931..d1932f0c5ee8 100644
--- a/kernel/sched/cache_sched.c
+++ b/kernel/sched/cache_sched.c
@@ -83,3 +83,277 @@ sched_cache_alloc_group(struct sched_cache_time __percpu *_pcpu_sched)
sched_cache_group_init(grp, _pcpu_sched);
return grp;
}
+
+static struct sched_cache_group *sched_cache_alloc_all(void)
+{
+ struct sched_cache_time __percpu *pcpu_sched;
+
+ pcpu_sched = alloc_percpu(struct sched_cache_time);
+ if (!pcpu_sched)
+ return NULL;
+
+ return sched_cache_alloc_group(pcpu_sched);
+}
+
+#ifdef CONFIG_NUMA_BALANCING
+/*
+ * When prctl() moves a task between groups, move its footprint estimate too:
+ * otherwise the group it leaves over-estimates its footprint forever, and the
+ * group it joins under-estimates it once the task exits.
+ */
+static void sched_cache_xfer_footprint(struct task_struct *p,
+ struct sched_cache_group *old,
+ struct sched_cache_group *new)
+{
+ unsigned long fp, sub;
+
+ if (!old || !new || old == new)
+ return;
+
+ sub = READ_ONCE(p->total_numa_faults);
+ if (!sub)
+ return;
+
+ fp = READ_ONCE(old->footprint);
+ sub = min(fp, sub);
+ WRITE_ONCE(old->footprint, fp - sub);
+
+ fp = READ_ONCE(new->footprint);
+ WRITE_ONCE(new->footprint, fp + sub);
+}
+#else
+static inline void sched_cache_xfer_footprint(struct task_struct *p,
+ struct sched_cache_group *old,
+ struct sched_cache_group *new)
+{
+}
+#endif /* CONFIG_NUMA_BALANCING */
+
+/* Swap a task's cache group pointer and return the previous one. */
+struct sched_cache_group *
+sched_cache_grp_replace(struct task_struct *p, struct sched_cache_group *grp)
+{
+ struct sched_cache_group *old;
+
+ lockdep_assert_held(&p->pi_lock);
+ old = rcu_dereference_protected(p->sched_cache_grp,
+ lockdep_is_held(&p->pi_lock));
+ rcu_assign_pointer(p->sched_cache_grp, grp);
+
+ return old;
+}
+
+static void __sched_cache_set(struct task_struct *p,
+ struct sched_cache_group *grp)
+{
+ struct sched_cache_group *old_grp;
+ unsigned long flags;
+
+ grp = sched_cache_group_get(grp);
+
+ /*
+ * p->pi_lock serializes the exchange against concurrent writers
+ * (other prctl callers as well as exec_mmap()/exit_mm()). Without
+ * it two writers could fetch the same old pointer and each drop a
+ * reference, over-decrementing the refcount.
+ */
+ raw_spin_lock_irqsave(&p->pi_lock, flags);
+
+ /*
+ * Avoid increasing the refcount for an exiting task,
+ * otherwise the grp can not be put after the task exits,
+ * and cause memory leak:
+ * CPU0 (prctl) CPU1 (task exiting)
+ * ---- ----
+ * exit_mm()
+ * put(p->sched_cache_grp)
+ * free(old_grp)
+ *
+ * __sched_cache_set(p, new_grp)
+ *
+ * free_task()
+ * free(p)
+ * the "freed" p is unable to put new_grp
+ *
+ * PF_EXITING is set in exit_signals() without p->pi_lock held, so this
+ * test on its own would only narrow the window. What closes it is that
+ * exit_mm() takes p->pi_lock to clear the pointer, and exit_signals()
+ * runs before exit_mm(). So of the two possible orders:
+ *
+ * - we get pi_lock before exit_mm() does: we install new_grp and its
+ * reference, and exit_mm() subsequently drops it, because it puts
+ * whatever sched_cache_grp_replace() hands back.
+ *
+ * - we get pi_lock after exit_mm() released it: the lock's release/
+ * acquire ordering makes the PF_EXITING store visible to us, so the
+ * test below fires and we install nothing.
+ *
+ * Either way the reference is dropped exactly once. The test is thus
+ * about not leaving a reference behind on a task that is already past
+ * exit_mm(), not about racing with the PF_EXITING store itself.
+ */
+ if (p->flags & PF_EXITING) {
+ raw_spin_unlock_irqrestore(&p->pi_lock, flags);
+ if (grp)
+ sched_cache_group_put(grp);
+ return;
+ }
+
+ old_grp = sched_cache_grp_replace(p, grp);
+ raw_spin_unlock_irqrestore(&p->pi_lock, flags);
+
+ /* Carry this task's footprint estimate to the group it just joined. */
+ sched_cache_xfer_footprint(p, old_grp, grp);
+
+ if (old_grp)
+ sched_cache_group_put(old_grp);
+}
+
+static struct task_struct *sched_cache_find_get_task(unsigned long vpid)
+{
+ struct task_struct *p;
+
+ guard(rcu)();
+ p = vpid ? find_task_by_vpid(vpid) : current;
+ if (p)
+ get_task_struct(p);
+
+ return p;
+}
+
+/*
+ * arg2: subcommand,
+ * arg3: destination pid, SHARE_FROM copies the group of arg4 to arg3
+ * arg4: source pid, or cookie out ptr for GET
+ * arg5: pid type, the scope of the destination
+ *
+ */
+int sched_cache_prctl(int option, unsigned long arg2, unsigned long arg3,
+ unsigned long arg4, unsigned long arg5)
+{
+ struct task_struct *dst = NULL, *src = NULL, *p;
+ struct sched_cache_group *grp = NULL;
+ enum pid_type type = arg5;
+ struct pid *pid_grp;
+ int err = 0;
+
+ if (arg2 >= PR_SCHED_CACHE_MAX || arg3 > INT_MAX ||
+ arg5 > PIDTYPE_PGID)
+ return -EINVAL;
+
+ /* only GET and SHARE_FROM take a 4th argument */
+ if (arg4 && arg2 != PR_SCHED_CACHE_GET &&
+ arg2 != PR_SCHED_CACHE_SHARE_FROM)
+ return -EINVAL;
+
+ dst = sched_cache_find_get_task(arg3);
+ if (!dst)
+ return -ESRCH;
+
+ if (dst->flags & PF_KTHREAD) {
+ err = -EINVAL;
+ goto out_task;
+ }
+
+ if (!ptrace_may_access(dst, PTRACE_MODE_READ_REALCREDS)) {
+ err = -EPERM;
+ goto out_task;
+ }
+
+ switch (arg2) {
+ case PR_SCHED_CACHE_GET: {
+ unsigned long id = 0;
+
+ if (type != PIDTYPE_PID || arg4 & 7) {
+ err = -EINVAL;
+ goto out_task;
+ }
+
+ grp = task_cache_group_get(dst);
+ if (grp)
+ ptr_to_hashval((void *)grp, &id);
+
+ if (arg4)
+ err = put_user((u64)id, (u64 __user *)arg4);
+
+ goto out_group;
+ }
+
+ case PR_SCHED_CACHE_CREATE:
+ /*
+ * Allocator owns ref 1, __sched_cache_set() acquires ref 2.
+ * The sched_cache_group_put() at out_group: drops ref 1, leaving
+ * ref 1 held by the task.
+ */
+ grp = sched_cache_alloc_all();
+ if (!grp) {
+ err = -ENOMEM;
+ goto out_task;
+ }
+ break;
+
+ case PR_SCHED_CACHE_SHARE_FROM:
+ if (arg4 > INT_MAX) {
+ err = -EINVAL;
+ goto out_task;
+ }
+
+ src = sched_cache_find_get_task(arg4);
+ if (!src) {
+ err = -ESRCH;
+ goto out_task;
+ }
+
+ if (src->flags & PF_KTHREAD) {
+ err = -EINVAL;
+ goto out_task;
+ }
+
+ if (!ptrace_may_access(src, PTRACE_MODE_READ_REALCREDS)) {
+ err = -EPERM;
+ goto out_task;
+ }
+
+ /* copy the group of src to dst */
+ grp = task_cache_group_get(src);
+ if (!grp) {
+ err = -ENOENT;
+ goto out_task;
+ }
+ break;
+
+ default:
+ err = -EINVAL;
+ goto out_task;
+ }
+
+ if (type == PIDTYPE_PID) {
+ __sched_cache_set(dst, grp);
+ goto out_group;
+ }
+
+ read_lock(&tasklist_lock);
+ pid_grp = task_pid_type(dst, type);
+
+ do_each_pid_thread(pid_grp, type, p) {
+ if (!ptrace_may_access(p, PTRACE_MODE_READ_REALCREDS)) {
+ err = -EPERM;
+ goto out_tasklist;
+ }
+ } while_each_pid_thread(pid_grp, type, p);
+
+ do_each_pid_thread(pid_grp, type, p) {
+ __sched_cache_set(p, grp);
+ } while_each_pid_thread(pid_grp, type, p);
+out_tasklist:
+ read_unlock(&tasklist_lock);
+
+out_group:
+ sched_cache_group_put(grp);
+out_task:
+ if (src)
+ put_task_struct(src);
+ if (dst)
+ put_task_struct(dst);
+ return err;
+}
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index be1f3568c3aa..d422b62ba987 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -1484,7 +1484,13 @@ static bool invalid_llc_nr(struct sched_cache_group *grp, struct task_struct *p,
{
int scale;
- if (get_nr_threads(p) <= 1)
+ /*
+ * Single threaded process that is not grouped with other threads
+ * do not need cache aware scheduling.
+ * A single-threaded process has a refcount of 2: from the mm and task
+ * respectively.
+ */
+ if (refcount_read(&grp->refcnt) <= 2 && get_nr_threads(p) <= 1)
return true;
/*
diff --git a/kernel/sys.c b/kernel/sys.c
index df69bd71de03..f13f1904654b 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -2907,6 +2907,11 @@ 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;
+#ifdef CONFIG_SCHED_CACHE
+ case PR_SCHED_CACHE:
+ error = sched_cache_prctl(option, arg2, arg3, arg4, arg5);
+ break;
+#endif
default:
trace_task_prctl_unknown(option, arg2, arg3, arg4, arg5);
error = -EINVAL;
--
2.32.0
next prev parent reply other threads:[~2026-08-28 22:23 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-28 22:29 [RFC PATCH 0/7] sched/cache: Per-task control of cache aware scheduling via prctl Tim Chen
2026-08-28 22:29 ` [RFC PATCH 1/7] sched/cache: Decouple sched_cache_group from mm Tim Chen
2026-08-28 22:29 ` [RFC PATCH 2/7] sched/cache: Introduce task_struct->sched_cache_grp Tim Chen
2026-08-28 22:29 ` [RFC PATCH 3/7] sched/cache: Extract sched_cache_alloc_group() helper Tim Chen
2026-08-28 22:29 ` Tim Chen [this message]
2026-08-28 22:29 ` [RFC PATCH 5/7] sched/cache: Allow a process to enable cache aware scheduling via prctl Tim Chen
2026-08-28 22:29 ` [RFC PATCH 6/7] sched/cache: Extend the enabled debugfs to more modes Tim Chen
2026-08-28 22:29 ` [RFC PATCH 7/7] sched/cache: Documentation: document the PR_SCHED_CACHE prctl Tim Chen
2026-08-29 9:27 ` [RFC PATCH 0/7] sched/cache: Per-task control of cache aware scheduling via prctl Peter Zijlstra
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=50fe2db1a62ea2376a87d0c14778b1ff456d11ec.1787955777.git.tim.c.chen@linux.intel.com \
--to=tim.c.chen@linux.intel.com \
--cc=adamli@os.amperecomputing.com \
--cc=aubrey.li@intel.com \
--cc=cyy@cyyself.name \
--cc=dietmar.eggemann@arm.com \
--cc=gavinguo@igalia.com \
--cc=haoxing990@gmail.com \
--cc=jianyong.wu@outlook.com \
--cc=joshdon@google.com \
--cc=juri.lelli@redhat.com \
--cc=kprateek.nayak@amd.com \
--cc=len.brown@intel.com \
--cc=linux-api@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=luogengkun2@huawei.com \
--cc=mingo@redhat.com \
--cc=peterz@infradead.org \
--cc=qyousef@layalina.io \
--cc=ricardo.neri@intel.com \
--cc=sshegde@linux.ibm.com \
--cc=tim.c.chen@intel.com \
--cc=tingyin.duan@gmail.com \
--cc=vernhao@tencent.com \
--cc=vincent.guittot@linaro.org \
--cc=vineethr@linux.ibm.com \
--cc=vschneid@redhat.com \
--cc=yi1.lai@intel.com \
--cc=yu.c.chen@intel.com \
--cc=yu.chen.surf@gmail.com \
--cc=zhao1.liu@intel.com \
--cc=ziqianlu@bytedance.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®