Re: [RFC-PATCH 1/2] mm: Add __GFP_NO_LOCKS flag

Thomas Gleixner
2020-08-13T23:59:09Z

On Fri, Aug 14 2020 at 00:06, peterz wrote:
> I'm still not getting it, how do we end up trying to allocate memory
> from under raw spinlocks if you're not allowed to use kfree_rcu() under
> one ?
>
> Can someone please spell out the actual problem?

Your actual problem is the heat wave. Get an icepack already :)

To set things straight:

1) kfree_rcu() which used to be just a conveniance wrapper around
call_rcu() always worked in any context except NMI.

Otherwise RT would have never worked or would have needed other
horrible hacks to defer kfree in certain contexts including
context switch.

2) RCU grew an optimization for kfree_rcu() which avoids the linked
list cache misses when a large number of objects is freed via
kfree_rcu(). Paul says it speeds up post grace period processing by
a factor of 8 which is impressive.

That's done by avoiding the linked list and storing the object
pointer in an array of pointers which is page sized. This array is
then freed in bulk without having to touch the rcu head linked list
which obviously avoids cache misses.

Occasionally kfree_rcu() needs to allocate a page for this but it
can fallback to the linked list when the allocation fails.

Inconveniantly this allocation happens with an RCU internal raw
lock held, but even if we could do the allocation outside the RCU
internal lock this would not solve the problem that kfree_rcu() can
be called in any context except NMI even on RT.

So RT forbids allocations from truly atomic contexts even with
GFP_ATOMIC/NOWAIT. GFP_ATOMIC is kinda meaningless on RT because
everything which calls it is in non-atomic context :) But still
GFP_ATOMIC or GFP_NOWAIT retain the semantics of !RT and do not go
down into deeper levels or wait for pages to become available.

3) #2 upsets lockdep (with the raw lock nesting enabled) rightfully
when RCU tries to allocate a page, the lockless page cache is empty
and it acquires zone->lock which is a regular spinlock

4) As kfree_rcu() can be used from any context except NMI and RT
relies on it we ran into a circular dependency problem.

If we disable that feature for RT then the problem would be solved
except that lockdep still would complain about taking zone->lock
within a forbidden context on !RT kernels.

Preventing that feature because of that is not a feasible option
either. Aside of that we discuss this postfactum, IOW the stuff is
upstream already despite the problem being known before.

5) Ulad posted patches which introduce a new GFP allocation mode
which makes the allocation fail if the lockless cache is empty,
i.e. it does not try to touch zone->lock in that case.

That works perfectly fine on RT and !RT, makes lockdep happy and
Paul is happy as well.

If the lockless cache, which works perfectly fine on RT, is empty
then the performance of kfree_rcu() post grace period processing is
probably not making the situation of the system worse.

Michal is not fond of the new GFP flag and wants to explore options
to avoid that completely. But so far there is no real feasible
solution.

A) It was suggested to make zone->lock raw, but that's a total
disaster latency wise. With just a kernel compile (!RT kernel)
spinwait times are in the hundreds of microseconds.

RT tests showed max latency of cyclictest go up from 30 to 220
microseconds, i.e. factor 7 just because of that.

So not really great.

It would have had the charm to keep the semantics of GFP_NOWAIT,
but OTOH even if it would work I'm pretty oppposed to open the
can of worm which allows allocations from the deepest atomic
contexts in general without a really good reason.

B) Michal suggested to have GFP_RT_ATOMIC defined to 0 which would
not require a new GFP bit and bail out when RT is enabled and
the context is atomic.

That of course does not solve the problem vs. lockdep.

Also the idea to make this conditional on !preemptible() does
not work because preemptible() returns always false for
CONFIG_PREEMPT_COUNT=n kernels.

C) I suggested to make GFP == 0 fail unconditionally when the
lockless cache is empty, but that changes the semantics on !RT
kernels for existing users which hand in 0.

D) To solve the lockdep issue of #B Michal suggested to have magic
lockdep annotations which allow !RT kernels to take zone->lock
from the otherwise forbidden contexts because on !RT this lock
nesting could be considered a false positive.

But this would be horrors of some sorts because there might be
locks further down which then need the same treatment or some
general 'yay, I'm excempt from everything' annotation which is
short of disabling lockdep completly.

Even if that could be solved and made correct for both RT and
!RT then this opens the can of worms that this kind of
annotation would show up all over the place within no time for
the completely wrong reasons.

Paul compiled this options list:

> 1. Prohibit invoking allocators from raw atomic context, such
> as when holding a raw spinlock.

Clearly the simplest solution but not Pauls favourite and
unfortunately he has a good reason.

> 2. Adding a GFP_ flag.

The simplest and most consistent solution. If you really need to do
allocations from such contexts then deal with the limitations whether
it's RT or not. Paul has no problem with that and this newfangled
kfree_rcu() is the only user and can live with that restriction.

Michal does not like the restriction for !RT kernels and tries to
avoid the introduction of a new allocation mode.

My argument here is that consistency is the best solution and the
extra GFP mode is explicitly restrictive due to the context which it
is called from. Aside of that this affects exactly ONE use case which
has a really good reason and does not care about the restriction even
on !RT because in that situation kfree_rcu() performance is not the
most urgent problem.

> 3. Reusing existing GFP_ flags/values/whatever to communicate
> the raw-context information that was to be communicated by
> the new GFP_ flag.
>
> 4. Making lockdep forgive acquiring spinlocks while holding
> raw spinlocks, but only in CONFIG_PREEMPT_NONE=y kernels.

These are not seperate of each other as #3 requires #4. The most
horrible solution IMO from a technical POV as it proliferates
inconsistency for no good reaosn.

Aside of that it'd be solving a problem which does not exist simply
because kfree_rcu() does not depend on it and we really don't want to
set precedence and encourage the (ab)use of this in any way.

Having a distinct GFP mode is technically correct, consistent on all
kernel variants and does not affect any exisiting user of the page
allocator aside of the new kfree_rcu().

I hope that the cloudy and rainy day cured most of my heat wave induced
brain damage to the extent that the above is correctly summarizing the
state of affairs. If not, then please yell and I get an icepack.

Thanks,

tglx