Hi Yangyu,
On 7/22/2026 5:10 PM, Yangyu Chen wrote:
Cache aware scheduling is currently controlled only through global
debugfs knobs, but the right aggressiveness is workload and platform
specific. A multi-threaded Verilator run is one example: its RSS is
large while only a small part of it is hot, so an RSS-based footprint
estimate should not decide whether it is aggregated; and packing its
threads onto the SMT siblings of one LLC beats spreading them across
LLCs on some platforms (e.g. AMD EPYC Turin) but not on others (e.g.
EPYC Milan). Such choices cannot be made globally for the whole
machine. Add a prctl interface to override the knobs per process
(per mm_struct):
prctl(PR_SCHED_CACHE, PR_SCHED_CACHE_SET, attr, value, 0);
prctl(PR_SCHED_CACHE, PR_SCHED_CACHE_GET, attr, &value, 0);
A single prctl command implements both directions, like
PR_RSEQ_SLICE_EXTENSION. The attributes are a per-process enable
(effective only while the feature is globally active), the two
aggregation tolerances, the overaggr percentage (applied where a
task's own migration is admitted; group level statistics span many
processes and keep using the global value), and an inherit mask
selecting which attributes an mm created by execve() keeps. fork()
always inherits everything, and the mask itself lives on the
task_struct so it survives both, which lets a numactl-like launcher
configure a workload and exec it.
The overrides live in mm->sc_stat with -1 meaning "follow the global
default"; GET stores the raw value through an int pointer so this
sentinel round-trips without being mistaken for an errno.
mm_init_sched() gains the creating task to tell fork (p != current)
from exec (p == current) apart. A disabled mm has its preferred LLC
invalidated at the existing invalidation points, so all group-level
statistics self-neutralize.
Also sync the tools/perf/trace/beauty copy of prctl.h.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Yangyu Chen <[email protected]>
[ ... ]
+static int sched_cache_set_attr(unsigned long attr, unsigned long val)
+{
+ struct mm_struct *mm = current->mm;
As preparation work, should we first decouple sc_stat from
mm_struct and tie this stat to per-task task_struct? In this
way, we could have per-task cache preference control and extend
it to tasks/threads/process/cgroup if needed, which looks more
flexible IMO. We have a proposal here:
https://github.com/chen-yu-surf/linux/commit/bd43a0b6dd189d5091fb88630208cb7bf67b3165.patch
which introduces a pointer in task_struct:
struct sched_cache_group __rcu *sched_cache_grp;
+ bool def = sched_cache_val_default(val);
+ int ival = def ? -1 : (int)val;
+
+ switch (attr) {
+ case PR_SCHED_CACHE_ENABLE:
+ if (!def && val > 1)
+ return -EINVAL;
+ WRITE_ONCE(mm->sc_stat.user_enabled, ival);
+ /*
+ * Drop the preferred LLC hint on any change: a process
+ * that became disabled must stop being honored right
+ * away, and one that became enabled re-establishes the
+ * hint within an epoch anyway. This is best effort: an
+ * in-flight task_cache_work() scan re-checks the enable
+ * before publishing a new preference, and a lost race
+ * is corrected at the next tick.
+ */
+ WRITE_ONCE(mm->sc_stat.cpu, -1);
+ break;
After we switching from per mm_struct to per task control, we could provide
fine-gain control at task/process/process group granularity(similar to
core-scheduling)
int prctl(PR_SCHED_CACHE, unsigned long subop, pid_t pid,
unsigned long cookie, unsigned long type);
pid argument: the PID of the target task. 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 the target task.
And the proposal is here:
https://github.com/chen-yu-surf/linux/commit/17718b7cef1d03948e9fd3bcd0b5a49aba7aae2d.patch
thanks,
Chenyu