Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1216571 > unrolled thread
| Started by | Mark Hairgrove <mhairgrove@nvidia.com> |
|---|---|
| First post | 2015-09-01 05:30 +0200 |
| Last post | 2015-09-01 17:00 +0200 |
| Articles | 2 — 2 participants |
Back to article view | Back to linux.kernel
This discussion starts older than the indexed window; earlier articles aren't shown. The article labeled Started by
below is the oldest one visible, not the original post.
Re: [PATCH 02/15] mmu_notifier: keep track of active invalidation ranges v4 Mark Hairgrove <mhairgrove@nvidia.com> - 2015-09-01 05:30 +0200
Re: [PATCH 02/15] mmu_notifier: keep track of active invalidation ranges v4 Jerome Glisse <jglisse@redhat.com> - 2015-09-01 17:00 +0200
| From | Mark Hairgrove <mhairgrove@nvidia.com> |
|---|---|
| Date | 2015-09-01 05:30 +0200 |
| Subject | Re: [PATCH 02/15] mmu_notifier: keep track of active invalidation ranges v4 |
| Message-ID | <q3KqK-2NR-7@gated-at.bofh.it> |
[Multipart message — attachments visible in raw view] — view raw
On Thu, 13 Aug 2015, Jérôme Glisse wrote:
> The invalidate_range_start() and invalidate_range_end() can be
> considered as forming an "atomic" section for the cpu page table
> update point of view. Between this two function the cpu page
> table content is unreliable for the address range being
> invalidated.
>
> This patch use a structure define at all place doing range
> invalidation. This structure is added to a list for the duration
> of the update ie added with invalid_range_start() and removed
> with invalidate_range_end().
>
> Helpers allow querying if a range is valid and wait for it if
> necessary.
>
> For proper synchronization, user must block any new range
> invalidation from inside there invalidate_range_start() callback.
s/there/their/
> Otherwise there is no garanty that a new range invalidation will
s/garanty/guarantee/
> not be added after the call to the helper function to query for
> existing range.
>
> [...]
>
> +/* mmu_notifier_range_is_valid_locked() - test if range overlap with active
s/overlap/overlaps/
> + * invalidation.
> + *
> + * @mm: The mm struct.
> + * @start: Start address of the range (inclusive).
> + * @end: End address of the range (exclusive).
> + * Returns: false if overlap with an active invalidation, true otherwise.
> + *
> + * This function test whether any active invalidated range conflict with a
s/test/tests/
s/invalidated/invalidation/
s/conflict/conflicts/
> + * given range ([start, end[), active invalidation are added to a list inside
end[ -> end]
s/invalidation/invalidations/
> + * __mmu_notifier_invalidate_range_start() and removed from that list inside
> + * __mmu_notifier_invalidate_range_end().
> + */
> +static bool mmu_notifier_range_is_valid_locked(struct mm_struct *mm,
> + unsigned long start,
> + unsigned long end)
> +{
> + struct mmu_notifier_range *range;
> +
> + list_for_each_entry(range, &mm->mmu_notifier_mm->ranges, list) {
> + if (range->end > start && range->start < end)
> + return false;
> + }
> + return true;
> +}
> +
> +/* mmu_notifier_range_is_valid() - test if range overlap with active
s/overlap/overlaps/
> + * invalidation.
> + *
> + * @mm: The mm struct.
> + * @start: Start address of the range (inclusive).
> + * @end: End address of the range (exclusive).
> + *
> + * This function wait for any active range invalidation that conflict with the
> + * given range, to end. See mmu_notifier_range_wait_valid() on how to use this
> + * function properly.
Bad copy/paste from range_wait_valid? mmu_notifier_range_is_valid just
queries the state, it doesn't wait.
> + */
> +bool mmu_notifier_range_is_valid(struct mm_struct *mm,
> + unsigned long start,
> + unsigned long end)
> +{
> + bool valid;
> +
> + spin_lock(&mm->mmu_notifier_mm->lock);
> + valid = mmu_notifier_range_is_valid_locked(mm, start, end);
> + spin_unlock(&mm->mmu_notifier_mm->lock);
> + return valid;
> +}
> +EXPORT_SYMBOL_GPL(mmu_notifier_range_is_valid);
> +
> +/* mmu_notifier_range_wait_valid() - wait for a range to have no conflict with
> + * active invalidation.
> + *
> + * @mm: The mm struct.
> + * @start: Start address of the range (inclusive).
> + * @end: End address of the range (exclusive).
> + *
> + * This function wait for any active range invalidation that conflict with the
> + * given range, to end.
> + *
> + * Note by the time this function return a new range invalidation that conflict
> + * might have started. So you need to atomically block new range and query
> + * again if range is still valid with mmu_notifier_range_is_valid(). So call
> + * sequence should be :
> + *
> + * again:
> + * mmu_notifier_range_wait_valid()
> + * // block new invalidation using that lock inside your range_start callback
> + * lock_block_new_invalidation()
> + * if (!mmu_notifier_range_is_valid())
> + * goto again;
> + * unlock()
I think this example sequence can deadlock so I wouldn't want to encourage
its use. New invalidation regions are added to the list before the
range_start callback is invoked.
Thread A Thread B
----------------- -----------------
mmu_notifier_range_wait_valid
// returns
__mmu_notifier_invalidate_range_start
list_add_tail
lock_block_new_invalidation
->invalidate_range_start
// invalidation blocked in callback
mmu_notifier_range_is_valid // fails
goto again
mmu_notifier_range_wait_valid // deadlock
mmu_notifier_range_wait_valid can't finish until thread B's callback
returns, but thread B's callback can't return because it's blocked.
I see that HMM in later patches takes the approach of not holding the lock
when mmu_notifier_range_is_valid returns false. Instead of stalling new
invalidations it returns -EAGAIN to the caller. While that resolves the
deadlock, it won't prevent the faulting thread from being starved in the
pathological case.
Is it out of the question to build a lock into the mmu notifier API
directly? It's a little worrisome to me that the complexity for this
locking is pushed into the callbacks rather than handled in the core.
Something like this:
mmu_notifier_range_lock(start, end)
mmu_notifier_range_unlock(start, end)
If that's not feasible and we have to stick with the current approach,
then I suggest changing the "valid" name. "valid" doesn't have a clear
meaning at first glance because the reader doesn't know what would make a
range "valid." How about "active" instead? Then the names would look
something like this, assuming the polarity matches their current versions:
mmu_notifier_range_inactive_locked
mmu_notifier_range_inactive
mmu_notifier_range_wait_active
> + */
> +void mmu_notifier_range_wait_valid(struct mm_struct *mm,
> + unsigned long start,
> + unsigned long end)
> +{
> + spin_lock(&mm->mmu_notifier_mm->lock);
> + while (!mmu_notifier_range_is_valid_locked(mm, start, end)) {
> + int nranges = mm->mmu_notifier_mm->nranges;
> +
> + spin_unlock(&mm->mmu_notifier_mm->lock);
> + wait_event(mm->mmu_notifier_mm->wait_queue,
> + nranges != mm->mmu_notifier_mm->nranges);
> + spin_lock(&mm->mmu_notifier_mm->lock);
> + }
> + spin_unlock(&mm->mmu_notifier_mm->lock);
> +}
> +EXPORT_SYMBOL_GPL(mmu_notifier_range_wait_valid);
> +
[toc] | [next] | [standalone]
| From | Jerome Glisse <jglisse@redhat.com> |
|---|---|
| Date | 2015-09-01 17:00 +0200 |
| Message-ID | <q3Vct-1fz-1@gated-at.bofh.it> |
| In reply to | #1216571 |
On Mon, Aug 31, 2015 at 08:27:17PM -0700, Mark Hairgrove wrote: > On Thu, 13 Aug 2015, Jérôme Glisse wrote: [...] Will fix syntax. [...] > > +/* mmu_notifier_range_wait_valid() - wait for a range to have no conflict with > > + * active invalidation. > > + * > > + * @mm: The mm struct. > > + * @start: Start address of the range (inclusive). > > + * @end: End address of the range (exclusive). > > + * > > + * This function wait for any active range invalidation that conflict with the > > + * given range, to end. > > + * > > + * Note by the time this function return a new range invalidation that conflict > > + * might have started. So you need to atomically block new range and query > > + * again if range is still valid with mmu_notifier_range_is_valid(). So call > > + * sequence should be : > > + * > > + * again: > > + * mmu_notifier_range_wait_valid() > > + * // block new invalidation using that lock inside your range_start callback > > + * lock_block_new_invalidation() > > + * if (!mmu_notifier_range_is_valid()) > > + * goto again; > > + * unlock() > > I think this example sequence can deadlock so I wouldn't want to encourage > its use. New invalidation regions are added to the list before the > range_start callback is invoked. > > Thread A Thread B > ----------------- ----------------- > mmu_notifier_range_wait_valid > // returns > __mmu_notifier_invalidate_range_start > list_add_tail > lock_block_new_invalidation > ->invalidate_range_start > // invalidation blocked in callback > mmu_notifier_range_is_valid // fails > goto again > mmu_notifier_range_wait_valid // deadlock > > mmu_notifier_range_wait_valid can't finish until thread B's callback > returns, but thread B's callback can't return because it's blocked. > > I see that HMM in later patches takes the approach of not holding the lock > when mmu_notifier_range_is_valid returns false. Instead of stalling new > invalidations it returns -EAGAIN to the caller. While that resolves the > deadlock, it won't prevent the faulting thread from being starved in the > pathological case. The comment here is not clear, what HMM does is what is intended. If mmu_notifier_range_is_valid() return false then you drop lock and try again. I am not sure we should care about the starve case as it can not happen, it would mean that something keeps invalidating over and over the same range of address space of a process. I do not see how such thing would happen. > > Is it out of the question to build a lock into the mmu notifier API > directly? It's a little worrisome to me that the complexity for this > locking is pushed into the callbacks rather than handled in the core. > Something like this: > > mmu_notifier_range_lock(start, end) > mmu_notifier_range_unlock(start, end) If a range is about to be invalidated it is better to avoid faulting in memory in the device as that same memory is about to be invalidated. This is why i always have invalidation take precedence over device fault. > If that's not feasible and we have to stick with the current approach, > then I suggest changing the "valid" name. "valid" doesn't have a clear > meaning at first glance because the reader doesn't know what would make a > range "valid." How about "active" instead? Then the names would look > something like this, assuming the polarity matches their current versions: > > mmu_notifier_range_inactive_locked > mmu_notifier_range_inactive > mmu_notifier_range_wait_active Those names are better i will update the patch accordingly. Thanks for the review, Jérôme -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web