Skip to content

Commit

Permalink
z3fold: fix possible reclaim races
Browse files Browse the repository at this point in the history
Reclaim and free can race on an object which is basically fine but in
order for reclaim to be able to map "freed" object we need to encode
object length in the handle.  handle_to_chunks() is then introduced to
extract object length from a handle and use it during mapping.

Moreover, to avoid racing on a z3fold "headless" page release, we should
not try to free that page in z3fold_free() if the reclaim bit is set.
Also, in the unlikely case of trying to reclaim a page being freed, we
should not proceed with that page.

While at it, fix the page accounting in reclaim function.

This patch supersedes "[PATCH] z3fold: fix reclaim lock-ups".

Link: http://lkml.kernel.org/r/20181105162225.74e8837d03583a9b707cf559@gmail.com
Signed-off-by: Vitaly Wool <vitaly.vul@sony.com>
Signed-off-by: Jongseok Kim <ks77sj@gmail.com>
Reported-by-by: Jongseok Kim <ks77sj@gmail.com>
Reviewed-by: Snild Dolkow <snild@sony.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
  • Loading branch information
vwool authored and torvalds committed Nov 18, 2018
1 parent 1ce80e0 commit ca0246b
Showing 1 changed file with 62 additions and 39 deletions.
101 changes: 62 additions & 39 deletions mm/z3fold.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ struct z3fold_header {
#define NCHUNKS ((PAGE_SIZE - ZHDR_SIZE_ALIGNED) >> CHUNK_SHIFT)

#define BUDDY_MASK (0x3)
#define BUDDY_SHIFT 2

/**
* struct z3fold_pool - stores metadata for each z3fold pool
Expand Down Expand Up @@ -145,7 +146,7 @@ enum z3fold_page_flags {
MIDDLE_CHUNK_MAPPED,
NEEDS_COMPACTING,
PAGE_STALE,
UNDER_RECLAIM
PAGE_CLAIMED, /* by either reclaim or free */
};

/*****************
Expand Down Expand Up @@ -174,7 +175,7 @@ static struct z3fold_header *init_z3fold_page(struct page *page,
clear_bit(MIDDLE_CHUNK_MAPPED, &page->private);
clear_bit(NEEDS_COMPACTING, &page->private);
clear_bit(PAGE_STALE, &page->private);
clear_bit(UNDER_RECLAIM, &page->private);
clear_bit(PAGE_CLAIMED, &page->private);

spin_lock_init(&zhdr->page_lock);
kref_init(&zhdr->refcount);
Expand Down Expand Up @@ -223,8 +224,11 @@ static unsigned long encode_handle(struct z3fold_header *zhdr, enum buddy bud)
unsigned long handle;

handle = (unsigned long)zhdr;
if (bud != HEADLESS)
handle += (bud + zhdr->first_num) & BUDDY_MASK;
if (bud != HEADLESS) {
handle |= (bud + zhdr->first_num) & BUDDY_MASK;
if (bud == LAST)
handle |= (zhdr->last_chunks << BUDDY_SHIFT);
}
return handle;
}

Expand All @@ -234,6 +238,12 @@ static struct z3fold_header *handle_to_z3fold_header(unsigned long handle)
return (struct z3fold_header *)(handle & PAGE_MASK);
}

/* only for LAST bud, returns zero otherwise */
static unsigned short handle_to_chunks(unsigned long handle)
{
return (handle & ~PAGE_MASK) >> BUDDY_SHIFT;
}

/*
* (handle & BUDDY_MASK) < zhdr->first_num is possible in encode_handle
* but that doesn't matter. because the masking will result in the
Expand Down Expand Up @@ -720,45 +730,47 @@ static void z3fold_free(struct z3fold_pool *pool, unsigned long handle)
page = virt_to_page(zhdr);

if (test_bit(PAGE_HEADLESS, &page->private)) {
/* HEADLESS page stored */
bud = HEADLESS;
} else {
z3fold_page_lock(zhdr);
bud = handle_to_buddy(handle);

switch (bud) {
case FIRST:
zhdr->first_chunks = 0;
break;
case MIDDLE:
zhdr->middle_chunks = 0;
zhdr->start_middle = 0;
break;
case LAST:
zhdr->last_chunks = 0;
break;
default:
pr_err("%s: unknown bud %d\n", __func__, bud);
WARN_ON(1);
z3fold_page_unlock(zhdr);
return;
/* if a headless page is under reclaim, just leave.
* NB: we use test_and_set_bit for a reason: if the bit
* has not been set before, we release this page
* immediately so we don't care about its value any more.
*/
if (!test_and_set_bit(PAGE_CLAIMED, &page->private)) {
spin_lock(&pool->lock);
list_del(&page->lru);
spin_unlock(&pool->lock);
free_z3fold_page(page);
atomic64_dec(&pool->pages_nr);
}
return;
}

if (bud == HEADLESS) {
spin_lock(&pool->lock);
list_del(&page->lru);
spin_unlock(&pool->lock);
free_z3fold_page(page);
atomic64_dec(&pool->pages_nr);
/* Non-headless case */
z3fold_page_lock(zhdr);
bud = handle_to_buddy(handle);

switch (bud) {
case FIRST:
zhdr->first_chunks = 0;
break;
case MIDDLE:
zhdr->middle_chunks = 0;
break;
case LAST:
zhdr->last_chunks = 0;
break;
default:
pr_err("%s: unknown bud %d\n", __func__, bud);
WARN_ON(1);
z3fold_page_unlock(zhdr);
return;
}

if (kref_put(&zhdr->refcount, release_z3fold_page_locked_list)) {
atomic64_dec(&pool->pages_nr);
return;
}
if (test_bit(UNDER_RECLAIM, &page->private)) {
if (test_bit(PAGE_CLAIMED, &page->private)) {
z3fold_page_unlock(zhdr);
return;
}
Expand Down Expand Up @@ -836,20 +848,30 @@ static int z3fold_reclaim_page(struct z3fold_pool *pool, unsigned int retries)
}
list_for_each_prev(pos, &pool->lru) {
page = list_entry(pos, struct page, lru);

/* this bit could have been set by free, in which case
* we pass over to the next page in the pool.
*/
if (test_and_set_bit(PAGE_CLAIMED, &page->private))
continue;

zhdr = page_address(page);
if (test_bit(PAGE_HEADLESS, &page->private))
/* candidate found */
break;

zhdr = page_address(page);
if (!z3fold_page_trylock(zhdr))
if (!z3fold_page_trylock(zhdr)) {
zhdr = NULL;
continue; /* can't evict at this point */
}
kref_get(&zhdr->refcount);
list_del_init(&zhdr->buddy);
zhdr->cpu = -1;
set_bit(UNDER_RECLAIM, &page->private);
break;
}

if (!zhdr)
break;

list_del_init(&page->lru);
spin_unlock(&pool->lock);

Expand Down Expand Up @@ -898,14 +920,15 @@ static int z3fold_reclaim_page(struct z3fold_pool *pool, unsigned int retries)
if (test_bit(PAGE_HEADLESS, &page->private)) {
if (ret == 0) {
free_z3fold_page(page);
atomic64_dec(&pool->pages_nr);
return 0;
}
spin_lock(&pool->lock);
list_add(&page->lru, &pool->lru);
spin_unlock(&pool->lock);
} else {
z3fold_page_lock(zhdr);
clear_bit(UNDER_RECLAIM, &page->private);
clear_bit(PAGE_CLAIMED, &page->private);
if (kref_put(&zhdr->refcount,
release_z3fold_page_locked)) {
atomic64_dec(&pool->pages_nr);
Expand Down Expand Up @@ -964,7 +987,7 @@ static void *z3fold_map(struct z3fold_pool *pool, unsigned long handle)
set_bit(MIDDLE_CHUNK_MAPPED, &page->private);
break;
case LAST:
addr += PAGE_SIZE - (zhdr->last_chunks << CHUNK_SHIFT);
addr += PAGE_SIZE - (handle_to_chunks(handle) << CHUNK_SHIFT);
break;
default:
pr_err("unknown buddy id %d\n", buddy);
Expand Down

0 comments on commit ca0246b

Please sign in to comment.