diff options
Diffstat (limited to 'mm/swap_state.c')
| -rw-r--r-- | mm/swap_state.c | 143 | 
1 files changed, 82 insertions, 61 deletions
diff --git a/mm/swap_state.c b/mm/swap_state.c index 5ae6b8b78c8..6d1daeb1cb4 100644 --- a/mm/swap_state.c +++ b/mm/swap_state.c @@ -67,10 +67,10 @@ void show_swap_cache_info(void)  }  /* - * add_to_swap_cache resembles add_to_page_cache_locked on swapper_space, + * __add_to_swap_cache resembles add_to_page_cache_locked on swapper_space,   * but sets SwapCache flag and private instead of mapping and index.   */ -int add_to_swap_cache(struct page *page, swp_entry_t entry, gfp_t gfp_mask) +static int __add_to_swap_cache(struct page *page, swp_entry_t entry)  {  	int error; @@ -78,28 +78,43 @@ int add_to_swap_cache(struct page *page, swp_entry_t entry, gfp_t gfp_mask)  	VM_BUG_ON(PageSwapCache(page));  	VM_BUG_ON(!PageSwapBacked(page)); +	page_cache_get(page); +	SetPageSwapCache(page); +	set_page_private(page, entry.val); + +	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); +		INC_CACHE_INFO(add_total); +	} +	spin_unlock_irq(&swapper_space.tree_lock); + +	if (unlikely(error)) { +		/* +		 * Only the context which have set SWAP_HAS_CACHE flag +		 * would call add_to_swap_cache(). +		 * So add_to_swap_cache() doesn't returns -EEXIST. +		 */ +		VM_BUG_ON(error == -EEXIST); +		set_page_private(page, 0UL); +		ClearPageSwapCache(page); +		page_cache_release(page); +	} + +	return error; +} + + +int add_to_swap_cache(struct page *page, swp_entry_t entry, gfp_t gfp_mask) +{ +	int error; +  	error = radix_tree_preload(gfp_mask);  	if (!error) { -		page_cache_get(page); -		SetPageSwapCache(page); -		set_page_private(page, entry.val); - -		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); -			INC_CACHE_INFO(add_total); -		} -		spin_unlock_irq(&swapper_space.tree_lock); +		error = __add_to_swap_cache(page, entry);  		radix_tree_preload_end(); - -		if (unlikely(error)) { -			set_page_private(page, 0UL); -			ClearPageSwapCache(page); -			page_cache_release(page); -		}  	}  	return error;  } @@ -137,38 +152,34 @@ int add_to_swap(struct page *page)  	VM_BUG_ON(!PageLocked(page));  	VM_BUG_ON(!PageUptodate(page)); -	for (;;) { -		entry = get_swap_page(); -		if (!entry.val) -			return 0; +	entry = get_swap_page(); +	if (!entry.val) +		return 0; +	/* +	 * Radix-tree node allocations from PF_MEMALLOC contexts could +	 * completely exhaust the page allocator. __GFP_NOMEMALLOC +	 * stops emergency reserves from being allocated. +	 * +	 * TODO: this could cause a theoretical memory reclaim +	 * deadlock in the swap out path. +	 */ +	/* +	 * Add it to the swap cache and mark it dirty +	 */ +	err = add_to_swap_cache(page, entry, +			__GFP_HIGH|__GFP_NOMEMALLOC|__GFP_NOWARN); + +	if (!err) {	/* Success */ +		SetPageDirty(page); +		return 1; +	} else {	/* -ENOMEM radix-tree allocation failure */  		/* -		 * Radix-tree node allocations from PF_MEMALLOC contexts could -		 * completely exhaust the page allocator. __GFP_NOMEMALLOC -		 * stops emergency reserves from being allocated. -		 * -		 * TODO: this could cause a theoretical memory reclaim -		 * deadlock in the swap out path. -		 */ -		/* -		 * Add it to the swap cache and mark it dirty +		 * add_to_swap_cache() doesn't return -EEXIST, so we can safely +		 * clear SWAP_HAS_CACHE flag.  		 */ -		err = add_to_swap_cache(page, entry, -				__GFP_HIGH|__GFP_NOMEMALLOC|__GFP_NOWARN); - -		switch (err) { -		case 0:				/* Success */ -			SetPageDirty(page); -			return 1; -		case -EEXIST: -			/* Raced with "speculative" read_swap_cache_async */ -			swapcache_free(entry, NULL); -			continue; -		default: -			/* -ENOMEM radix-tree allocation failure */ -			swapcache_free(entry, NULL); -			return 0; -		} +		swapcache_free(entry, NULL); +		return 0;  	}  } @@ -290,26 +301,31 @@ struct page *read_swap_cache_async(swp_entry_t entry, gfp_t gfp_mask,  		}  		/* +		 * call radix_tree_preload() while we can wait. +		 */ +		err = radix_tree_preload(gfp_mask & GFP_KERNEL); +		if (err) +			break; + +		/*  		 * Swap entry may have been freed since our caller observed it.  		 */  		err = swapcache_prepare(entry); -		if (err == -EEXIST) /* seems racy */ +		if (err == -EEXIST) {	/* seems racy */ +			radix_tree_preload_end();  			continue; -		if (err)           /* swp entry is obsolete ? */ +		} +		if (err) {		/* swp entry is obsolete ? */ +			radix_tree_preload_end();  			break; +		} -		/* -		 * Associate the page with swap entry in the swap cache. -		 * May fail (-EEXIST) if there is already a page associated -		 * with this entry in the swap cache: added by a racing -		 * read_swap_cache_async, or add_to_swap or shmem_writepage -		 * re-using the just freed swap entry for an existing page. -		 * May fail (-ENOMEM) if radix-tree node allocation failed. -		 */ +		/* May fail (-ENOMEM) if radix-tree node allocation failed. */  		__set_page_locked(new_page);  		SetPageSwapBacked(new_page); -		err = add_to_swap_cache(new_page, entry, gfp_mask & GFP_KERNEL); +		err = __add_to_swap_cache(new_page, entry);  		if (likely(!err)) { +			radix_tree_preload_end();  			/*  			 * Initiate read into locked page and return.  			 */ @@ -317,8 +333,13 @@ struct page *read_swap_cache_async(swp_entry_t entry, gfp_t gfp_mask,  			swap_readpage(new_page);  			return new_page;  		} +		radix_tree_preload_end();  		ClearPageSwapBacked(new_page);  		__clear_page_locked(new_page); +		/* +		 * add_to_swap_cache() doesn't return -EEXIST, so we can safely +		 * clear SWAP_HAS_CACHE flag. +		 */  		swapcache_free(entry, NULL);  	} while (err != -ENOMEM);  | 
