lockless pagecache
Subsystems (10)
cover lockless pagecache
Hi,
I wonder what everyone thinks about getting the lockless pagecache patch into -mm? This version uses Hugh's suggestion to avoid a smp_rmb and a load and branch in the lockless lookup side, and avoids some atomic ops in the reclaim path, and avoids using a page flag! The coolest thing about it is that it speeds up single-threaded pagecache lookups...
Patches are against latest git for RFC.
--
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/6 mm: readahead scan lockless
This can actually go upstream now...
--
radix_tree_next_hole is implemented as a series of radix_tree_lookup()s. So it can be called locklessly, under rcu_read_lock().
---
mm/readahead.c +3 -3
@@ -376,9 +376,9 @@ ondemand_readahead(struct address_spaceif (hit_readahead_marker) {pgoff_t start; -read_lock_irq (&mapping->tree_lock); - start =radix_tree_next_hole (&mapping->page_tree, offset, max+1 ); -read_unlock_irq (&mapping->tree_lock); +rcu_read_lock (); + start =radix_tree_next_hole (&mapping->page_tree, offset,max+1 ); +rcu_read_unlock ();if (!start || start - offset > max)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 2/6 radix-tree: gang_lookup_slot
In reply to Nick PigginIntroduce gang_lookup_slot and gang_lookup_slot_tag functions, which are used by lockless pagecache.
include/linux/radix-tree.h +11 -1
@@ -99,12 +99,15 @@ 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_gang_lookup_tag_slot * radix_tree_tagged * -* The first 4 functions are able to be called locklessly, using RCU. The +* The first 7 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.
@@ -159,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_next_hole (struct radix_tree_root *root,unsigned long index,unsigned long max_scan);int radix_tree_preload (gfp_t gfp_mask);
@@ -173,6 +179,10 @@ unsigned int radix_tree_gang_lookup_tag(struct radix_tree_root *root, void **results, unsigned long first_index, unsigned int max_items, unsigned int tag); +unsigned int +radix_tree_gang_lookup_tag_slot (struct radix_tree_root *root,void ***results, +unsigned long first_index,unsigned int max_items, +unsigned int tag);int radix_tree_tagged (struct radix_tree_root *root,unsigned int tag);static inline void radix_tree_preload_end (void )
lib/radix-tree.c +155 -23
262 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/6 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).
include/linux/pagemap.h +91 -3
@@ -12,6 +12,7 @@#include <asm/uaccess.h> #include <linux/gfp.h> #include <linux/bitops.h> +#include <linux/hardirq.h> /* for in_interrupt() */ /* * Bits in mapping->flags. The lower __GFP_BITS_SHIFT bits are the page
@@ -62,6 +63,96 @@ static inline void mapping_set_gfp_mask(#define page_cache_release (page) put_page(page)void release_pages (struct page **pages,int nr,int cold); +/* +* speculatively take a reference to a page. +* If the page is free (_count == 0), then _count is untouched, and 0 +* is returned. Otherwise, _count is incremented by 1 and 1 is returned. +* +* This function must be run in the same rcu_read_lock() section as has +* been used to lookup the page in the pagecache radix-tree: this allows +* allocators to use a synchronize_rcu() to stabilize _count. +* +* Unless an RCU grace period has passed, the count of all pages coming out +* of the allocator must be considered unstable. page_count may return higher +* than expected, and put_page must be able to do the right thing when the +* page has been finished with (because put_page is what is used to drop an +* invalid speculative reference). +* +* This forms the core of the lockless pagecache locking protocol, where +* the lookup-side (eg. find_get_page) has the following pattern: +* 1. find page in radix tree +* 2. conditionally increment refcount +* 3. check the page is still in pagecache (if no, goto 1) +* +* Remove-side that cares about stability of _count (eg. reclaim) has the +* following (with tree_lock held for write): +* A. atomically check refcount is correct and set it to 0 (atomic_cmpxchg) +* B. remove page from pagecache +* C. free the page +* +* There are 2 critical interleavings that matter: +* - 2 runs before A: in this case, A sees elevated refcount and bails out +* - A runs before 2: in this case, 2 sees zero refcount and retries; +* subsequently, B will complete and 1 will find no page, causing the +* lookup to return NULL. +* +* It is possible that between 1 and 2, the page is removed then the exact same +* page is inserted into the same position in pagecache. That's OK: the +* old find_get_page using tree_lock could equally have run before or after +* such a re-insertion, depending on order that locks are granted. +* +* Lookups racing against pagecache insertion isn't a big problem: either 1 +* will find the page or it will not. Likewise, the old find_get_page could run +* either before the insertion or afterwards, depending on timing. +*/ +static inline int page_cache_get_speculative (struct page *page) +{ +VM_BUG_ON (in_interrupt ()); + +#ifndef CONFIG_SMP +# ifdef CONFIG_PREEMPT +VM_BUG_ON (!in_atomic ()); +# endif +/* +* Preempt must be disabled here - we rely on rcu_read_lock doing +* this for us. +* +* Pagecache won't be truncated from interrupt context, so if we have +* found a page in the radix tree here, we have pinned its refcount by +* disabling preempt, and hence no need for the "speculative get" that +* SMP requires. +*/ +VM_BUG_ON (page_count (page) ==0 ); +atomic_inc (&page->_count); + +#else +if (unlikely (!get_page_unless_zero (page))) { +/* +* Either the page has been freed, or will be freed. +* In either case, retry here and the caller should +* do the right thing (see comments above). +*/ +return 0 ; + } +#endif +VM_BUG_ON (PageCompound (page) && (struct page *)page_private (page) != page); + +return 1 ; +} + +static inline int page_freeze_refs (struct page *page,int count) +{ +return likely (atomic_cmpxchg (&page->_count, count,0 ) == count); +} + +static inline void page_unfreeze_refs (struct page *page,int count) +{ +VM_BUG_ON (page_count (page) !=0 ); +VM_BUG_ON (count ==0 ); + +atomic_set (&page->_count, count); +} +#ifdef CONFIG_NUMA extern struct page *__page_cache_alloc (gfp_t gfp);#else
@@ -155,14 +246,11 @@ extern void FASTCALL(unlock_page(structstatic inline void __set_page_locked (struct page *page) { -/* concurrent access would cause data loss with non-atomic bitop */ -VM_BUG_ON (page_count (page) !=1 );__set_bit (PG_locked, &page->flags); }static inline void __clear_page_locked (struct page *page) { -VM_BUG_ON (page_count (page) !=1 );__clear_bit (PG_locked, &page->flags); }
mm/vmscan.c +44 -18
110 lines not on this page · raw message
mm/filemap.c +10 -4
@@ -444,17 +444,23 @@ int add_to_page_cache_locked(struct page error =radix_tree_preload (gfp_mask & ~__GFP_HIGHMEM);if (error ==0 ) { +page_cache_get (page); + page->mapping = mapping; + page->index = offset; +write_lock_irq (&mapping->tree_lock); error =radix_tree_insert (&mapping->page_tree, offset, page); -if (!error) { -page_cache_get (page); - page->mapping = mapping; - page->index = offset; +if (likely (!error)) { mapping->nrpages++;__inc_zone_page_state (page,NR_FILE_PAGES ); }write_unlock_irq (&mapping->tree_lock);radix_tree_preload_end (); + +if (unlikely (error)) { + page->mapping =NULL ; +page_cache_release (page); + } }return error; }
mm/swap_state.c +11 -4
@@ -79,18 +79,25 @@ static int __add_to_swap_cache(struct paBUG_ON (PagePrivate (page)); error =radix_tree_preload (gfp_mask);if (!error) { +page_cache_get (page); +SetPageSwapCache (page); +set_page_private (page, entry.val); +write_lock_irq (&swapper_space.tree_lock); error =radix_tree_insert (&swapper_space.page_tree, entry.val, page); -if (!error) { -page_cache_get (page); -SetPageSwapCache (page); -set_page_private (page, entry.val); +if (likely (!error)) { total_swapcache_pages++;__inc_zone_page_state (page,NR_FILE_PAGES ); }write_unlock_irq (&swapper_space.tree_lock);radix_tree_preload_end (); + +if (unlikely (error)) { +set_page_private (page,0UL ); +ClearPageSwapCache (page); +page_cache_release (page); + } }return error; }
mm/migrate.c +9 -1
@@ -294,6 +294,7 @@ out:static int migrate_page_move_mapping (struct address_space *mapping,struct page *newpage,struct page *page) { +int expected_count;void **pslot;if (!mapping) {
@@ -308,12 +309,18 @@ static int migrate_page_move_mapping(str pslot =radix_tree_lookup_slot (&mapping->page_tree,page_index (page)); -if (page_count (page) !=2 + !!PagePrivate (page) || + expected_count =2 + !!PagePrivate (page); +if (page_count (page) != expected_count || (struct page *)radix_tree_deref_slot (pslot) != page) {write_unlock_irq (&mapping->tree_lock);return -EAGAIN ; } +if (!page_freeze_refs (page, expected_count)) +write_unlock_irq (&mapping->tree_lock); +return -EAGAIN ; + } +/* * Now we know that no one else is looking at the page. */
@@ -327,6 +334,7 @@ static int migrate_page_move_mapping(strradix_tree_replace_slot (pslot, newpage); +page_unfreeze_refs (page, expected_count);/* * Drop cache reference from old page. * We know this isn't the last reference.
--
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>
Re: [patch 3/6] mm: speculative get page
include/linux/pagemap.h static inline void mapping_set_gfp_mask( · line 154
Quoted 12 lines · Nick Piggin
include/linux/pagemap.h +12
-0+static inline int page_freeze_refs (struct page *page,int count) +{ +return likely (atomic_cmpxchg (&page->_count, count,0 ) == count); +} + +static inline void page_unfreeze_refs (struct page *page,int count) +{ +VM_BUG_ON (page_count (page) !=0 ); +VM_BUG_ON (count ==0 ); + +atomic_set (&page->_count, count); +}
Good idea. That avoids another page bit.
Quoted 19 lines
Index:
linux-2.6/mm/migrate.c
===================================================================mm/migrate.c +3
-1@@ -294,6 +294,7 @@ out:static int migrate_page_move_mapping (struct address_space *mapping,struct page *newpage,struct page *page) { +int expected_count;void **pslot;if (!mapping) {@@ -308,12 +309,18 @@ static int migrate_page_move_mapping(str pslot =radix_tree_lookup_slot (&mapping->page_tree,page_index (page)); - if (page_count(page) != 2 + !!PagePrivate(page) || + expected_count =2 + !!PagePrivate (page); + if (page_count(page) != expected_count ||
mm/migrate.c static int migrate_page_move_mapping(str · line 323
Quoted 10 lines
mm/migrate.c +5
-0(struct page *)radix_tree_deref_slot(pslot) != page) {write_unlock_irq (&mapping->tree_lock);return -EAGAIN ; } +if (!page_freeze_refs (page, expected_count)) +write_unlock_irq (&mapping->tree_lock); +return -EAGAIN ; + } +
Looks okay but I think you could remove the earlier performance check. We already modified the page struct by obtaining the page lock so we hold it exclusively. And the failure rate here is typicalyvery low.
--
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>
Re: [patch 3/6] mm: speculative get page
Quoted 16 lines · Christoph Lameter
include/linux/pagemap.h static inline void mapping_set_gfp_mask( · line 154
Quoted 12 lines · Nick Piggin
include/linux/pagemap.h +12
-0+static inline int page_freeze_refs (struct page *page,int count) +{ +return likely (atomic_cmpxchg (&page->_count, count,0 ) == count); +} + +static inline void page_unfreeze_refs (struct page *page,int count) +{ +VM_BUG_ON (page_count (page) !=0 ); +VM_BUG_ON (count ==0 ); + +atomic_set (&page->_count, count); +}Good idea. That avoids another page bit.
Yeah, it's Hugh's good idea. It avoids smp_rmb() in the find_get_page path as well, which will be helpful at least for things like powerpc and ia64, if not x86. At one single atomic operation to lookup and take a reference on a pagecache page, I think it is approaching the fastest possible implementation ;)
Quoted 24 lines
Quoted 24 lines
Index:
linux-2.6/mm/migrate.c
===================================================================mm/migrate.c +3
-1@@ -294,6 +294,7 @@ out:static int migrate_page_move_mapping (struct address_space *mapping,struct page *newpage,struct page *page) { +int expected_count;void **pslot;if (!mapping) {@@ -308,12 +309,18 @@ static int migrate_page_move_mapping(str pslot =radix_tree_lookup_slot (&mapping->page_tree,page_index (page)); -if (page_count (page) !=2 + !!PagePrivate (page) || + expected_count =2 + !!PagePrivate (page); +if (page_count (page) != expected_count || (struct page *)radix_tree_deref_slot (pslot) != page) {write_unlock_irq (&mapping->tree_lock);return -EAGAIN ; }
Quoted 9 lines
mm/migrate.c static int migrate_page_move_mapping(str · line 323
Quoted 5 lines
mm/migrate.c +5
-0+if (!page_freeze_refs (page, expected_count)) +write_unlock_irq (&mapping->tree_lock); +return -EAGAIN ; + } +Looks okay but I think you could remove the earlier performance check. We already modified the page struct by obtaining the page lock so we hold it exclusively. And the failure rate here is typicalyvery low.
It's up to you. Honestly, I don't have good test facilities for page migration. If it's all the same to you, do you mind if we leave it like this, and then you can change it in future?
I expect the earlier check won't hurt too much either, even if it doesn't trigger for 99.9% of pages...
--
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>
Re: [patch 3/6] mm: speculative get page
Quoted 7 lines · Nick Piggin
Quoted 1 line
Good idea. That avoids another page bit.
Yeah, it's Hugh's good idea. It avoids
smp_rmb()in thefind_get_pagepath as well, which will be helpful at least for things like powerpc and ia64, if not x86. At one single atomic operation to lookup and take a reference on a pagecache page, I think it is approaching the fastest possible implementation ;)
Well I hope all locations that do get_page_unless_zero are aware that a failure does not mean that the page is being freed.
Quoted 7 lines
Quoted 3 lines
Looks okay but I think you could remove the earlier performance check. We already modified the page struct by obtaining the page lock so we hold it exclusively. And the failure rate here is typicalyvery low.
It's up to you. Honestly, I don't have good test facilities for page migration. If it's all the same to you, do you mind if we leave it like this, and then you can change it in future?
Ok. Lee will likely get to that ;-).
--
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>
Re: [patch 3/6] mm: speculative get page
Quoted 4 lines · Christoph Lameter
Quoted 2 lines · Nick Piggin
Quoted 1 line
Good idea. That avoids another page bit.
Quoted 8 lines
Quoted 5 lines
Yeah, it's Hugh's good idea. It avoids
smp_rmb()in thefind_get_pagepath as well, which will be helpful at least for things like powerpc and ia64, if not x86. At one single atomic operation to lookup and take a reference on a pagecache page, I think it is approaching the fastest possible implementation ;)Well I hope all locations that do
get_page_unless_zeroare aware that a failure does not mean that the page is being freed.
Often it does mean the page will be freed soon anyway, so it fits quite well. But no, nothing assumes the page will be freed. It is hard to imagine the use of such knowledge would be (outside the page allocator).
You can't use it to assume the page is going to be free at any point T+now, nor can you assume the pagecache coordinate which used to hold the page is empty.
Quoted 9 lines
Quoted 7 lines
Quoted 3 lines
Looks okay but I think you could remove the earlier performance check. We already modified the page struct by obtaining the page lock so we hold it exclusively. And the failure rate here is typicalyvery low.
It's up to you. Honestly, I don't have good test facilities for page migration. If it's all the same to you, do you mind if we leave it like this, and then you can change it in future?
Ok. Lee will likely get to that ;-).
OK good, thanks ;)
--
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/6 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 +134 -45
@@ -601,15 +601,35 @@ 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,pgoff_t offset) +struct page *find_get_page (struct address_space *mapping,pgoff_t 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? +* This is part of the lockless pagecache protocol. See +* include/linux/pagemap.h for details. +*/ +if (unlikely (page != *pagep)) { +page_cache_release (page); + goto repeat; + } + } +rcu_read_unlock (); +return page; }EXPORT_SYMBOL (find_get_page);
@@ -624,32 +644,22 @@ EXPORT_SYMBOL(find_get_page);* * Returns zero if the page was not present. find_lock_page() may sleep. */ -struct page *find_lock_page (struct address_space *mapping, -pgoff_t offset) +struct page *find_lock_page (struct address_space *mapping,pgoff_t offset) {struct page *page; repeat: -read_lock_irq (&mapping->tree_lock); - page =radix_tree_lookup (&mapping->page_tree, offset); + page =find_get_page (mapping, offset);if (page) { -page_cache_get (page); -if (!trylock_page (page)) { -read_unlock_irq (&mapping->tree_lock); -__lock_page (page); - -/* Has the page been truncated while we slept? */ -if (unlikely (page->mapping != mapping)) { -unlock_page (page); -page_cache_release (page); - goto repeat; - } -VM_BUG_ON (page->index != offset); - goto out; +lock_page (page); +/* Has the page been truncated? */ +if (unlikely (page->mapping != mapping)) { +unlock_page (page); +page_cache_release (page); + goto repeat; } +VM_BUG_ON (page->index != offset); } -read_unlock_irq (&mapping->tree_lock); -out:return page; }EXPORT_SYMBOL (find_lock_page);
@@ -715,13 +725,39 @@ unsigned find_get_pages(struct address_s {unsigned int i;unsigned int ret; +unsigned int nr_found; + +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; + } -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); + pages[ret] = page; + ret++; + } +rcu_read_unlock ();return ret; }
@@ -742,19 +778,44 @@ unsigned find_get_pages_contig(struct ad {unsigned int i;unsigned int ret; +unsigned int nr_found; + +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; -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) +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_contig);
@@ -774,15 +835,43 @@ unsigned find_get_pages_tag(struct addre {unsigned int i;unsigned int ret; +unsigned int nr_found; + +rcu_read_lock (); +restart: + nr_found =radix_tree_gang_lookup_tag_slot (&mapping->page_tree, + (void ***)pages, *index, nr_pages, tag); + 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 (); -read_lock_irq (&mapping->tree_lock); - ret =radix_tree_gang_lookup_tag (&mapping->page_tree, - (void **)pages, *index, nr_pages, tag); -for (i =0 ; i < ret; i++) -page_cache_get (pages[i]);if (ret) *index = pages[ret -1 ]->index +1 ; -read_unlock_irq (&mapping->tree_lock); +return ret; }EXPORT_SYMBOL (find_get_pages_tag);
--
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 5/6 mm: spinlock tree_lock
In reply to Nick Pigginmapping->tree_lock has no read lockers. convert the lock from an rwlock to a spinlock.
---
fs/buffer.c +2 -2
@@ -704,7 +704,7 @@ static int __set_page_dirty(struct pageif (TestSetPageDirty (page))return 0 ; -write_lock_irq (&mapping->tree_lock); +spin_lock_irq (&mapping->tree_lock);if (page->mapping) {/* Race with truncate? */ WARN_ON_ONCE (warn && !PageUptodate (page));
@@ -717,7 +717,7 @@ static int __set_page_dirty(struct pageradix_tree_tag_set (&mapping->page_tree,page_index (page),PAGECACHE_TAG_DIRTY ); } -write_unlock_irq (&mapping->tree_lock); +spin_unlock_irq (&mapping->tree_lock);__mark_inode_dirty (mapping->host,I_DIRTY_PAGES );return 1 ;
fs/inode.c +1 -1
@@ -209,7 +209,7 @@ void inode_init_once(struct inode *inodeINIT_LIST_HEAD (&inode->i_dentry);INIT_LIST_HEAD (&inode->i_devices);INIT_RADIX_TREE (&inode->i_data.page_tree,GFP_ATOMIC ); -rwlock_init (&inode->i_data.tree_lock); +spin_lock_init (&inode->i_data.tree_lock);spin_lock_init (&inode->i_data.i_mmap_lock);INIT_LIST_HEAD (&inode->i_data.private_list);spin_lock_init (&inode->i_data.private_lock);
include/linux/fs.h +1 -1
@@ -497,7 +497,7 @@ struct backing_dev_info;struct address_space {struct inode *host;/* owner: inode, block_device */ struct radix_tree_root page_tree;/* radix tree of all pages */ -rwlock_t tree_lock;/* and rwlock protecting it */ +spinlock_t tree_lock;/* and lock protecting it */ unsigned int i_mmap_writable;/* count VM_SHARED mappings */ struct prio_tree_root i_mmap;/* tree of private and shared mappings */ struct list_head i_mmap_nonlinear;/*list VM_NONLINEAR mappings */
mm/filemap.c +5 -5
@@ -113,7 +113,7 @@ generic_file_direct_IO(int rw, struct ki/* * Remove a page from the page cache and free it. Caller has to make * sure the page is locked and that nobody else uses it - or that usage -* is safe. The caller must hold a write_lock on the mapping's tree_lock. +* is safe. The caller must hold the mapping's tree_lock. */ void __remove_from_page_cache (struct page *page) {
@@ -132,9 +132,9 @@ void remove_from_page_cache(struct pageBUG_ON (!PageLocked (page)); -write_lock_irq (&mapping->tree_lock); +spin_lock_irq (&mapping->tree_lock);__remove_from_page_cache (page); -write_unlock_irq (&mapping->tree_lock); +spin_unlock_irq (&mapping->tree_lock); }static int sync_page (void *word)
@@ -448,13 +448,13 @@ int add_to_page_cache_locked(struct page page->mapping = mapping; page->index = offset; -write_lock_irq (&mapping->tree_lock); +spin_lock_irq (&mapping->tree_lock); error =radix_tree_insert (&mapping->page_tree, offset, page);if (likely (!error)) { mapping->nrpages++;__inc_zone_page_state (page,NR_FILE_PAGES ); } -write_unlock_irq (&mapping->tree_lock); +spin_unlock_irq (&mapping->tree_lock);radix_tree_preload_end ();if (unlikely (error)) {
mm/swap_state.c +5 -5
@@ -38,7 +38,7 @@ static struct backing_dev_info swap_backstruct address_space swapper_space = { .page_tree =RADIX_TREE_INIT (GFP_ATOMIC |__GFP_NOWARN), - .tree_lock =__RW_LOCK_UNLOCKED (swapper_space.tree_lock), + .tree_lock =__SPIN_LOCK_UNLOCKED (swapper_space.tree_lock), .a_ops = &swap_aops, .i_mmap_nonlinear =LIST_HEAD_INIT (swapper_space.i_mmap_nonlinear), .backing_dev_info = &swap_backing_dev_info,
@@ -83,14 +83,14 @@ static int __add_to_swap_cache(struct paSetPageSwapCache (page);set_page_private (page, entry.val); -write_lock_irq (&swapper_space.tree_lock); +spin_lock_irq (&swapper_space.tree_lock); error =radix_tree_insert (&swapper_space.page_tree, entry.val, page);if (likely (!error)) { total_swapcache_pages++;__inc_zone_page_state (page,NR_FILE_PAGES ); } -write_unlock_irq (&swapper_space.tree_lock); +spin_unlock_irq (&swapper_space.tree_lock);radix_tree_preload_end ();if (unlikely (error)) {
@@ -209,9 +209,9 @@ void delete_from_swap_cache(struct page entry.val =page_private (page); -write_lock_irq (&swapper_space.tree_lock); +spin_lock_irq (&swapper_space.tree_lock);__delete_from_swap_cache (page); -write_unlock_irq (&swapper_space.tree_lock); +spin_unlock_irq (&swapper_space.tree_lock);swap_free (entry);page_cache_release (page);
mm/swapfile.c +2 -2
@@ -367,13 +367,13 @@ int remove_exclusive_swap_page(struct pa retval =0 ;if (p->swap_map[swp_offset (entry)] ==1 ) {/* Recheck the page count with the swapcache lock held.. */ -write_lock_irq (&swapper_space.tree_lock); +spin_lock_irq (&swapper_space.tree_lock);if ((page_count (page) ==2 ) && !PageWriteback (page)) {__delete_from_swap_cache (page);SetPageDirty (page); retval =1 ; } -write_unlock_irq (&swapper_space.tree_lock); +spin_unlock_irq (&swapper_space.tree_lock); }spin_unlock (&swap_lock);
mm/truncate.c +3 -3
@@ -350,18 +350,18 @@ invalidate_complete_page2(struct addressif (PagePrivate (page) && !try_to_release_page (page,GFP_KERNEL ))return 0 ; -write_lock_irq (&mapping->tree_lock); +spin_lock_irq (&mapping->tree_lock);if (PageDirty (page)) goto failed;BUG_ON (PagePrivate (page));__remove_from_page_cache (page); -write_unlock_irq (&mapping->tree_lock); +spin_unlock_irq (&mapping->tree_lock);ClearPageUptodate (page);page_cache_release (page);/* pagecache ref */ return 1 ; failed: -write_unlock_irq (&mapping->tree_lock); +spin_unlock_irq (&mapping->tree_lock);return 0 ; }
mm/vmscan.c +4 -4
@@ -382,7 +382,7 @@ static int __remove_mapping(struct addreBUG_ON (!PageLocked (page));BUG_ON (mapping !=page_mapping (page)); -write_lock_irq (&mapping->tree_lock); +spin_lock_irq (&mapping->tree_lock);/* * The non racy check for a busy page. *
@@ -417,18 +417,18 @@ static int __remove_mapping(struct addreif (PageSwapCache (page)) {swp_entry_t swap = { .val =page_private (page) };__delete_from_swap_cache (page); -write_unlock_irq (&mapping->tree_lock); +spin_unlock_irq (&mapping->tree_lock);swap_free (swap); }else {__remove_from_page_cache (page); -write_unlock_irq (&mapping->tree_lock); +spin_unlock_irq (&mapping->tree_lock); } free_it:return 1 ; cannot_free: -write_unlock_irq (&mapping->tree_lock); +spin_unlock_irq (&mapping->tree_lock);return 0 ; }
mm/page-writeback.c +6 -6
@@ -1000,7 +1000,7 @@ int __set_page_dirty_nobuffers(struct paif (!mapping)return 1 ; -write_lock_irq (&mapping->tree_lock); +spin_lock_irq (&mapping->tree_lock); mapping2 =page_mapping (page);if (mapping2) {/* Race with truncate? */ BUG_ON (mapping2 != mapping);
@@ -1014,7 +1014,7 @@ int __set_page_dirty_nobuffers(struct paradix_tree_tag_set (&mapping->page_tree,page_index (page),PAGECACHE_TAG_DIRTY ); } -write_unlock_irq (&mapping->tree_lock); +spin_unlock_irq (&mapping->tree_lock);if (mapping->host) {/* !PageAnon && !swapper_space */ __mark_inode_dirty (mapping->host,I_DIRTY_PAGES );
@@ -1170,7 +1170,7 @@ int test_clear_page_writeback(struct pagstruct backing_dev_info *bdi = mapping->backing_dev_info;unsigned long flags; -write_lock_irqsave (&mapping->tree_lock, flags); +spin_lock_irqsave (&mapping->tree_lock, flags); ret =TestClearPageWriteback (page);if (ret) { radix_tree_tag_clear(&mapping->page_tree,
@@ -1181,7 +1181,7 @@ int test_clear_page_writeback(struct pag__bdi_writeout_inc (bdi); } } -write_unlock_irqrestore (&mapping->tree_lock, flags); +spin_unlock_irqrestore (&mapping->tree_lock, flags); }else { ret =TestClearPageWriteback (page); }
@@ -1199,7 +1199,7 @@ int test_set_page_writeback(struct pagestruct backing_dev_info *bdi = mapping->backing_dev_info;unsigned long flags; -write_lock_irqsave (&mapping->tree_lock, flags); +spin_lock_irqsave (&mapping->tree_lock, flags); ret =TestSetPageWriteback (page);if (!ret) { radix_tree_tag_set(&mapping->page_tree,
@@ -1212,7 +1212,7 @@ int test_set_page_writeback(struct pageradix_tree_tag_clear (&mapping->page_tree,page_index (page),PAGECACHE_TAG_DIRTY ); -write_unlock_irqrestore (&mapping->tree_lock, flags); +spin_unlock_irqrestore (&mapping->tree_lock, flags); }else { ret =TestSetPageWriteback (page); }
include/asm-arm/cacheflush.h +2 -2
@@ -413,9 +413,9 @@ static inline void flush_anon_page(struc }#define flush_dcache_mmap_lock (mapping) \ - write_lock_irq(&(mapping)->tree_lock) + spin_lock_irq(&(mapping)->tree_lock)#define flush_dcache_mmap_unlock (mapping) \ - write_unlock_irq(&(mapping)->tree_lock) + spin_unlock_irq(&(mapping)->tree_lock)#define flush_icache_user_range (vma,page,addr,len) \ flush_dcache_page(page)
include/asm-parisc/cacheflush.h +2 -2
@@ -45,9 +45,9 @@ void flush_cache_mm(struct mm_struct *mmextern void flush_dcache_page (struct page *page);#define flush_dcache_mmap_lock (mapping) \ - write_lock_irq(&(mapping)->tree_lock) + spin_lock_irq(&(mapping)->tree_lock)#define flush_dcache_mmap_unlock (mapping) \ - write_unlock_irq(&(mapping)->tree_lock) + spin_unlock_irq(&(mapping)->tree_lock)#define flush_icache_page (vma,page) do { \ flush_kernel_dcache_page(page); \
mm/migrate.c +4 -4
@@ -304,7 +304,7 @@ static int migrate_page_move_mapping(strreturn 0 ; } -write_lock_irq (&mapping->tree_lock); +spin_lock_irq (&mapping->tree_lock); pslot =radix_tree_lookup_slot (&mapping->page_tree,page_index (page));
@@ -312,12 +312,12 @@ static int migrate_page_move_mapping(str expected_count =2 + !!PagePrivate (page);if (page_count (page) != expected_count || (struct page *)radix_tree_deref_slot (pslot) != page) { -write_unlock_irq (&mapping->tree_lock); +spin_unlock_irq (&mapping->tree_lock);return -EAGAIN ; }if (!page_freeze_refs (page, expected_count)) -write_unlock_irq (&mapping->tree_lock); +spin_unlock_irq (&mapping->tree_lock);return -EAGAIN ; }
@@ -354,7 +354,7 @@ static int migrate_page_move_mapping(str__dec_zone_page_state (page,NR_FILE_PAGES );__inc_zone_page_state (newpage,NR_FILE_PAGES ); -write_unlock_irq (&mapping->tree_lock); +spin_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 6/6 mm: speculative refcount debug
In reply to Nick PigginAdd some debugging for lockless pagecache.
---
include/linux/mm.h +21 -0
@@ -236,9 +236,30 @@ static inline struct page *compound_headreturn page; } +#ifdef CONFIG_DEBUG_VM +extern int ll_counter; +#endif +static inline int page_count (struct page *page) { +#ifndef CONFIG_DEBUG_VM return atomic_read (&compound_head (page)->_count); +#else + +int count; +/* +* debug testing for lockless pagecache. add a random value to +* page_count every now and then, to simulate speculative references +* to it. +*/ + count =atomic_read (&compound_head (page)->_count); +if (count) { + ll_counter++; +if (ll_counter %5 ==0 || ll_counter %7 ==0 ) + count += ll_counter %11 ; + } +return count; +#endif }static inline void get_page (struct page *page)
mm/page_alloc.c +2 -0
@@ -173,6 +173,8 @@ static void set_pageblock_migratetype(st }#ifdef CONFIG_DEBUG_VM +int ll_counter;/* used in include/linux/mm.h, for lockless pagecache */ +EXPORT_SYMBOL (ll_counter);static int page_outside_zone_boundaries (struct zone *zone,struct page *page) {int ret =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>
Quoted 9 lines · Nick Piggin
Hi,
I wonder what everyone thinks about getting the lockless pagecache patch into -mm? This version uses Hugh's suggestion to avoid a
smp_rmband a load and branch in the lockless lookup side, and avoids some atomic ops in the reclaim path, and avoids using a page flag! The coolest thing about it is that it speeds up single-threaded pagecache lookups...Patches are against latest git for RFC.
Full set
Quoted 6 lines · Nick Piggin
I wonder what everyone thinks about getting the lockless pagecache patch into -mm? This version uses Hugh's suggestion to avoid a
smp_rmband a load and branch in the lockless lookup side, and avoids some atomic ops in the reclaim path, and avoids using a page flag! The coolest thing about it is that it speeds up single-threaded pagecache lookups...
I've liked this in the past, with the exception of PageNoNewRefs which seemed an unnecessary ugliness. Now you've eliminated that, thank you, I expect I should like it through and through (if I actually found time to redigest it). A moment came up and I thought I'd give it a spin...
Quoted 1 line
Patches are against latest git for RFC.
... but they're not. You seem to have descended into sending out ?cleanup? patches at intervals, and recursive dependence upon them. This set relies on there being something called __set_page_locked() in include/linux/pagemap.h, but there isn't in latest git (nor mm). Ah, you posted a patch earlier which introduced that, but it relies on there being something called set_page_locked() in include/linux/pagemap.h, but there isn't in latest git (nor mm). Ah, you posted a patch earlier which introduced that ... I gave up at this point.
We've all got lots of other things to do, please make it easier.
Hugh
--
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>
Quoted 8 lines · Hugh Dickins
Quoted 6 lines · Nick Piggin
I wonder what everyone thinks about getting the lockless pagecache patch into -mm? This version uses Hugh's suggestion to avoid a
smp_rmband a load and branch in the lockless lookup side, and avoids some atomic ops in the reclaim path, and avoids using a page flag! The coolest thing about it is that it speeds up single-threaded pagecache lookups...
Quoted 4 lines
I've liked this in the past, with the exception of PageNoNewRefs which seemed an unnecessary ugliness. Now you've eliminated that, thank you, I expect I should like it through and through (if I actually found time to redigest it). A moment came up and I thought I'd give it a spin...
Yeah I decided it is actually just as good or better at it's job -- neither really protects against an errant get_page() or put_page(), however at least this scheme will go bug with CONFIG_DEBUG_VM, as opposed to the current or old lockless schemes (which I guess will silently allow it).
Quoted 11 lines
Quoted 1 line
Patches are against latest git for RFC.
... but they're not. You seem to have descended into sending out ?cleanup? patches at intervals, and recursive dependence upon them. This set relies on there being something called
__set_page_locked()ininclude/linux/pagemap.h, but there isn't in latest git (nor mm). Ah, you posted a patch earlier which introduced that, but it relies on there being something calledset_page_locked()ininclude/linux/pagemap.h, but there isn't in latest git (nor mm). Ah, you posted a patch earlier which introduced that ... I gave up at this point.
Quoted 1 line
We've all got lots of other things to do, please make it easier.
Sorry, I honestly didn't pay enough attention there because I didn't think anybody would run it! I'll update it and resend.
--
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>
Quoted 9 lines · Nick Piggin
Hi,
I wonder what everyone thinks about getting the lockless pagecache patch into -mm? This version uses Hugh's suggestion to avoid a
smp_rmband a load and branch in the lockless lookup side, and avoids some atomic ops in the reclaim path, and avoids using a page flag! The coolest thing about it is that it speeds up single-threaded pagecache lookups...Patches are against latest git for RFC.
How are we doing with this?
--
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>