aboutsummaryrefslogtreecommitdiffstats
path: root/mm/swap_state.c
diff options
context:
space:
mode:
authorJan Kara <jack@suse.cz>2013-09-11 17:26:05 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2013-09-11 18:59:36 -0400
commit5e4c0d974139a98741b829b27cf38dc8f9284490 (patch)
treefddd959828300c1de1ade15eeb33606c317b79db /mm/swap_state.c
parent4b39248365e09fb8268b6fecd1704907ffc3d980 (diff)
lib/radix-tree.c: make radix_tree_node_alloc() work correctly within interrupt
With users of radix_tree_preload() run from interrupt (block/blk-ioc.c is one such possible user), the following race can happen: radix_tree_preload() ... radix_tree_insert() radix_tree_node_alloc() if (rtp->nr) { ret = rtp->nodes[rtp->nr - 1]; <interrupt> ... radix_tree_preload() ... radix_tree_insert() radix_tree_node_alloc() if (rtp->nr) { ret = rtp->nodes[rtp->nr - 1]; And we give out one radix tree node twice. That clearly results in radix tree corruption with different results (usually OOPS) depending on which two users of radix tree race. We fix the problem by making radix_tree_node_alloc() always allocate fresh radix tree nodes when in interrupt. Using preloading when in interrupt doesn't make sense since all the allocations have to be atomic anyway and we cannot steal nodes from process-context users because some users rely on radix_tree_insert() succeeding after radix_tree_preload(). in_interrupt() check is somewhat ugly but we cannot simply key off passed gfp_mask as that is acquired from root_gfp_mask() and thus the same for all preload users. Another part of the fix is to avoid node preallocation in radix_tree_preload() when passed gfp_mask doesn't allow waiting. Again, preallocation in such case doesn't make sense and when preallocation would happen in interrupt we could possibly leak some allocated nodes. However, some users of radix_tree_preload() require following radix_tree_insert() to succeed. To avoid unexpected effects for these users, radix_tree_preload() only warns if passed gfp mask doesn't allow waiting and we provide a new function radix_tree_maybe_preload() for those users which get different gfp mask from different call sites and which are prepared to handle radix_tree_insert() failure. Signed-off-by: Jan Kara <jack@suse.cz> Cc: Jens Axboe <jaxboe@fusionio.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'mm/swap_state.c')
-rw-r--r--mm/swap_state.c4
1 files changed, 2 insertions, 2 deletions
diff --git a/mm/swap_state.c b/mm/swap_state.c
index f24ab0dff554..e6f15f8ca2af 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -122,7 +122,7 @@ int add_to_swap_cache(struct page *page, swp_entry_t entry, gfp_t gfp_mask)
122{ 122{
123 int error; 123 int error;
124 124
125 error = radix_tree_preload(gfp_mask); 125 error = radix_tree_maybe_preload(gfp_mask);
126 if (!error) { 126 if (!error) {
127 error = __add_to_swap_cache(page, entry); 127 error = __add_to_swap_cache(page, entry);
128 radix_tree_preload_end(); 128 radix_tree_preload_end();
@@ -328,7 +328,7 @@ struct page *read_swap_cache_async(swp_entry_t entry, gfp_t gfp_mask,
328 /* 328 /*
329 * call radix_tree_preload() while we can wait. 329 * call radix_tree_preload() while we can wait.
330 */ 330 */
331 err = radix_tree_preload(gfp_mask & GFP_KERNEL); 331 err = radix_tree_maybe_preload(gfp_mask & GFP_KERNEL);
332 if (err) 332 if (err)
333 break; 333 break;
334 334