diff options
author | Weijie Yang <weijie.yang@samsung.com> | 2013-11-12 18:08:26 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-11-12 22:09:10 -0500 |
commit | 67d13fe846c57a54d12578e7a4518f68c5c86ad7 (patch) | |
tree | b1b00b15b87941b26f05bb181150c5d1fffbccc0 /mm/zswap.c | |
parent | 7a67d7abcc8da30a16ed64c3909d3fea004bde93 (diff) |
mm/zswap: bugfix: memory leak when invalidate and reclaim occur concurrently
Consider the following scenario:
thread 0: reclaim entry x (get refcount, but not call zswap_get_swap_cache_page)
thread 1: call zswap_frontswap_invalidate_page to invalidate entry x.
finished, entry x and its zbud is not freed as its refcount != 0
now, the swap_map[x] = 0
thread 0: now call zswap_get_swap_cache_page
swapcache_prepare return -ENOENT because entry x is not used any more
zswap_get_swap_cache_page return ZSWAP_SWAPCACHE_NOMEM
zswap_writeback_entry do nothing except put refcount
Now, the memory of zswap_entry x and its zpage leak.
Modify:
- check the refcount in fail path, free memory if it is not referenced.
- use ZSWAP_SWAPCACHE_FAIL instead of ZSWAP_SWAPCACHE_NOMEM as the fail path
can be not only caused by nomem but also by invalidate.
Signed-off-by: Weijie Yang <weijie.yang@samsung.com>
Reviewed-by: Bob Liu <bob.liu@oracle.com>
Reviewed-by: Minchan Kim <minchan@kernel.org>
Acked-by: Seth Jennings <sjenning@linux.vnet.ibm.com>
Cc: <stable@vger.kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'mm/zswap.c')
-rw-r--r-- | mm/zswap.c | 22 |
1 files changed, 14 insertions, 8 deletions
diff --git a/mm/zswap.c b/mm/zswap.c index 001474c1a594..0ffcad03baea 100644 --- a/mm/zswap.c +++ b/mm/zswap.c | |||
@@ -387,7 +387,7 @@ static void zswap_free_entry(struct zswap_tree *tree, struct zswap_entry *entry) | |||
387 | enum zswap_get_swap_ret { | 387 | enum zswap_get_swap_ret { |
388 | ZSWAP_SWAPCACHE_NEW, | 388 | ZSWAP_SWAPCACHE_NEW, |
389 | ZSWAP_SWAPCACHE_EXIST, | 389 | ZSWAP_SWAPCACHE_EXIST, |
390 | ZSWAP_SWAPCACHE_NOMEM | 390 | ZSWAP_SWAPCACHE_FAIL, |
391 | }; | 391 | }; |
392 | 392 | ||
393 | /* | 393 | /* |
@@ -401,9 +401,10 @@ enum zswap_get_swap_ret { | |||
401 | * added to the swap cache, and returned in retpage. | 401 | * added to the swap cache, and returned in retpage. |
402 | * | 402 | * |
403 | * If success, the swap cache page is returned in retpage | 403 | * If success, the swap cache page is returned in retpage |
404 | * Returns 0 if page was already in the swap cache, page is not locked | 404 | * Returns ZSWAP_SWAPCACHE_EXIST if page was already in the swap cache |
405 | * Returns 1 if the new page needs to be populated, page is locked | 405 | * Returns ZSWAP_SWAPCACHE_NEW if the new page needs to be populated, |
406 | * Returns <0 on error | 406 | * the new page is added to swapcache and locked |
407 | * Returns ZSWAP_SWAPCACHE_FAIL on error | ||
407 | */ | 408 | */ |
408 | static int zswap_get_swap_cache_page(swp_entry_t entry, | 409 | static int zswap_get_swap_cache_page(swp_entry_t entry, |
409 | struct page **retpage) | 410 | struct page **retpage) |
@@ -475,7 +476,7 @@ static int zswap_get_swap_cache_page(swp_entry_t entry, | |||
475 | if (new_page) | 476 | if (new_page) |
476 | page_cache_release(new_page); | 477 | page_cache_release(new_page); |
477 | if (!found_page) | 478 | if (!found_page) |
478 | return ZSWAP_SWAPCACHE_NOMEM; | 479 | return ZSWAP_SWAPCACHE_FAIL; |
479 | *retpage = found_page; | 480 | *retpage = found_page; |
480 | return ZSWAP_SWAPCACHE_EXIST; | 481 | return ZSWAP_SWAPCACHE_EXIST; |
481 | } | 482 | } |
@@ -529,11 +530,11 @@ static int zswap_writeback_entry(struct zbud_pool *pool, unsigned long handle) | |||
529 | 530 | ||
530 | /* try to allocate swap cache page */ | 531 | /* try to allocate swap cache page */ |
531 | switch (zswap_get_swap_cache_page(swpentry, &page)) { | 532 | switch (zswap_get_swap_cache_page(swpentry, &page)) { |
532 | case ZSWAP_SWAPCACHE_NOMEM: /* no memory */ | 533 | case ZSWAP_SWAPCACHE_FAIL: /* no memory or invalidate happened */ |
533 | ret = -ENOMEM; | 534 | ret = -ENOMEM; |
534 | goto fail; | 535 | goto fail; |
535 | 536 | ||
536 | case ZSWAP_SWAPCACHE_EXIST: /* page is unlocked */ | 537 | case ZSWAP_SWAPCACHE_EXIST: |
537 | /* page is already in the swap cache, ignore for now */ | 538 | /* page is already in the swap cache, ignore for now */ |
538 | page_cache_release(page); | 539 | page_cache_release(page); |
539 | ret = -EEXIST; | 540 | ret = -EEXIST; |
@@ -594,7 +595,12 @@ static int zswap_writeback_entry(struct zbud_pool *pool, unsigned long handle) | |||
594 | 595 | ||
595 | fail: | 596 | fail: |
596 | spin_lock(&tree->lock); | 597 | spin_lock(&tree->lock); |
597 | zswap_entry_put(entry); | 598 | refcount = zswap_entry_put(entry); |
599 | if (refcount <= 0) { | ||
600 | /* invalidate happened, consider writeback as success */ | ||
601 | zswap_free_entry(tree, entry); | ||
602 | ret = 0; | ||
603 | } | ||
598 | spin_unlock(&tree->lock); | 604 | spin_unlock(&tree->lock); |
599 | return ret; | 605 | return ret; |
600 | } | 606 | } |