mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 14:11:52 +00:00
mm: workingset: fix crash in shadow node shrinker caused by replace_page_cache_page()
Antonio reports the following crash when using fuse under memory pressure:
kernel BUG at /build/linux-a2WvEb/linux-4.4.0/mm/workingset.c:346!
invalid opcode: 0000 [#1] SMP
Modules linked in: all of them
CPU: 2 PID: 63 Comm: kswapd0 Not tainted 4.4.0-36-generic #55-Ubuntu
Hardware name: System manufacturer System Product Name/P8H67-M PRO, BIOS 3904 04/27/2013
task: ffff88040cae6040 ti: ffff880407488000 task.ti: ffff880407488000
RIP: shadow_lru_isolate+0x181/0x190
Call Trace:
__list_lru_walk_one.isra.3+0x8f/0x130
list_lru_walk_one+0x23/0x30
scan_shadow_nodes+0x34/0x50
shrink_slab.part.40+0x1ed/0x3d0
shrink_zone+0x2ca/0x2e0
kswapd+0x51e/0x990
kthread+0xd8/0xf0
ret_from_fork+0x3f/0x70
which corresponds to the following sanity check in the shadow node
tracking:
BUG_ON(node->count & RADIX_TREE_COUNT_MASK);
The workingset code tracks radix tree nodes that exclusively contain
shadow entries of evicted pages in them, and this (somewhat obscure)
line checks whether there are real pages left that would interfere with
reclaim of the radix tree node under memory pressure.
While discussing ways how fuse might sneak pages into the radix tree
past the workingset code, Miklos pointed to replace_page_cache_page(),
and indeed there is a problem there: it properly accounts for the old
page being removed - __delete_from_page_cache() does that - but then
does a raw raw radix_tree_insert(), not accounting for the replacement
page. Eventually the page count bits in node->count underflow while
leaving the node incorrectly linked to the shadow node LRU.
To address this, make sure replace_page_cache_page() uses the tracked
page insertion code, page_cache_tree_insert(). This fixes the page
accounting and makes sure page-containing nodes are properly unlinked
from the shadow node LRU again.
Also, make the sanity checks a bit less obscure by using the helpers for
checking the number of pages and shadows in a radix tree node.
Fixes: 449dd6984d
("mm: keep page cache radix tree nodes in check")
Link: http://lkml.kernel.org/r/20160919155822.29498-1-hannes@cmpxchg.org
Signed-off-by: Johannes Weiner <hannes@cmpxchg.org>
Reported-by: Antonio SJ Musumeci <trapexit@spawn.link>
Debugged-by: Miklos Szeredi <miklos@szeredi.hu>
Cc: <stable@vger.kernel.org> [3.15+]
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
e3b3656ca6
commit
22f2ac51b6
@ -257,6 +257,7 @@ static inline void workingset_node_pages_inc(struct radix_tree_node *node)
|
||||
|
||||
static inline void workingset_node_pages_dec(struct radix_tree_node *node)
|
||||
{
|
||||
VM_BUG_ON(!workingset_node_pages(node));
|
||||
node->count--;
|
||||
}
|
||||
|
||||
@ -272,6 +273,7 @@ static inline void workingset_node_shadows_inc(struct radix_tree_node *node)
|
||||
|
||||
static inline void workingset_node_shadows_dec(struct radix_tree_node *node)
|
||||
{
|
||||
VM_BUG_ON(!workingset_node_shadows(node));
|
||||
node->count -= 1U << RADIX_TREE_COUNT_SHIFT;
|
||||
}
|
||||
|
||||
|
114
mm/filemap.c
114
mm/filemap.c
@ -110,6 +110,62 @@
|
||||
* ->tasklist_lock (memory_failure, collect_procs_ao)
|
||||
*/
|
||||
|
||||
static int page_cache_tree_insert(struct address_space *mapping,
|
||||
struct page *page, void **shadowp)
|
||||
{
|
||||
struct radix_tree_node *node;
|
||||
void **slot;
|
||||
int error;
|
||||
|
||||
error = __radix_tree_create(&mapping->page_tree, page->index, 0,
|
||||
&node, &slot);
|
||||
if (error)
|
||||
return error;
|
||||
if (*slot) {
|
||||
void *p;
|
||||
|
||||
p = radix_tree_deref_slot_protected(slot, &mapping->tree_lock);
|
||||
if (!radix_tree_exceptional_entry(p))
|
||||
return -EEXIST;
|
||||
|
||||
mapping->nrexceptional--;
|
||||
if (!dax_mapping(mapping)) {
|
||||
if (shadowp)
|
||||
*shadowp = p;
|
||||
if (node)
|
||||
workingset_node_shadows_dec(node);
|
||||
} else {
|
||||
/* DAX can replace empty locked entry with a hole */
|
||||
WARN_ON_ONCE(p !=
|
||||
(void *)(RADIX_TREE_EXCEPTIONAL_ENTRY |
|
||||
RADIX_DAX_ENTRY_LOCK));
|
||||
/* DAX accounts exceptional entries as normal pages */
|
||||
if (node)
|
||||
workingset_node_pages_dec(node);
|
||||
/* Wakeup waiters for exceptional entry lock */
|
||||
dax_wake_mapping_entry_waiter(mapping, page->index,
|
||||
false);
|
||||
}
|
||||
}
|
||||
radix_tree_replace_slot(slot, page);
|
||||
mapping->nrpages++;
|
||||
if (node) {
|
||||
workingset_node_pages_inc(node);
|
||||
/*
|
||||
* Don't track node that contains actual pages.
|
||||
*
|
||||
* Avoid acquiring the list_lru lock if already
|
||||
* untracked. The list_empty() test is safe as
|
||||
* node->private_list is protected by
|
||||
* mapping->tree_lock.
|
||||
*/
|
||||
if (!list_empty(&node->private_list))
|
||||
list_lru_del(&workingset_shadow_nodes,
|
||||
&node->private_list);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void page_cache_tree_delete(struct address_space *mapping,
|
||||
struct page *page, void *shadow)
|
||||
{
|
||||
@ -561,7 +617,7 @@ int replace_page_cache_page(struct page *old, struct page *new, gfp_t gfp_mask)
|
||||
|
||||
spin_lock_irqsave(&mapping->tree_lock, flags);
|
||||
__delete_from_page_cache(old, NULL);
|
||||
error = radix_tree_insert(&mapping->page_tree, offset, new);
|
||||
error = page_cache_tree_insert(mapping, new, NULL);
|
||||
BUG_ON(error);
|
||||
mapping->nrpages++;
|
||||
|
||||
@ -584,62 +640,6 @@ int replace_page_cache_page(struct page *old, struct page *new, gfp_t gfp_mask)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(replace_page_cache_page);
|
||||
|
||||
static int page_cache_tree_insert(struct address_space *mapping,
|
||||
struct page *page, void **shadowp)
|
||||
{
|
||||
struct radix_tree_node *node;
|
||||
void **slot;
|
||||
int error;
|
||||
|
||||
error = __radix_tree_create(&mapping->page_tree, page->index, 0,
|
||||
&node, &slot);
|
||||
if (error)
|
||||
return error;
|
||||
if (*slot) {
|
||||
void *p;
|
||||
|
||||
p = radix_tree_deref_slot_protected(slot, &mapping->tree_lock);
|
||||
if (!radix_tree_exceptional_entry(p))
|
||||
return -EEXIST;
|
||||
|
||||
mapping->nrexceptional--;
|
||||
if (!dax_mapping(mapping)) {
|
||||
if (shadowp)
|
||||
*shadowp = p;
|
||||
if (node)
|
||||
workingset_node_shadows_dec(node);
|
||||
} else {
|
||||
/* DAX can replace empty locked entry with a hole */
|
||||
WARN_ON_ONCE(p !=
|
||||
(void *)(RADIX_TREE_EXCEPTIONAL_ENTRY |
|
||||
RADIX_DAX_ENTRY_LOCK));
|
||||
/* DAX accounts exceptional entries as normal pages */
|
||||
if (node)
|
||||
workingset_node_pages_dec(node);
|
||||
/* Wakeup waiters for exceptional entry lock */
|
||||
dax_wake_mapping_entry_waiter(mapping, page->index,
|
||||
false);
|
||||
}
|
||||
}
|
||||
radix_tree_replace_slot(slot, page);
|
||||
mapping->nrpages++;
|
||||
if (node) {
|
||||
workingset_node_pages_inc(node);
|
||||
/*
|
||||
* Don't track node that contains actual pages.
|
||||
*
|
||||
* Avoid acquiring the list_lru lock if already
|
||||
* untracked. The list_empty() test is safe as
|
||||
* node->private_list is protected by
|
||||
* mapping->tree_lock.
|
||||
*/
|
||||
if (!list_empty(&node->private_list))
|
||||
list_lru_del(&workingset_shadow_nodes,
|
||||
&node->private_list);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __add_to_page_cache_locked(struct page *page,
|
||||
struct address_space *mapping,
|
||||
pgoff_t offset, gfp_t gfp_mask,
|
||||
|
@ -418,21 +418,19 @@ static enum lru_status shadow_lru_isolate(struct list_head *item,
|
||||
* no pages, so we expect to be able to remove them all and
|
||||
* delete and free the empty node afterwards.
|
||||
*/
|
||||
|
||||
BUG_ON(!node->count);
|
||||
BUG_ON(node->count & RADIX_TREE_COUNT_MASK);
|
||||
BUG_ON(!workingset_node_shadows(node));
|
||||
BUG_ON(workingset_node_pages(node));
|
||||
|
||||
for (i = 0; i < RADIX_TREE_MAP_SIZE; i++) {
|
||||
if (node->slots[i]) {
|
||||
BUG_ON(!radix_tree_exceptional_entry(node->slots[i]));
|
||||
node->slots[i] = NULL;
|
||||
BUG_ON(node->count < (1U << RADIX_TREE_COUNT_SHIFT));
|
||||
node->count -= 1U << RADIX_TREE_COUNT_SHIFT;
|
||||
workingset_node_shadows_dec(node);
|
||||
BUG_ON(!mapping->nrexceptional);
|
||||
mapping->nrexceptional--;
|
||||
}
|
||||
}
|
||||
BUG_ON(node->count);
|
||||
BUG_ON(workingset_node_shadows(node));
|
||||
inc_node_state(page_pgdat(virt_to_page(node)), WORKINGSET_NODERECLAIM);
|
||||
if (!__radix_tree_delete_node(&mapping->page_tree, node))
|
||||
BUG();
|
||||
|
Loading…
Reference in New Issue
Block a user