[Intel-gfx] [CI] drm/i915: Use a ctor for TYPESAFE_BY_RCU i915_request
Ruhl, Michael J
michael.j.ruhl at intel.com
Fri Nov 22 21:08:42 UTC 2019
>-----Original Message-----
>From: Chris Wilson <chris at chris-wilson.co.uk>
>Sent: Friday, November 22, 2019 3:50 PM
>To: Ruhl, Michael J <michael.j.ruhl at intel.com>; intel-
>gfx at lists.freedesktop.org
>Subject: RE: [Intel-gfx] [CI] drm/i915: Use a ctor for TYPESAFE_BY_RCU
>i915_request
>
>Quoting Ruhl, Michael J (2019-11-22 20:37:59)
>> >-----Original Message-----
>> >From: Intel-gfx <intel-gfx-bounces at lists.freedesktop.org> On Behalf Of
>Chris
>> >Wilson
>> >Sent: Friday, November 22, 2019 2:03 AM
>> >To: intel-gfx at lists.freedesktop.org
>> >Subject: [Intel-gfx] [CI] drm/i915: Use a ctor for TYPESAFE_BY_RCU
>> >i915_request
>> >
>> >As we start peeking into requests for longer and longer, e.g.
>> >incorporating use of spinlocks when only protected by an
>> >rcu_read_lock(), we need to be careful in how we reset the request when
>> >recycling and need to preserve any barriers that may still be in use as
>> >the request is reset for reuse.
>> >
>> >Quoting Linus Torvalds:
>> >
>> >> If there is refcounting going on then why use SLAB_TYPESAFE_BY_RCU?
>> >
>> > .. because the object can be accessed (by RCU) after the refcount has
>> > gone down to zero, and the thing has been released.
>> >
>> > That's the whole and only point of SLAB_TYPESAFE_BY_RCU.
>> >
>> > That flag basically says:
>> >
>> > "I may end up accessing this object *after* it has been free'd,
>> > because there may be RCU lookups in flight"
>> >
>> > This has nothing to do with constructors. It's ok if the object gets
>> > reused as an object of the same type and does *not* get
>> > re-initialized, because we're perfectly fine seeing old stale data.
>> >
>> > What it guarantees is that the slab isn't shared with any other kind
>> > of object, _and_ that the underlying pages are free'd after an RCU
>> > quiescent period (so the pages aren't shared with another kind of
>> > object either during an RCU walk).
>> >
>> > And it doesn't necessarily have to have a constructor, because the
>> > thing that a RCU walk will care about is
>> >
>> > (a) guaranteed to be an object that *has* been on some RCU list (so
>> > it's not a "new" object)
>> >
>> > (b) the RCU walk needs to have logic to verify that it's still the
>> > *same* object and hasn't been re-used as something else.
>> >
>> > In contrast, a SLAB_TYPESAFE_BY_RCU memory gets free'd and re-used
>> > immediately, but because it gets reused as the same kind of object,
>> > the RCU walker can "know" what parts have meaning for re-use, in a way
>> > it couidn't if the re-use was random.
>> >
>> > That said, it *is* subtle, and people should be careful.
>> >
>> >> So the re-use might initialize the fields lazily, not necessarily using a ctor.
>> >
>> > If you have a well-defined refcount, and use "atomic_inc_not_zero()"
>> > to guard the speculative RCU access section, and use
>> > "atomic_dec_and_test()" in the freeing section, then you should be
>> > safe wrt new allocations.
>> >
>> > If you have a completely new allocation that has "random stale
>> > content", you know that it cannot be on the RCU list, so there is no
>> > speculative access that can ever see that random content.
>> >
>> > So the only case you need to worry about is a re-use allocation, and
>> > you know that the refcount will start out as zero even if you don't
>> > have a constructor.
>> >
>> > So you can think of the refcount itself as always having a zero
>> > constructor, *BUT* you need to be careful with ordering.
>> >
>> > In particular, whoever does the allocation needs to then set the
>> > refcount to a non-zero value *after* it has initialized all the other
>> > fields. And in particular, it needs to make sure that it uses the
>> > proper memory ordering to do so.
>> >
>> > NOTE! One thing to be very worried about is that re-initializing
>> > whatever RCU lists means that now the RCU walker may be walking on
>the
>> > wrong list so the walker may do the right thing for this particular
>> > entry, but it may miss walking *other* entries. So then you can get
>> > spurious lookup failures, because the RCU walker never walked all the
>> > way to the end of the right list. That ends up being a much more
>> > subtle bug.
>> >
>> >Signed-off-by: Chris Wilson <chris at chris-wilson.co.uk>
>> >Cc: Tvrtko Ursulin <tvrtko.ursulin at intel.com>
>> >---
>> > drivers/gpu/drm/i915/i915_request.c | 49 ++++++++++++++++++---------
>> > drivers/gpu/drm/i915/i915_scheduler.c | 6 ++++
>> > drivers/gpu/drm/i915/i915_scheduler.h | 1 +
>> > drivers/gpu/drm/i915/i915_sw_fence.c | 8 +++++
>> > drivers/gpu/drm/i915/i915_sw_fence.h | 2 ++
>> > 5 files changed, 50 insertions(+), 16 deletions(-)
>> >
>> >diff --git a/drivers/gpu/drm/i915/i915_request.c
>> >b/drivers/gpu/drm/i915/i915_request.c
>> >index 00011f9533b6..a558f64186fa 100644
>> >--- a/drivers/gpu/drm/i915/i915_request.c
>> >+++ b/drivers/gpu/drm/i915/i915_request.c
>> >@@ -188,7 +188,7 @@ static void free_capture_list(struct i915_request
>> >*request)
>> > {
>> > struct i915_capture_list *capture;
>> >
>> >- capture = request->capture_list;
>> >+ capture = fetch_and_zero(&request->capture_list);
>> > while (capture) {
>> > struct i915_capture_list *next = capture->next;
>> >
>> >@@ -214,7 +214,7 @@ static void remove_from_engine(struct
>i915_request
>> >*rq)
>> > spin_lock(&engine->active.lock);
>> > locked = engine;
>> > }
>> >- list_del(&rq->sched.link);
>> >+ list_del_init(&rq->sched.link);
>> > spin_unlock_irq(&locked->active.lock);
>> > }
>> >
>> >@@ -586,6 +586,21 @@ request_alloc_slow(struct intel_timeline *tl, gfp_t
>> >gfp)
>> > return kmem_cache_alloc(global.slab_requests, gfp);
>> > }
>> >
>> >+static void __i915_request_ctor(void *arg)
>> >+{
>> >+ struct i915_request *rq = arg;
>> >+
>> >+ spin_lock_init(&rq->lock);
>> >+ i915_sched_node_init(&rq->sched);
>> >+ i915_sw_fence_init(&rq->submit, submit_notify);
>> >+ i915_sw_fence_init(&rq->semaphore, semaphore_notify);
>> >+
>> >+ rq->file_priv = NULL;
>> >+ rq->capture_list = NULL;
>> >+
>> >+ INIT_LIST_HEAD(&rq->execute_cb);
>> >+}
>>
>> My understanding of the cache ctor is that it will only get invoked when
>> the cache is expanded. It does not get called on each kmem_cache_alloc().
>>
>> I.e. if you do an _alloc() and there is nothing available in the cache, a new
>> slab is created, and the ctor is called.
>
>Correct.
>
>> Is that the subtly that you are referring to?
>>
>> Is doing this ctor only when new cache slabs are created sufficient?
>
>That is precisely the point. As we only protect some accesses with RCU,
>the request we are peeking at may be reused while we access it. The
>challenge is to keep all such access valid. E.g. consider that the whole
>of i915_gem_busy_ioctl() is performed only protected by the
>rcu_read_lock(), and yet we need the results to be accurate even though
>the requests be reallocated in the middle of it.
>
>The situation that spurred the ctor was taking a spinlock in an rcu path
>and finding it was re-initialised in the middle of that.
To requote:
> > This has nothing to do with constructors. It's ok if the object gets
> > reused as an object of the same type and does *not* get
> > re-initialized, because we're perfectly fine seeing old stale data.
Ok. I think I am following this. You are using the side effect of the ctor
to protect against old data from being re-initialized until after RCU is
done with it.
Thanks for the clarification.
Mike
>-Chris
More information about the Intel-gfx
mailing list