]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
rseq: Optimize event setting
authorThomas Gleixner <tglx@linutronix.de>
Mon, 27 Oct 2025 08:45:14 +0000 (09:45 +0100)
committerIngo Molnar <mingo@kernel.org>
Tue, 4 Nov 2025 07:34:03 +0000 (08:34 +0100)
After removing the various condition bits earlier it turns out that one
extra information is needed to avoid setting event::sched_switch and
TIF_NOTIFY_RESUME unconditionally on every context switch.

The update of the RSEQ user space memory is only required, when either

  the task was interrupted in user space and schedules

or

  the CPU or MM CID changes in schedule() independent of the entry mode

Right now only the interrupt from user information is available.

Add an event flag, which is set when the CPU or MM CID or both change.

Evaluate this event in the scheduler to decide whether the sched_switch
event and the TIF bit need to be set.

It's an extra conditional in context_switch(), but the downside of
unconditionally handling RSEQ after a context switch to user is way more
significant. The utilized boolean logic minimizes this to a single
conditional branch.

Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Link: https://patch.msgid.link/20251027084307.578058898@linutronix.de
fs/exec.c
include/linux/rseq.h
include/linux/rseq_types.h
kernel/rseq.c
kernel/sched/core.c
kernel/sched/sched.h

index e45b298902691345ca4a2ab171c0ec8dcf456d62..90e47eb156abe7c01bcb43c973fff7a8dd48787f 100644 (file)
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1775,7 +1775,7 @@ out:
                force_fatal_sig(SIGSEGV);
 
        sched_mm_cid_after_execve(current);
-       rseq_sched_switch_event(current);
+       rseq_force_update();
        current->in_execve = 0;
 
        return retval;
index f5a43188023faafb23aecafc04b2a30fb7592606..abfbeb42d1a235673785d03e1aa2391d9251c7e1 100644 (file)
@@ -11,7 +11,8 @@ void __rseq_handle_notify_resume(struct pt_regs *regs);
 
 static inline void rseq_handle_notify_resume(struct pt_regs *regs)
 {
-       if (current->rseq.event.has_rseq)
+       /* '&' is intentional to spare one conditional branch */
+       if (current->rseq.event.sched_switch & current->rseq.event.has_rseq)
                __rseq_handle_notify_resume(regs);
 }
 
@@ -33,12 +34,75 @@ static inline void rseq_signal_deliver(struct ksignal *ksig, struct pt_regs *reg
        }
 }
 
-/* Raised from context switch and exevce to force evaluation on exit to user */
-static inline void rseq_sched_switch_event(struct task_struct *t)
+static inline void rseq_raise_notify_resume(struct task_struct *t)
 {
-       if (t->rseq.event.has_rseq) {
-               t->rseq.event.sched_switch = true;
-               set_tsk_thread_flag(t, TIF_NOTIFY_RESUME);
+       set_tsk_thread_flag(t, TIF_NOTIFY_RESUME);
+}
+
+/* Invoked from context switch to force evaluation on exit to user */
+static __always_inline void rseq_sched_switch_event(struct task_struct *t)
+{
+       struct rseq_event *ev = &t->rseq.event;
+
+       if (IS_ENABLED(CONFIG_GENERIC_IRQ_ENTRY)) {
+               /*
+                * Avoid a boat load of conditionals by using simple logic
+                * to determine whether NOTIFY_RESUME needs to be raised.
+                *
+                * It's required when the CPU or MM CID has changed or
+                * the entry was from user space.
+                */
+               bool raise = (ev->user_irq | ev->ids_changed) & ev->has_rseq;
+
+               if (raise) {
+                       ev->sched_switch = true;
+                       rseq_raise_notify_resume(t);
+               }
+       } else {
+               if (ev->has_rseq) {
+                       t->rseq.event.sched_switch = true;
+                       rseq_raise_notify_resume(t);
+               }
+       }
+}
+
+/*
+ * Invoked from __set_task_cpu() when a task migrates to enforce an IDs
+ * update.
+ *
+ * This does not raise TIF_NOTIFY_RESUME as that happens in
+ * rseq_sched_switch_event().
+ */
+static __always_inline void rseq_sched_set_task_cpu(struct task_struct *t, unsigned int cpu)
+{
+       t->rseq.event.ids_changed = true;
+}
+
+/*
+ * Invoked from switch_mm_cid() in context switch when the task gets a MM
+ * CID assigned.
+ *
+ * This does not raise TIF_NOTIFY_RESUME as that happens in
+ * rseq_sched_switch_event().
+ */
+static __always_inline void rseq_sched_set_task_mm_cid(struct task_struct *t, unsigned int cid)
+{
+       /*
+        * Requires a comparison as the switch_mm_cid() code does not
+        * provide a conditional for it readily. So avoid excessive updates
+        * when nothing changes.
+        */
+       if (t->rseq.ids.mm_cid != cid)
+               t->rseq.event.ids_changed = true;
+}
+
+/* Enforce a full update after RSEQ registration and when execve() failed */
+static inline void rseq_force_update(void)
+{
+       if (current->rseq.event.has_rseq) {
+               current->rseq.event.ids_changed = true;
+               current->rseq.event.sched_switch = true;
+               rseq_raise_notify_resume(current);
        }
 }
 
@@ -55,7 +119,7 @@ static inline void rseq_sched_switch_event(struct task_struct *t)
 static inline void rseq_virt_userspace_exit(void)
 {
        if (current->rseq.event.sched_switch)
-               set_tsk_thread_flag(current, TIF_NOTIFY_RESUME);
+               rseq_raise_notify_resume(current);
 }
 
 static inline void rseq_reset(struct task_struct *t)
@@ -91,6 +155,9 @@ static inline void rseq_fork(struct task_struct *t, u64 clone_flags)
 static inline void rseq_handle_notify_resume(struct pt_regs *regs) { }
 static inline void rseq_signal_deliver(struct ksignal *ksig, struct pt_regs *regs) { }
 static inline void rseq_sched_switch_event(struct task_struct *t) { }
+static inline void rseq_sched_set_task_cpu(struct task_struct *t, unsigned int cpu) { }
+static inline void rseq_sched_set_task_mm_cid(struct task_struct *t, unsigned int cid) { }
+static inline void rseq_force_update(void) { }
 static inline void rseq_virt_userspace_exit(void) { }
 static inline void rseq_fork(struct task_struct *t, u64 clone_flags) { }
 static inline void rseq_execve(struct task_struct *t) { }
index 7c123947bb98d9c1addae41ee7ea1131d3595dbe..a1389fff4fca183c932e45200a8f2519de0008bf 100644 (file)
@@ -11,20 +11,27 @@ struct rseq;
  * struct rseq_event - Storage for rseq related event management
  * @all:               Compound to initialize and clear the data efficiently
  * @events:            Compound to access events with a single load/store
- * @sched_switch:      True if the task was scheduled out
+ * @sched_switch:      True if the task was scheduled and needs update on
+ *                     exit to user
+ * @ids_changed:       Indicator that IDs need to be updated
  * @user_irq:          True on interrupt entry from user mode
  * @has_rseq:          True if the task has a rseq pointer installed
  * @error:             Compound error code for the slow path to analyze
  * @fatal:             User space data corrupted or invalid
+ *
+ * @sched_switch and @ids_changed must be adjacent and the combo must be
+ * 16bit aligned to allow a single store, when both are set at the same
+ * time in the scheduler.
  */
 struct rseq_event {
        union {
                u64                             all;
                struct {
                        union {
-                               u16             events;
+                               u32             events;
                                struct {
                                        u8      sched_switch;
+                                       u8      ids_changed;
                                        u8      user_irq;
                                };
                        };
index 148fb21030234192f240e9f3a45ce7ec7f4fa626..183dde75680848d3f0671d4823aaef2f2966f7a4 100644 (file)
@@ -464,7 +464,7 @@ SYSCALL_DEFINE4(rseq, struct rseq __user *, rseq, u32, rseq_len, int, flags, u32
         * are updated before returning to user-space.
         */
        current->rseq.event.has_rseq = true;
-       rseq_sched_switch_event(current);
+       rseq_force_update();
        return 0;
 
 efault:
index b75e8e1eca4acd0d0068cb8e8b83e693fa119fc8..579a8e93578fe4f8e0e22795754ea4bc16519d3f 100644 (file)
@@ -5118,7 +5118,6 @@ prepare_task_switch(struct rq *rq, struct task_struct *prev,
        kcov_prepare_switch(prev);
        sched_info_switch(rq, prev, next);
        perf_event_task_sched_out(prev, next);
-       rseq_sched_switch_event(prev);
        fire_sched_out_preempt_notifiers(prev, next);
        kmap_local_sched_out();
        prepare_task(next);
@@ -5316,6 +5315,12 @@ context_switch(struct rq *rq, struct task_struct *prev,
        /* switch_mm_cid() requires the memory barriers above. */
        switch_mm_cid(rq, prev, next);
 
+       /*
+        * Tell rseq that the task was scheduled in. Must be after
+        * switch_mm_cid() to get the TIF flag set.
+        */
+       rseq_sched_switch_event(next);
+
        prepare_lock_switch(rq, next, rf);
 
        /* Here we just switch the register state and the stack. */
index adfb6e3409d729d26a7ae664553d60fa8e165163..4838dda75b105120e8c167bfda340d1e106013ae 100644 (file)
@@ -2209,6 +2209,7 @@ static inline void __set_task_cpu(struct task_struct *p, unsigned int cpu)
        smp_wmb();
        WRITE_ONCE(task_thread_info(p)->cpu, cpu);
        p->wake_cpu = cpu;
+       rseq_sched_set_task_cpu(p, cpu);
 #endif /* CONFIG_SMP */
 }
 
@@ -3807,8 +3808,10 @@ static inline void switch_mm_cid(struct rq *rq,
                mm_cid_put_lazy(prev);
                prev->mm_cid = -1;
        }
-       if (next->mm_cid_active)
+       if (next->mm_cid_active) {
                next->last_mm_cid = next->mm_cid = mm_cid_get(rq, next, next->mm);
+               rseq_sched_set_task_mm_cid(next, next->mm_cid);
+       }
 }
 
 #else /* !CONFIG_SCHED_MM_CID: */