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
66 #include <linux/spinlock.h>
77 #include <linux/mm_types.h>
88 #include <linux/srcu.h>
9+#include <linux/interval_tree.h>
910
1011 struct mmu_notifier_mm;
1112 struct mmu_notifier;
1213 struct mmu_notifier_range;
14+struct mmu_interval_notifier;
1315
1416 /**
1517 * enum mmu_notifier_event - reason for the mmu notifier callback
@@ -32,6 +34,9 @@ struct mmu_notifier_range; · 16 unmodified lines
3234 * access flags). User should soft dirty the page in the end callback to make
3335 * sure that anyone relying on soft dirtyness catch pages that might be written
3436 * 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.
3540 */
3641 enum mmu_notifier_event {
3742 MMU_NOTIFY_UNMAP = 0,
@@ -39,6 +44,7 @@ enum mmu_notifier_event { · 1 unmodified line
3944 MMU_NOTIFY_PROTECTION_VMA,
4045 MMU_NOTIFY_PROTECTION_PAGE,
4146 MMU_NOTIFY_SOFT_DIRTY,
47+ MMU_NOTIFY_RELEASE,
4248 };
4349
4450 #define MMU_NOTIFIER_RANGE_BLOCKABLE (1 << 0)
@@ -222,6 +228,26 @@ struct mmu_notifier { · 177 unmodified lines
222228 unsigned int users;
223229 };
224230
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+ */
237+struct 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+
243+struct 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+
225251 #ifdef CONFIG_MMU_NOTIFIER
226252
227253 #ifdef CONFIG_LOCKDEP
@@ -263,6 +289,81 @@ extern int __mmu_notifier_register(struct mmu_notifier *mn, · 35 unmodified lines
263289 struct mm_struct *mm);
264290 extern void mmu_notifier_unregister(struct mmu_notifier *mn,
265291 struct mm_struct *mm);
292+
293+unsigned long mmu_interval_read_begin(struct mmu_interval_notifier *mni);
294+int 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);
298+int 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);
302+void 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+ */
317+static 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+ */
337+static 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+ */
360+static 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+
266367 extern void __mmu_notifier_mm_destroy(struct mm_struct *mm);
267368 extern void __mmu_notifier_release(struct mm_struct *mm);
268369 extern 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