History

mm/mmu_notifier: add an interval tree notifier

Of the 13 users of mmu_notifiers, 8 of them use only invalidate_range_start/end() and immediately intersect the mmu_notifier_range with some kind of internal list of VAs. 4 use an interval tree (i915_gem, radeon_mn, umem_odp, hfi1). 4 use a linked list of some kind (scif_dma, vhost, gntdev, hmm) And the remaining 5 either don't use invalidate_range_start() or do some special thing with it. It turns out that building a correct scheme with an interval tree is pretty complicated, particularly if the use case is synchronizing against another thread doing get_user_pages(). Many of these implementations have various subtle and difficult to fix races. This approach puts the interval tree as common code at the top of the mmu notifier call tree and implements a shareable locking scheme. It includes: - An interval tree tracking VA ranges, with per-range callbacks - A read/write locking scheme for the interval tree that avoids sleeping in the notifier path (for OOM killer) - A sequence counter based collision-retry locking scheme to tell device page fault that a VA range is being concurrently invalidated. This is based on various ideas: - hmm accumulates invalidated VA ranges and releases them when all invalidates are done, via active_invalidate_ranges count. This approach avoids having to intersect the interval tree twice (as umem_odp does) at the potential cost of a longer device page fault. - kvm/umem_odp use a sequence counter to drive the collision retry, via invalidate_seq - a deferred work todo list on unlock scheme like RTNL, via deferred_list. This makes adding/removing interval tree members more deterministic - seqlock, except this version makes the seqlock idea multi-holder on the write side by protecting it with active_invalidate_ranges and a spinlock To minimize MM overhead when only the interval tree is being used, the entire SRCU and hlist overheads are dropped using some simple branches. Similarly the interval tree overhead is dropped when in hlist mode. The overhead from the mandatory spinlock is broadly the same as most of existing users which already had a lock (or two) of some sort on the invalidation path.

Jason Gunthorpe committed 7 years ago

Commit 99cb2521 parent 56f434f3 files · +620 −26Browse files

Showing the changes to include/linux (1 of 3 files). Show all

Unified · Split

include/linux/mmu_notifier.h+101−0
@@ -6,10 +6,12 @@ · 5 unmodified lines
6#include <linux/spinlock.h>6#include <linux/spinlock.h>
7#include <linux/mm_types.h>7#include <linux/mm_types.h>
8#include <linux/srcu.h>8#include <linux/srcu.h>
9#include <linux/interval_tree.h>
9 10
10struct mmu_notifier_mm;11struct mmu_notifier_mm;
11struct mmu_notifier;12struct mmu_notifier;
12struct mmu_notifier_range;13struct mmu_notifier_range;
14struct mmu_interval_notifier;
13 15
14/**16/**
15 * enum mmu_notifier_event - reason for the mmu notifier callback17 * enum mmu_notifier_event - reason for the mmu notifier callback
@@ -32,6 +34,9 @@ struct mmu_notifier_range; · 16 unmodified lines
32 * access flags). User should soft dirty the page in the end callback to make34 * access flags). User should soft dirty the page in the end callback to make
33 * sure that anyone relying on soft dirtyness catch pages that might be written35 * sure that anyone relying on soft dirtyness catch pages that might be written
34 * through non CPU mappings.36 * through non CPU mappings.
37 *
38 * @MMU_NOTIFY_RELEASE: used during mmu_interval_notifier invalidate to signal
39 * that the mm refcount is zero and the range is no longer accessible.
35 */40 */
36enum mmu_notifier_event {41enum mmu_notifier_event {
37 MMU_NOTIFY_UNMAP = 0,42 MMU_NOTIFY_UNMAP = 0,
@@ -39,6 +44,7 @@ enum mmu_notifier_event { · 1 unmodified line
39 MMU_NOTIFY_PROTECTION_VMA,44 MMU_NOTIFY_PROTECTION_VMA,
40 MMU_NOTIFY_PROTECTION_PAGE,45 MMU_NOTIFY_PROTECTION_PAGE,
41 MMU_NOTIFY_SOFT_DIRTY,46 MMU_NOTIFY_SOFT_DIRTY,
47 MMU_NOTIFY_RELEASE,
42};48};
43 49
44#define MMU_NOTIFIER_RANGE_BLOCKABLE (1 << 0)50#define MMU_NOTIFIER_RANGE_BLOCKABLE (1 << 0)
@@ -222,6 +228,26 @@ struct mmu_notifier { · 177 unmodified lines
222 unsigned int users;228 unsigned int users;
223};229};
224 230
231/**
232 * struct mmu_interval_notifier_ops
233 * @invalidate: Upon return the caller must stop using any SPTEs within this
234 * range. This function can sleep. Return false only if sleeping
235 * was required but mmu_notifier_range_blockable(range) is false.
236 */
237struct mmu_interval_notifier_ops {
238 bool (*invalidate)(struct mmu_interval_notifier *mni,
239 const struct mmu_notifier_range *range,
240 unsigned long cur_seq);
241};
242
243struct mmu_interval_notifier {
244 struct interval_tree_node interval_tree;
245 const struct mmu_interval_notifier_ops *ops;
246 struct mm_struct *mm;
247 struct hlist_node deferred_item;
248 unsigned long invalidate_seq;
249};
250
225#ifdef CONFIG_MMU_NOTIFIER251#ifdef CONFIG_MMU_NOTIFIER
226 252
227#ifdef CONFIG_LOCKDEP253#ifdef CONFIG_LOCKDEP
@@ -263,6 +289,81 @@ extern int __mmu_notifier_register(struct mmu_notifier *mn, · 35 unmodified lines
263 struct mm_struct *mm);289 struct mm_struct *mm);
264extern void mmu_notifier_unregister(struct mmu_notifier *mn,290extern void mmu_notifier_unregister(struct mmu_notifier *mn,
265 struct mm_struct *mm);291 struct mm_struct *mm);
292
293unsigned long mmu_interval_read_begin(struct mmu_interval_notifier *mni);
294int mmu_interval_notifier_insert(struct mmu_interval_notifier *mni,
295 struct mm_struct *mm, unsigned long start,
296 unsigned long length,
297 const struct mmu_interval_notifier_ops *ops);
298int mmu_interval_notifier_insert_locked(
299 struct mmu_interval_notifier *mni, struct mm_struct *mm,
300 unsigned long start, unsigned long length,
301 const struct mmu_interval_notifier_ops *ops);
302void mmu_interval_notifier_remove(struct mmu_interval_notifier *mni);
303
304/**
305 * mmu_interval_set_seq - Save the invalidation sequence
306 * @mni - The mni passed to invalidate
307 * @cur_seq - The cur_seq passed to the invalidate() callback
308 *
309 * This must be called unconditionally from the invalidate callback of a
310 * struct mmu_interval_notifier_ops under the same lock that is used to call
311 * mmu_interval_read_retry(). It updates the sequence number for later use by
312 * mmu_interval_read_retry(). The provided cur_seq will always be odd.
313 *
314 * If the caller does not call mmu_interval_read_begin() or
315 * mmu_interval_read_retry() then this call is not required.
316 */
317static inline void mmu_interval_set_seq(struct mmu_interval_notifier *mni,
318 unsigned long cur_seq)
319{
320 WRITE_ONCE(mni->invalidate_seq, cur_seq);
321}
322
323/**
324 * mmu_interval_read_retry - End a read side critical section against a VA range
325 * mni: The range
326 * seq: The return of the paired mmu_interval_read_begin()
327 *
328 * This MUST be called under a user provided lock that is also held
329 * unconditionally by op->invalidate() when it calls mmu_interval_set_seq().
330 *
331 * Each call should be paired with a single mmu_interval_read_begin() and
332 * should be used to conclude the read side.
333 *
334 * Returns true if an invalidation collided with this critical section, and
335 * the caller should retry.
336 */
337static inline bool mmu_interval_read_retry(struct mmu_interval_notifier *mni,
338 unsigned long seq)
339{
340 return mni->invalidate_seq != seq;
341}
342
343/**
344 * mmu_interval_check_retry - Test if a collision has occurred
345 * mni: The range
346 * seq: The return of the matching mmu_interval_read_begin()
347 *
348 * This can be used in the critical section between mmu_interval_read_begin()
349 * and mmu_interval_read_retry(). A return of true indicates an invalidation
350 * has collided with this critical region and a future
351 * mmu_interval_read_retry() will return true.
352 *
353 * False is not reliable and only suggests a collision may not have
354 * occured. It can be called many times and does not have to hold the user
355 * provided lock.
356 *
357 * This call can be used as part of loops and other expensive operations to
358 * expedite a retry.
359 */
360static inline bool mmu_interval_check_retry(struct mmu_interval_notifier *mni,
361 unsigned long seq)
362{
363 /* Pairs with the WRITE_ONCE in mmu_interval_set_seq() */
364 return READ_ONCE(mni->invalidate_seq) != seq;
365}
366
266extern void __mmu_notifier_mm_destroy(struct mm_struct *mm);367extern void __mmu_notifier_mm_destroy(struct mm_struct *mm);
267extern void __mmu_notifier_release(struct mm_struct *mm);368extern void __mmu_notifier_release(struct mm_struct *mm);
268extern int __mmu_notifier_clear_flush_young(struct mm_struct *mm,369extern int __mmu_notifier_clear_flush_young(struct mm_struct *mm,

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t