lockless pagecache for 2.6.18-mm3
Subsystems (8)
cover lockless pagecache for 2.6.18-mm3
Updated lockless patchset against 2.6.18-mm3. Boots and survives basic stress testing so far.
I have decided not to implement the tweak Hugh suggested yet, for reasons outlined in the patch description. I still think it is a promising approach, I just haven't been able to convince myself of correctness in some paths yet.
Nick
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH 1/4 radix-tree: use indirect bit
Rather than sign direct radix-tree pointers with a special bit, sign the indirect one that hangs off the root. This means that, given a lookup_slot operation, the invalid result will be differentiated from the valid (previously, valid results could have the bit either set or clear).
This does not affect slot lookups which occur under lock -- they can never return an invalid result. Is needed in future for lockless pagecache.
include/linux/radix-tree.h +22 -18
@@ -27,28 +27,31 @@#include <linux/rcupdate.h> /* -* A direct pointer (root->rnode pointing directly to a data item, -* rather than another radix_tree_node) is signalled by the low bit -* set in the root->rnode pointer. -* -* In this case root->height is also NULL, but the direct pointer tests are -* needed for RCU lookups when root->height is unreliable. +* An indirect pointer (root->rnode pointing to a radix_tree_node, rather +* than a data item) is signalled by the low bit set in the root->rnode +* pointer. +* +* In this case root->height is > 0, but the indirect pointer tests are +* needed for RCU lookups (because root->height is unreliable). The only +* time callers need worry about this is when doing a lookup_slot under +* RCU. */ -#define RADIX_TREE_DIRECT_PTR 1 +#define RADIX_TREE_INDIRECT_PTR 1 +#define RADIX_TREE_RETRY ((void *)-1UL) -static inline void *radix_tree_ptr_to_direct (void *ptr) +static inline void *radix_tree_ptr_to_indirect (void *ptr) { -return (void *)((unsigned long )ptr |RADIX_TREE_DIRECT_PTR ); +return (void *)((unsigned long )ptr |RADIX_TREE_INDIRECT_PTR ); } -static inline void *radix_tree_direct_to_ptr (void *ptr) +static inline void *radix_tree_indirect_to_ptr (void *ptr) { -return (void *)((unsigned long )ptr & ~RADIX_TREE_DIRECT_PTR ); +return (void *)((unsigned long )ptr & ~RADIX_TREE_INDIRECT_PTR ); } -static inline int radix_tree_is_direct_ptr (void *ptr) +static inline int radix_tree_is_indirect_ptr (void *ptr) { -return (int )((unsigned long )ptr &RADIX_TREE_DIRECT_PTR ); +return (int )((unsigned long )ptr &RADIX_TREE_INDIRECT_PTR ); }/*** radix-tree API starts here ***/
@@ -131,7 +134,10 @@ do { \*/ static inline void *radix_tree_deref_slot (void **pslot) { -return radix_tree_direct_to_ptr (*pslot); +void *ret = *pslot; +if (unlikely (radix_tree_is_indirect_ptr (ret))) + ret =RADIX_TREE_RETRY ; +return ret; }/** * radix_tree_replace_slot - replace item in a slot
@@ -143,10 +149,8 @@ static inline void *radix_tree_deref_slo*/ static inline void radix_tree_replace_slot (void **pslot,void *item) { -BUG_ON (radix_tree_is_direct_ptr (item)); -rcu_assign_pointer (*pslot, - (void *)((unsigned long )item | - ((unsigned long )*pslot &RADIX_TREE_DIRECT_PTR ))); +BUG_ON (radix_tree_is_indirect_ptr (item)); +rcu_assign_pointer (*pslot, item); }int radix_tree_insert (struct radix_tree_root *,unsigned long ,void *);
lib/radix-tree.c +43 -26
200 lines not on this page · raw message
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH 2/4 radix-tree: gang_lookup_slot
In reply to Nick PigginIntroduce a gang_lookup_slot function which is used by lockless pagecache.
include/linux/radix-tree.h +6 -1
@@ -100,12 +100,14 @@ do { \* * The notable exceptions to this rule are the following functions: * radix_tree_lookup +* radix_tree_lookup_slot * radix_tree_tag_get * radix_tree_gang_lookup +* radix_tree_gang_lookup_slot * radix_tree_gang_lookup_tag * radix_tree_tagged * -* The first 4 functions are able to be called locklessly, using RCU. The +* The first 6 functions are able to be called locklessly, using RCU. The * caller must ensure calls to these functions are made within rcu_read_lock() * regions. Other readers (lock-free or otherwise) and modifications may be * running concurrently.
@@ -160,6 +162,9 @@ void *radix_tree_delete(struct radix_treunsigned int radix_tree_gang_lookup (struct radix_tree_root *root,void **results,unsigned long first_index,unsigned int max_items); +unsigned int +radix_tree_gang_lookup_slot (struct radix_tree_root *root,void ***results, +unsigned long first_index,unsigned int max_items);unsigned long radix_tree_scan_hole_backward (struct radix_tree_root *root,unsigned long index,unsigned long max_scan); unsigned long radix_tree_scan_hole(struct radix_tree_root *root,
lib/radix-tree.c +76 -10
128 lines not on this page · raw message
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH 3/4 mm: speculative get page
In reply to Nick PigginIf we can be sure that elevating the page_count on a pagecache page will pin it, we can speculatively run this operation, and subsequently check to see if we hit the right page rather than relying on holding a lock or otherwise pinning a reference to the page.
This can be done if get_page/put_page behaves consistently throughout the whole tree (ie. if we "get" the page after it has been used for something else, we must be able to free it with a put_page).
Actually, there is a period where the count behaves differently:
when the page is free or if it is a constituent page of a compound page. We need an atomic_inc_not_zero operation to ensure we don't try to grab the page in either case.
This patch introduces the core locking protocol to the pagecache (ie. adds page_cache_get_speculative, and tweaks some update-side code to make it work).
[Hugh notices that PG_nonewrefs might be dispensed with entirely
if current SetPageNoNewRefs instead atomically save the page count and temporarily set it to zero.
This is a nice idea, and simplifies find_get_page very much, but cannot be applied to all current SetPageNoNewRefs sites. Need to verify that add_to_page_cache and add_to_swap_cache can cope without it or make do some other way.
In the meantime, this version is a slightly more mechanical replacement.]
include/linux/page-flags.h +8 -2
@@ -91,8 +91,9 @@#define PG_nosave_free 18/* Used for system suspend/resume */ #define PG_buddy 19/* Page is free, on buddy lists */ -#define PG_readahead 20/* Reminder to do readahead */ - +#define PG_nonewrefs 20/* Block concurrent pagecache lookups +* while testing refcount */ +#define PG_readahead 21/* Reminder to do readahead */ #if (BITS_PER_LONG >32 )/*
@@ -253,6 +254,11 @@ static inline void SetPageUptodate(struc#define SetPageReadahead (page) set_bit(PG_readahead, &(page)->flags)#define TestClearPageReadahead (page) test_and_clear_bit(PG_readahead, &(page)->flags) +#define PageNoNewRefs (page) test_bit(PG_nonewrefs, &(page)->flags) +#define SetPageNoNewRefs (page) set_bit(PG_nonewrefs, &(page)->flags) +#define ClearPageNoNewRefs (page) clear_bit(PG_nonewrefs, &(page)->flags) +#define __ClearPageNoNewRefs (page) __clear_bit(PG_nonewrefs, &(page)->flags) +struct page ;/* forward declaration */ int test_clear_page_dirty (struct page *page);
include/linux/pagemap.h +105 -0
117 lines not on this page · raw message
mm/vmscan.c +9 -3
@@ -383,6 +383,8 @@ int remove_mapping(struct address_spaceBUG_ON (!PageLocked (page));BUG_ON (mapping !=page_mapping (page)); +SetPageNoNewRefs (page); +smp_wmb ();write_lock_irq (&mapping->tree_lock);/* * The non racy check for a busy page.
@@ -421,17 +423,21 @@ int remove_mapping(struct address_space__delete_from_swap_cache (page);write_unlock_irq (&mapping->tree_lock);swap_free (swap); -__put_page (page);/* The pagecache ref */ -return 1 ; + goto free_it; }__remove_from_page_cache (page);write_unlock_irq (&mapping->tree_lock); -__put_page (page); + +free_it: +smp_wmb (); +__ClearPageNoNewRefs (page); +__put_page (page);/* The pagecache ref */ return 1 ; cannot_free:write_unlock_irq (&mapping->tree_lock); +ClearPageNoNewRefs (page);return 0 ; }
mm/filemap.c +4 -0
@@ -449,6 +449,8 @@ int add_to_page_cache(struct page *page,int error =radix_tree_preload (gfp_mask & ~__GFP_HIGHMEM);if (error ==0 ) { +SetPageNoNewRefs (page); +smp_wmb ();write_lock_irq (&mapping->tree_lock); error =radix_tree_insert (&mapping->page_tree, offset, page);if (!error) {
@@ -460,6 +462,8 @@ int add_to_page_cache(struct page *page,__inc_zone_page_state (page,NR_FILE_PAGES ); }write_unlock_irq (&mapping->tree_lock); +smp_wmb (); +ClearPageNoNewRefs (page);radix_tree_preload_end (); }return error;
mm/swap_state.c +4 -0
@@ -79,6 +79,8 @@ static int __add_to_swap_cache(struct paBUG_ON (PagePrivate (page)); error =radix_tree_preload (gfp_mask);if (!error) { +SetPageNoNewRefs (page); +smp_wmb ();write_lock_irq (&swapper_space.tree_lock); error =radix_tree_insert (&swapper_space.page_tree, entry.val, page);
@@ -92,6 +94,8 @@ static int __add_to_swap_cache(struct pa__inc_zone_page_state (page,NR_FILE_PAGES ); }write_unlock_irq (&swapper_space.tree_lock); +smp_wmb (); +ClearPageNoNewRefs (page);radix_tree_preload_end (); }return error;
mm/migrate.c +7 -2
@@ -303,6 +303,8 @@ static int migrate_page_move_mapping(strreturn 0 ; } +SetPageNoNewRefs (page); +smp_wmb ();write_lock_irq (&mapping->tree_lock); pslot = radix_tree_lookup_slot(&mapping->page_tree,
@@ -311,6 +313,7 @@ static int migrate_page_move_mapping(strif (page_count (page) !=2 + !!PagePrivate (page) || (struct page *)radix_tree_deref_slot (pslot) != page) {write_unlock_irq (&mapping->tree_lock); +ClearPageNoNewRefs (page);return -EAGAIN ; }
@@ -326,6 +329,10 @@ static int migrate_page_move_mapping(str#endif radix_tree_replace_slot (pslot, newpage); + page->mapping =NULL ; +write_unlock_irq (&mapping->tree_lock); +smp_wmb (); +ClearPageNoNewRefs (page);/* * Drop cache reference from old page.
@@ -333,8 +340,6 @@ static int migrate_page_move_mapping(str*/ __put_page (page); -write_unlock_irq (&mapping->tree_lock); -return 0 ; }
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>
PATCH 4/4 mm: lockless pagecache lookups
In reply to Nick PigginCombine page_cache_get_speculative with lockless radix tree lookups to introduce lockless page cache lookups (ie. no mapping->tree_lock on the read-side).
The only atomicity changes this introduces is that the gang pagecache lookup functions now behave as if they are implemented with multiple find_get_page calls, rather than operating on a snapshot of the pages. In practice, this atomicity guarantee is not used anyway, and it is difficult to see how it could be. Gang pagecache lookups are designed to replace individual lookups, so these semantics are natural.
mm/filemap.c +101 -47
@@ -577,22 +577,15 @@ EXPORT_SYMBOL(unlock_page);/* * Probing page existence. -*/ -int __probe_page (struct address_space *mapping,pgoff_t offset) -{ -return !!radix_tree_lookup (&mapping->page_tree, offset); -} - -/* -* Here we just do not bother to grab the page, it's meaningless anyway. +* We do not bother to take a ref to the page, it's meaningless anyway. */ int probe_page (struct address_space *mapping,pgoff_t offset) {int exists; -read_lock_irq (&mapping->tree_lock); - exists =__probe_page (mapping, offset); -read_unlock_irq (&mapping->tree_lock); +rcu_read_lock (); + exists = !!radix_tree_lookup (&mapping->page_tree, offset); +rcu_read_unlock ();return exists; }
@@ -666,15 +659,31 @@ void fastcall __lock_page_nosync(struct* Is there a pagecache struct page at the given (mapping, offset) tuple? * If yes, increment its refcount and return it; if no, return NULL. */ -struct page *find_get_page (struct address_space *mapping,unsigned long offset) +struct page *find_get_page (struct address_space *mapping,unsigned long offset) { +void **pagep;struct page *page; -read_lock_irq (&mapping->tree_lock); - page =radix_tree_lookup (&mapping->page_tree, offset); -if (page) -page_cache_get (page); -read_unlock_irq (&mapping->tree_lock); +rcu_read_lock (); +repeat: + page =NULL ; + pagep =radix_tree_lookup_slot (&mapping->page_tree, offset); +if (pagep) { + page =radix_tree_deref_slot (pagep); +if (unlikely (!page || page ==RADIX_TREE_RETRY )) + goto repeat; + +if (!page_cache_get_speculative (page)) + goto repeat; + +/* Has the page moved? */ +if (unlikely (page != *pagep)) { +page_cache_release (page); + goto repeat; + } + } +rcu_read_unlock (); +return page; }EXPORT_SYMBOL (find_get_page);
@@ -714,26 +723,19 @@ struct page *find_lock_page(struct addre {struct page *page; -read_lock_irq (&mapping->tree_lock); repeat: - page =radix_tree_lookup (&mapping->page_tree, offset); + page =find_get_page (mapping, offset);if (page) { -page_cache_get (page); -if (TestSetPageLocked (page)) { -read_unlock_irq (&mapping->tree_lock); -__lock_page (page); -read_lock_irq (&mapping->tree_lock); - -/* Has the page been truncated while we slept? */ -if (unlikely (page->mapping != mapping || - page->index != offset)) { -unlock_page (page); -page_cache_release (page); - goto repeat; - } +lock_page (page); +/* Has the page been truncated? */ +if (unlikely (page->mapping != mapping + || page->index != offset)) { +unlock_page (page); +page_cache_release (page); + goto repeat; } } -read_unlock_irq (&mapping->tree_lock); +return page; }EXPORT_SYMBOL (find_lock_page);
@@ -803,13 +805,39 @@ unsigned find_get_pages(struct address_s {unsigned int i;unsigned int ret; +unsigned int nr_found; -read_lock_irq (&mapping->tree_lock); - ret =radix_tree_gang_lookup (&mapping->page_tree, - (void **)pages, start, nr_pages); -for (i =0 ; i < ret; i++) -page_cache_get (pages[i]); -read_unlock_irq (&mapping->tree_lock); +rcu_read_lock (); +restart: + nr_found =radix_tree_gang_lookup_slot (&mapping->page_tree, + (void ***)pages, start, nr_pages); + ret =0 ; +for (i =0 ; i < nr_found; i++) { +struct page *page; +repeat: + page =radix_tree_deref_slot ((void **)pages[i]); +if (unlikely (!page)) +continue ; +/* +* this can only trigger if nr_found == 1, making livelock +* a non issue. +*/ +if (unlikely (page ==RADIX_TREE_RETRY )) + goto restart; + +if (!page_cache_get_speculative (page)) + goto repeat; + +/* Has the page moved? */ +if (unlikely (page != *((void **)pages[i]))) { +page_cache_release (page); + goto repeat; + } + + pages[ret] = page; + ret++; + } +rcu_read_unlock ();return ret; }EXPORT_SYMBOL (find_get_pages);
@@ -831,19 +859,44 @@ unsigned find_get_pages_contig(struct ad {unsigned int i;unsigned int ret; +unsigned int nr_found; -read_lock_irq (&mapping->tree_lock); - ret =radix_tree_gang_lookup (&mapping->page_tree, - (void **)pages, index, nr_pages); -for (i =0 ; i < ret; i++) { -if (pages[i]->mapping ==NULL || pages[i]->index != index) +rcu_read_lock (); +restart: + nr_found =radix_tree_gang_lookup_slot (&mapping->page_tree, + (void ***)pages, index, nr_pages); + ret =0 ; +for (i =0 ; i < nr_found; i++) { +struct page *page; +repeat: + page =radix_tree_deref_slot ((void **)pages[i]); +if (unlikely (!page)) +continue ; +/* +* this can only trigger if nr_found == 1, making livelock +* a non issue. +*/ +if (unlikely (page ==RADIX_TREE_RETRY )) + goto restart; + +if (page->mapping ==NULL || page->index != index)break ; -page_cache_get (pages[i]); +if (!page_cache_get_speculative (page)) + goto repeat; + +/* Has the page moved? */ +if (unlikely (page != *((void **)pages[i]))) { +page_cache_release (page); + goto repeat; + } + + pages[ret] = page; + ret++; index++; } -read_unlock_irq (&mapping->tree_lock); -return i; +rcu_read_unlock (); +return ret; }EXPORT_SYMBOL (find_get_pages_tag);
@@ -865,6 +918,7 @@ unsigned find_get_pages_tag(struct addreunsigned int ret;read_lock_irq (&mapping->tree_lock); +/* TODO: implement lookup_tag_slot and make this lockless */ ret =radix_tree_gang_lookup_tag (&mapping->page_tree, (void **)pages, *index, nr_pages, tag);for (i =0 ; i < ret; i++)
mm/readahead.c +11 -16
@@ -429,21 +429,20 @@ __do_page_cache_readahead(struct address/* * Preallocate as many pages as we will need. */ -read_lock_irq (&mapping->tree_lock);for (page_idx =0 ; page_idx < nr_to_read; page_idx++) {pgoff_t page_offset = offset + page_idx;if (page_offset > end_index)break ; +rcu_read_lock (); page =radix_tree_lookup (&mapping->page_tree, page_offset); +rcu_read_unlock ();if (page)continue ; -read_unlock_irq (&mapping->tree_lock); page =page_cache_alloc_cold (mapping);cond_resched (); -read_lock_irq (&mapping->tree_lock);if (!page)break ; page->index = page_offset;
@@ -452,7 +451,6 @@ __do_page_cache_readahead(struct addressSetPageReadahead (page); ret++; } -read_unlock_irq (&mapping->tree_lock);/* * Now start the IO. We ignore I/O errors - if the page is not
@@ -1358,14 +1356,14 @@ static pgoff_t find_segtail(struct addreread_lock_irq (&mapping->tree_lock); ra_index =radix_tree_scan_hole (&mapping->page_tree, index, max_scan);#ifdef DEBUG_READAHEAD_RADIXTREE -BUG_ON (!__probe_page (mapping, index)); +BUG_ON (!probe_page (mapping, index));WARN_ON (ra_index < index); -if (ra_index != index && !__probe_page (mapping, ra_index -1 )) +if (ra_index != index && !probe_page (mapping, ra_index -1 ))printk (KERN_ERR "radix_tree_scan_hole(index=%lu ra_index=%lu " "max_scan=%lu nrpages=%lu) fooled!\n" , index, ra_index, max_scan, mapping->nrpages);if (ra_index != ~0UL && ra_index - index < max_scan) -WARN_ON (__probe_page (mapping, ra_index)); +WARN_ON (probe_page (mapping, ra_index));#endif read_unlock_irq (&mapping->tree_lock);
@@ -1390,13 +1388,10 @@ static pgoff_t find_segtail_backward(str* Poor man's radix_tree_scan_data_backward() implementation. * Acceptable because max_scan won't be large. */ -read_lock_irq (&mapping->tree_lock); -for (; origin - index < max_scan;) -if (__probe_page (mapping, --index)) { -read_unlock_irq (&mapping->tree_lock); +for (; origin - index < max_scan;) { +if (probe_page (mapping, --index))return index +1 ; - } -read_unlock_irq (&mapping->tree_lock); + }return 0 ; }
@@ -1453,9 +1448,9 @@ static unsigned long query_page_cache_se#ifdef DEBUG_READAHEAD_RADIXTREE WARN_ON (index > offset -1 );if (index != offset -1 ) -WARN_ON (!__probe_page (mapping, index +1 )); +WARN_ON (!probe_page (mapping, index +1 ));if (index && offset -1 - index < ra_max) -WARN_ON (__probe_page (mapping, index)); +WARN_ON (probe_page (mapping, index));#endif *remain = (offset -1 ) - index;
@@ -1485,7 +1480,7 @@ static unsigned long query_page_cache_se100 / (readahead_ratio |1 );for (count += ra_max; count < nr_lookback; count += ra_max) -if (!__probe_page (mapping, offset - count)) +if (!probe_page (mapping, offset - count))break ; out_unlock:
mm/page-writeback.c +3 -5
@@ -965,17 +965,15 @@ void writeback_congestion_end(void)EXPORT_SYMBOL (writeback_congestion_end);/* -* Return true if any of the pages in the mapping are marged with the +* Return true if any of the pages in the mapping are marked with the * passed tag. */ int mapping_tagged (struct address_space *mapping,int tag) { -unsigned long flags;int ret; - -read_lock_irqsave (&mapping->tree_lock, flags); +rcu_read_lock (); ret =radix_tree_tagged (&mapping->page_tree, tag); -read_unlock_irqrestore (&mapping->tree_lock, flags); +rcu_read_unlock ();return ret; }EXPORT_SYMBOL (mapping_tagged);
include/linux/pagemap.h +0 -1
@@ -173,7 +173,6 @@ static inline struct page *page_cache_altypedef int filler_t (void *,struct page *); -extern int __probe_page (struct address_space *mapping,pgoff_t offset);extern int probe_page (struct address_space *mapping,pgoff_t offset);extern struct page *find_get_page (struct address_space *mapping,unsigned long index);
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in the body to [email protected]. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"[email protected]"> [email protected] </a>