Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

x86/resctl: fix scheduler confusion with 'current'

The implementation of 'current' on x86 is very intentionally special: it
is a very common thing to look up, and it uses 'this_cpu_read_stable()'
to get the current thread pointer efficiently from per-cpu storage.

And the keyword in there is 'stable': the current thread pointer never
changes as far as a single thread is concerned. Even if when a thread
is preempted, or moved to another CPU, or even across an explicit call
'schedule()' that thread will still have the same value for 'current'.

It is, after all, the kernel base pointer to thread-local storage.
That's why it's stable to begin with, but it's also why it's important
enough that we have that special 'this_cpu_read_stable()' access for it.

So this is all done very intentionally to allow the compiler to treat
'current' as a value that never visibly changes, so that the compiler
can do CSE and combine multiple different 'current' accesses into one.

However, there is obviously one very special situation when the
currently running thread does actually change: inside the scheduler
itself.

So the scheduler code paths are special, and do not have a 'current'
thread at all. Instead there are _two_ threads: the previous and the
next thread - typically called 'prev' and 'next' (or prev_p/next_p)
internally.

So this is all actually quite straightforward and simple, and not all
that complicated.

Except for when you then have special code that is run in scheduler
context, that code then has to be aware that 'current' isn't really a
valid thing. Did you mean 'prev'? Did you mean 'next'?

In fact, even if then look at the code, and you use 'current' after the
new value has been assigned to the percpu variable, we have explicitly
told the compiler that 'current' is magical and always stable. So the
compiler is quite free to use an older (or newer) value of 'current',
and the actual assignment to the percpu storage is not relevant even if
it might look that way.

Which is exactly what happened in the resctl code, that blithely used
'current' in '__resctrl_sched_in()' when it really wanted the new
process state (as implied by the name: we're scheduling 'into' that new
resctl state). And clang would end up just using the old thread pointer
value at least in some configurations.

This could have happened with gcc too, and purely depends on random
compiler details. Clang just seems to have been more aggressive about
moving the read of the per-cpu current_task pointer around.

The fix is trivial: just make the resctl code adhere to the scheduler
rules of using the prev/next thread pointer explicitly, instead of using
'current' in a situation where it just wasn't valid.

That same code is then also used outside of the scheduler context (when
a thread resctl state is explicitly changed), and then we will just pass
in 'current' as that pointer, of course. There is no ambiguity in that
case.

The fix may be trivial, but noticing and figuring out what went wrong
was not. The credit for that goes to Stephane Eranian.

Reported-by: Stephane Eranian <eranian@google.com>
Link: https://lore.kernel.org/lkml/20230303231133.1486085-1-eranian@google.com/
Link: https://lore.kernel.org/lkml/alpine.LFD.2.01.0908011214330.3304@localhost.localdomain/
Reviewed-by: Nick Desaulniers <ndesaulniers@google.com>
Tested-by: Tony Luck <tony.luck@intel.com>
Tested-by: Stephane Eranian <eranian@google.com>
Tested-by: Babu Moger <babu.moger@amd.com>
Cc: stable@kernel.org
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

+10 -10
+6 -6
arch/x86/include/asm/resctrl.h
··· 49 49 * simple as possible. 50 50 * Must be called with preemption disabled. 51 51 */ 52 - static void __resctrl_sched_in(void) 52 + static inline void __resctrl_sched_in(struct task_struct *tsk) 53 53 { 54 54 struct resctrl_pqr_state *state = this_cpu_ptr(&pqr_state); 55 55 u32 closid = state->default_closid; ··· 61 61 * Else use the closid/rmid assigned to this cpu. 62 62 */ 63 63 if (static_branch_likely(&rdt_alloc_enable_key)) { 64 - tmp = READ_ONCE(current->closid); 64 + tmp = READ_ONCE(tsk->closid); 65 65 if (tmp) 66 66 closid = tmp; 67 67 } 68 68 69 69 if (static_branch_likely(&rdt_mon_enable_key)) { 70 - tmp = READ_ONCE(current->rmid); 70 + tmp = READ_ONCE(tsk->rmid); 71 71 if (tmp) 72 72 rmid = tmp; 73 73 } ··· 88 88 return val * scale; 89 89 } 90 90 91 - static inline void resctrl_sched_in(void) 91 + static inline void resctrl_sched_in(struct task_struct *tsk) 92 92 { 93 93 if (static_branch_likely(&rdt_enable_key)) 94 - __resctrl_sched_in(); 94 + __resctrl_sched_in(tsk); 95 95 } 96 96 97 97 void resctrl_cpu_detect(struct cpuinfo_x86 *c); 98 98 99 99 #else 100 100 101 - static inline void resctrl_sched_in(void) {} 101 + static inline void resctrl_sched_in(struct task_struct *tsk) {} 102 102 static inline void resctrl_cpu_detect(struct cpuinfo_x86 *c) {} 103 103 104 104 #endif /* CONFIG_X86_CPU_RESCTRL */
+2 -2
arch/x86/kernel/cpu/resctrl/rdtgroup.c
··· 314 314 * executing task might have its own closid selected. Just reuse 315 315 * the context switch code. 316 316 */ 317 - resctrl_sched_in(); 317 + resctrl_sched_in(current); 318 318 } 319 319 320 320 /* ··· 530 530 * Otherwise, the MSR is updated when the task is scheduled in. 531 531 */ 532 532 if (task == current) 533 - resctrl_sched_in(); 533 + resctrl_sched_in(task); 534 534 } 535 535 536 536 static void update_task_closid_rmid(struct task_struct *t)
+1 -1
arch/x86/kernel/process_32.c
··· 212 212 switch_fpu_finish(); 213 213 214 214 /* Load the Intel cache allocation PQR MSR. */ 215 - resctrl_sched_in(); 215 + resctrl_sched_in(next_p); 216 216 217 217 return prev_p; 218 218 }
+1 -1
arch/x86/kernel/process_64.c
··· 656 656 } 657 657 658 658 /* Load the Intel cache allocation PQR MSR. */ 659 - resctrl_sched_in(); 659 + resctrl_sched_in(next_p); 660 660 661 661 return prev_p; 662 662 }