Re: [PATCH v7 00/31] context_tracking,x86: Defer some IPIs until a user->kernel transition

Paul E. McKenney
2025-11-15T02:30:35Z

On Fri, Nov 14, 2025 at 04:29:31PM -0800, Andy Lutomirski wrote:
>
>
> On Fri, Nov 14, 2025, at 12:03 PM, Paul E. McKenney wrote:
> > On Fri, Nov 14, 2025 at 10:45:08AM -0800, Andy Lutomirski wrote:
> >>
> >>
> >> On Fri, Nov 14, 2025, at 10:14 AM, Paul E. McKenney wrote:
> >> > On Fri, Nov 14, 2025 at 09:22:35AM -0800, Andy Lutomirski wrote:
> >> >>
> >>
> >> >> > Oh, any another primitive would be possible: one CPU could plausibly
> >> >> > execute another CPU's interrupts or soft-irqs or whatever by taking a
> >> >> > special lock that would effectively pin the remote CPU in user mode --
> >> >> > you'd set a flag in the target cpu_flags saying "pin in USER mode" and
> >> >> > the transition on that CPU to kernel mode would then spin on entry to
> >> >> > kernel mode and wait for the lock to be released. This could plausibly
> >> >> > get a lot of the on_each_cpu callers to switch over in one fell swoop:
> >> >> > anything that needs to synchronize to the remote CPU but does not need
> >> >> > to poke its actual architectural state could be executed locally while
> >> >> > the remote CPU is pinned.
> >> >
> >> > It would be necessary to arrange for the remote CPU to remain pinned
> >> > while the local CPU executed on its behalf. Does the above approach
> >> > make that happen without re-introducing our current context-tracking
> >> > overhead and complexity?
> >>
> >> Using the pseudo-implementation farther down, I think this would be like:
> >>
> >> if (my_state->status == INDETERMINATE) {
> >> // we were lazy and we never promised to do work atomically.
> >> atomic_set(&my_state->status, KERNEL);
> >> this_entry_work = 0;
> >> /* we are definitely not pinned in this path */
> >> } else {
> >> // we were not lazy and we promised we would do work atomically
> >> atomic exchange the entire state to { .work = 0, .status = KERNEL }
> >> this_entry_work = (whatever we just read);
> >> if (this_entry_work & PINNED) {
> >> u32 this_cpu_pin_count = this_cpu_ptr(pin_count);
> >> while (atomic_read(&this_cpu_pin_count)) {
> >> cpu_relax();
> >> }
> >> }
> >> }
> >>
> >> and we'd have something like:
> >>
> >> bool try_pin_remote_cpu(int cpu)
> >> {
> >> u32 *remote_pin_count = ...;
> >> struct fancy_cpu_state *remote_state = ...;
> >> atomic_inc(remote_pin_count); // optimistic
> >>
> >> // Hmm, we do not want that read to get reordered with the inc, so we probably
> >> // need a full barrier or seq_cst. How does Linux spell that? C++ has atomic::load
> >> // with seq_cst and maybe the optimizer can do the right thing. Maybe it's:
> >> smp_mb__after_atomic();
> >>
> >> if (atomic_read(&remote_state->status) == USER) {
> >> // Okay, it's genuinely pinned.
> >> return true;
> >>
> >> // egads, if this is some arch with very weak ordering,
> >> // do we need to be concerned that we just took a lock but we
> >> // just did a relaxed read and therefore a subsequent access
> >> // that thinks it's locked might appear to precede the load and therefore
> >> // somehow get surprisingly seen out of order by the target cpu?
> >> // maybe we wanted atomic_read_acquire above instead?
> >> } else {
> >> // We might not have successfully pinned it
> >> atomic_dec(remote_pin_count);
> >> }
> >> }
> >>
> >> void unpin_remote_cpu(int cpu)
> >> {
> >> atomic_dec(remote_pin_count();
> >> }
> >>
> >> and we'd use it like:
> >>
> >> if (try_pin_remote_cpu(cpu)) {
> >> // do something useful
> >> } else {
> >> send IPI;
> >> }
> >>
> >> but we'd really accumulate the set of CPUs that need the IPIs and do them all at once.
> >>
> >> I ran the theorem prover that lives inside my head on this code using the assumption that the machine is a well-behaved x86 system and it said "yeah, looks like it might be correct". I trust an actual formalized system or someone like you who is genuinely very good at this stuff much more than I trust my initial impression :)
> >
> > Let's start with requirements, non-traditional though that might be. ;-)
>
> That's ridiculous! :-)

;-) ;-) ;-)

> > An "RCU idle" CPU is either in deep idle or executing in nohz_full
> > userspace.
> >
> > 1. If the RCU grace-period kthread sees an RCU-idle CPU, then:
> >
> > a. Everything that this CPU did before entering RCU-idle
> > state must be visible to sufficiently later code executed
> > by the grace-period kthread, and:
> >
> > b. Everything that the CPU will do after exiting RCU-idle
> > state must *not* have been visible to the grace-period
> > kthread sufficiently prior to having sampled this
> > CPU's state.
> >
> > 2. If the RCU grace-period kthread sees an RCU-nonidle CPU, then
> > it depends on whether this is the same nonidle sojourn as was
> > initially seen. (If the kthread initially saw the CPU in an
> > RCU-idle state, it would not have bothered resampling.)
> >
> > a. If this is the same nonidle sojourn, then there are no
> > ordering requirements. RCU must continue to wait on
> > this CPU.
> >
> > b. Otherwise, everything that this CPU did before entering
> > its last RCU-idle state must be visible to sufficiently
> > later code executed by the grace-period kthread.
> > Similar to (1a) above.
> >
> > 3. If a given CPU quickly switches into and out of RCU-idle
> > state, and it is always in RCU-nonidle state whenever the RCU
> > grace-period kthread looks, RCU must still realize that this
> > CPU has passed through at least one quiescent state.
> >
> > This is why we have a counter for RCU rather than just a
> > simple state.

Sigh...

4. Entering and exiting either idle or nohz_full usermode execution
at task level can race with interrupts and NMIs attempting to
make this same transition. (And you had much to do with the
current code that mediates the NMI races!)

> ...
>
> > I am not seeing how the third requirement above is met, though. I have
> > not verified the sampling code that the RCU grace-period kthread is
> > supposed to use because I am not seeing it right off-hand.
>
> This is ct_rcu_watching_cpu_acquire, right? Lemme think. Maybe there's even a way to do everything I'm suggesting without changing that interface.

Yes.

Also, the ordering can sometimes be implemnted at a distance, but it
really does have to be there.

> >> It wouldn't be outrageous to have real-time imply the full USER transition.
> >
> > We could have special code for RT, but this of course increases the
> > complexity. Which might be justified by sufficient speedup.
>
> I'm thinking that the code would be arranged so that going to user mode in the USER or the INTERMEDIATE state would be fully valid and would just have different performance characteristics. So the only extra complexity here would be the actual logic to choose which state to go to. and...
> >
> > And yes, many distros enable NO_HZ_FULL by default. I will refrain from
> > suggesting additional static branches. ;-)
>
> I'm not even suggesting a static branch. I think the potential performance wins are big enough to justify a bona fide ordinary if statement or two :) I'm also contemplating whether it could make sense to make this whole thing be unconditionally configured in if the performance in the case where no one uses it (i.e. everything is INTERMEDIATE instead of USER) is good enough.

Both sound like they would be nice to have...

> I will ponder.

...give or take feasibility, of course! ;-)

Thanx, Paul