mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-15 16:53:54 +08:00
5cedf721a7
The LRU_RETRY code assumes that the list traversal status after we have dropped and regained the list lock. Unfortunately, this is not a valid assumption, and that can lead to racing traversals isolating objects that the other traversal expects to be the next item on the list. This is causing problems with the inode cache shrinker isolation, with races resulting in an inode on a dispose list being "isolated" because a racing traversal still thinks it is on the LRU. The inode is then never reclaimed and that causes hangs if a subsequent lookup on that inode occurs. Fix it by always restarting the list walk on a LRU_RETRY return from the isolate callback. Avoid the possibility of livelocks the current code was trying to avoid by always decrementing the nr_to_walk counter on retries so that even if we keep hitting the same item on the list we'll eventually stop trying to walk and exit out of the situation causing the problem. Reported-by: Michal Hocko <mhocko@suse.cz> Signed-off-by: Dave Chinner <dchinner@redhat.com> Cc: Glauber Costa <glommer@gmail.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
189 lines
4.1 KiB
C
189 lines
4.1 KiB
C
/*
|
|
* Copyright (c) 2013 Red Hat, Inc. and Parallels Inc. All rights reserved.
|
|
* Authors: David Chinner and Glauber Costa
|
|
*
|
|
* Generic LRU infrastructure
|
|
*/
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/list_lru.h>
|
|
|
|
bool list_lru_add(struct list_lru *lru, struct list_head *item)
|
|
{
|
|
int nid = page_to_nid(virt_to_page(item));
|
|
struct list_lru_node *nlru = &lru->node[nid];
|
|
|
|
spin_lock(&nlru->lock);
|
|
WARN_ON_ONCE(nlru->nr_items < 0);
|
|
if (list_empty(item)) {
|
|
list_add_tail(item, &nlru->list);
|
|
if (nlru->nr_items++ == 0)
|
|
node_set(nid, lru->active_nodes);
|
|
spin_unlock(&nlru->lock);
|
|
return true;
|
|
}
|
|
spin_unlock(&nlru->lock);
|
|
return false;
|
|
}
|
|
EXPORT_SYMBOL_GPL(list_lru_add);
|
|
|
|
bool list_lru_del(struct list_lru *lru, struct list_head *item)
|
|
{
|
|
int nid = page_to_nid(virt_to_page(item));
|
|
struct list_lru_node *nlru = &lru->node[nid];
|
|
|
|
spin_lock(&nlru->lock);
|
|
if (!list_empty(item)) {
|
|
list_del_init(item);
|
|
if (--nlru->nr_items == 0)
|
|
node_clear(nid, lru->active_nodes);
|
|
WARN_ON_ONCE(nlru->nr_items < 0);
|
|
spin_unlock(&nlru->lock);
|
|
return true;
|
|
}
|
|
spin_unlock(&nlru->lock);
|
|
return false;
|
|
}
|
|
EXPORT_SYMBOL_GPL(list_lru_del);
|
|
|
|
unsigned long list_lru_count(struct list_lru *lru)
|
|
{
|
|
unsigned long count = 0;
|
|
int nid;
|
|
|
|
for_each_node_mask(nid, lru->active_nodes) {
|
|
struct list_lru_node *nlru = &lru->node[nid];
|
|
|
|
spin_lock(&nlru->lock);
|
|
WARN_ON_ONCE(nlru->nr_items < 0);
|
|
count += nlru->nr_items;
|
|
spin_unlock(&nlru->lock);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
EXPORT_SYMBOL_GPL(list_lru_count);
|
|
|
|
static unsigned long
|
|
list_lru_walk_node(struct list_lru *lru, int nid, list_lru_walk_cb isolate,
|
|
void *cb_arg, unsigned long *nr_to_walk)
|
|
{
|
|
|
|
struct list_lru_node *nlru = &lru->node[nid];
|
|
struct list_head *item, *n;
|
|
unsigned long isolated = 0;
|
|
|
|
spin_lock(&nlru->lock);
|
|
restart:
|
|
list_for_each_safe(item, n, &nlru->list) {
|
|
enum lru_status ret;
|
|
|
|
/*
|
|
* decrement nr_to_walk first so that we don't livelock if we
|
|
* get stuck on large numbesr of LRU_RETRY items
|
|
*/
|
|
if (--(*nr_to_walk) == 0)
|
|
break;
|
|
|
|
ret = isolate(item, &nlru->lock, cb_arg);
|
|
switch (ret) {
|
|
case LRU_REMOVED:
|
|
if (--nlru->nr_items == 0)
|
|
node_clear(nid, lru->active_nodes);
|
|
WARN_ON_ONCE(nlru->nr_items < 0);
|
|
isolated++;
|
|
break;
|
|
case LRU_ROTATE:
|
|
list_move_tail(item, &nlru->list);
|
|
break;
|
|
case LRU_SKIP:
|
|
break;
|
|
case LRU_RETRY:
|
|
/*
|
|
* The lru lock has been dropped, our list traversal is
|
|
* now invalid and so we have to restart from scratch.
|
|
*/
|
|
goto restart;
|
|
default:
|
|
BUG();
|
|
}
|
|
}
|
|
|
|
spin_unlock(&nlru->lock);
|
|
return isolated;
|
|
}
|
|
EXPORT_SYMBOL_GPL(list_lru_walk_node);
|
|
|
|
unsigned long list_lru_walk(struct list_lru *lru, list_lru_walk_cb isolate,
|
|
void *cb_arg, unsigned long nr_to_walk)
|
|
{
|
|
unsigned long isolated = 0;
|
|
int nid;
|
|
|
|
for_each_node_mask(nid, lru->active_nodes) {
|
|
isolated += list_lru_walk_node(lru, nid, isolate,
|
|
cb_arg, &nr_to_walk);
|
|
if (nr_to_walk <= 0)
|
|
break;
|
|
}
|
|
return isolated;
|
|
}
|
|
EXPORT_SYMBOL_GPL(list_lru_walk);
|
|
|
|
static unsigned long list_lru_dispose_all_node(struct list_lru *lru, int nid,
|
|
list_lru_dispose_cb dispose)
|
|
{
|
|
struct list_lru_node *nlru = &lru->node[nid];
|
|
LIST_HEAD(dispose_list);
|
|
unsigned long disposed = 0;
|
|
|
|
spin_lock(&nlru->lock);
|
|
while (!list_empty(&nlru->list)) {
|
|
list_splice_init(&nlru->list, &dispose_list);
|
|
disposed += nlru->nr_items;
|
|
nlru->nr_items = 0;
|
|
node_clear(nid, lru->active_nodes);
|
|
spin_unlock(&nlru->lock);
|
|
|
|
dispose(&dispose_list);
|
|
|
|
spin_lock(&nlru->lock);
|
|
}
|
|
spin_unlock(&nlru->lock);
|
|
return disposed;
|
|
}
|
|
|
|
unsigned long list_lru_dispose_all(struct list_lru *lru,
|
|
list_lru_dispose_cb dispose)
|
|
{
|
|
unsigned long disposed;
|
|
unsigned long total = 0;
|
|
int nid;
|
|
|
|
do {
|
|
disposed = 0;
|
|
for_each_node_mask(nid, lru->active_nodes) {
|
|
disposed += list_lru_dispose_all_node(lru, nid,
|
|
dispose);
|
|
}
|
|
total += disposed;
|
|
} while (disposed != 0);
|
|
|
|
return total;
|
|
}
|
|
|
|
int list_lru_init(struct list_lru *lru)
|
|
{
|
|
int i;
|
|
|
|
nodes_clear(lru->active_nodes);
|
|
for (i = 0; i < MAX_NUMNODES; i++) {
|
|
spin_lock_init(&lru->node[i].lock);
|
|
INIT_LIST_HEAD(&lru->node[i].list);
|
|
lru->node[i].nr_items = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(list_lru_init);
|