diff options
-rw-r--r-- | include/linux/list_lru.h | 23 | ||||
-rw-r--r-- | mm/list_lru.c | 146 |
2 files changed, 129 insertions, 40 deletions
diff --git a/include/linux/list_lru.h b/include/linux/list_lru.h index 1a548b0b7578..f4d4cb608c02 100644 --- a/include/linux/list_lru.h +++ b/include/linux/list_lru.h | |||
@@ -8,6 +8,7 @@ | |||
8 | #define _LRU_LIST_H | 8 | #define _LRU_LIST_H |
9 | 9 | ||
10 | #include <linux/list.h> | 10 | #include <linux/list.h> |
11 | #include <linux/nodemask.h> | ||
11 | 12 | ||
12 | /* list_lru_walk_cb has to always return one of those */ | 13 | /* list_lru_walk_cb has to always return one of those */ |
13 | enum lru_status { | 14 | enum lru_status { |
@@ -18,11 +19,26 @@ enum lru_status { | |||
18 | internally, but has to return locked. */ | 19 | internally, but has to return locked. */ |
19 | }; | 20 | }; |
20 | 21 | ||
21 | struct list_lru { | 22 | struct list_lru_node { |
22 | spinlock_t lock; | 23 | spinlock_t lock; |
23 | struct list_head list; | 24 | struct list_head list; |
24 | /* kept as signed so we can catch imbalance bugs */ | 25 | /* kept as signed so we can catch imbalance bugs */ |
25 | long nr_items; | 26 | long nr_items; |
27 | } ____cacheline_aligned_in_smp; | ||
28 | |||
29 | struct list_lru { | ||
30 | /* | ||
31 | * Because we use a fixed-size array, this struct can be very big if | ||
32 | * MAX_NUMNODES is big. If this becomes a problem this is fixable by | ||
33 | * turning this into a pointer and dynamically allocating this to | ||
34 | * nr_node_ids. This quantity is firwmare-provided, and still would | ||
35 | * provide room for all nodes at the cost of a pointer lookup and an | ||
36 | * extra allocation. Because that allocation will most likely come from | ||
37 | * a different slab cache than the main structure holding this | ||
38 | * structure, we may very well fail. | ||
39 | */ | ||
40 | struct list_lru_node node[MAX_NUMNODES]; | ||
41 | nodemask_t active_nodes; | ||
26 | }; | 42 | }; |
27 | 43 | ||
28 | int list_lru_init(struct list_lru *lru); | 44 | int list_lru_init(struct list_lru *lru); |
@@ -66,10 +82,7 @@ bool list_lru_del(struct list_lru *lru, struct list_head *item); | |||
66 | * guarantee that the list is not updated while the count is being computed. | 82 | * guarantee that the list is not updated while the count is being computed. |
67 | * Callers that want such a guarantee need to provide an outer lock. | 83 | * Callers that want such a guarantee need to provide an outer lock. |
68 | */ | 84 | */ |
69 | static inline unsigned long list_lru_count(struct list_lru *lru) | 85 | unsigned long list_lru_count(struct list_lru *lru); |
70 | { | ||
71 | return lru->nr_items; | ||
72 | } | ||
73 | 86 | ||
74 | typedef enum lru_status | 87 | typedef enum lru_status |
75 | (*list_lru_walk_cb)(struct list_head *item, spinlock_t *lock, void *cb_arg); | 88 | (*list_lru_walk_cb)(struct list_head *item, spinlock_t *lock, void *cb_arg); |
diff --git a/mm/list_lru.c b/mm/list_lru.c index dd74c5434cd8..1efe4ecc02b1 100644 --- a/mm/list_lru.c +++ b/mm/list_lru.c | |||
@@ -6,41 +6,73 @@ | |||
6 | */ | 6 | */ |
7 | #include <linux/kernel.h> | 7 | #include <linux/kernel.h> |
8 | #include <linux/module.h> | 8 | #include <linux/module.h> |
9 | #include <linux/mm.h> | ||
9 | #include <linux/list_lru.h> | 10 | #include <linux/list_lru.h> |
10 | 11 | ||
11 | bool list_lru_add(struct list_lru *lru, struct list_head *item) | 12 | bool list_lru_add(struct list_lru *lru, struct list_head *item) |
12 | { | 13 | { |
13 | spin_lock(&lru->lock); | 14 | int nid = page_to_nid(virt_to_page(item)); |
15 | struct list_lru_node *nlru = &lru->node[nid]; | ||
16 | |||
17 | spin_lock(&nlru->lock); | ||
18 | WARN_ON_ONCE(nlru->nr_items < 0); | ||
14 | if (list_empty(item)) { | 19 | if (list_empty(item)) { |
15 | list_add_tail(item, &lru->list); | 20 | list_add_tail(item, &nlru->list); |
16 | lru->nr_items++; | 21 | if (nlru->nr_items++ == 0) |
17 | spin_unlock(&lru->lock); | 22 | node_set(nid, lru->active_nodes); |
23 | spin_unlock(&nlru->lock); | ||
18 | return true; | 24 | return true; |
19 | } | 25 | } |
20 | spin_unlock(&lru->lock); | 26 | spin_unlock(&nlru->lock); |
21 | return false; | 27 | return false; |
22 | } | 28 | } |
23 | EXPORT_SYMBOL_GPL(list_lru_add); | 29 | EXPORT_SYMBOL_GPL(list_lru_add); |
24 | 30 | ||
25 | bool list_lru_del(struct list_lru *lru, struct list_head *item) | 31 | bool list_lru_del(struct list_lru *lru, struct list_head *item) |
26 | { | 32 | { |
27 | spin_lock(&lru->lock); | 33 | int nid = page_to_nid(virt_to_page(item)); |
34 | struct list_lru_node *nlru = &lru->node[nid]; | ||
35 | |||
36 | spin_lock(&nlru->lock); | ||
28 | if (!list_empty(item)) { | 37 | if (!list_empty(item)) { |
29 | list_del_init(item); | 38 | list_del_init(item); |
30 | lru->nr_items--; | 39 | if (--nlru->nr_items == 0) |
31 | spin_unlock(&lru->lock); | 40 | node_clear(nid, lru->active_nodes); |
41 | WARN_ON_ONCE(nlru->nr_items < 0); | ||
42 | spin_unlock(&nlru->lock); | ||
32 | return true; | 43 | return true; |
33 | } | 44 | } |
34 | spin_unlock(&lru->lock); | 45 | spin_unlock(&nlru->lock); |
35 | return false; | 46 | return false; |
36 | } | 47 | } |
37 | EXPORT_SYMBOL_GPL(list_lru_del); | 48 | EXPORT_SYMBOL_GPL(list_lru_del); |
38 | 49 | ||
39 | unsigned long list_lru_walk(struct list_lru *lru, list_lru_walk_cb isolate, | 50 | unsigned long list_lru_count(struct list_lru *lru) |
40 | void *cb_arg, unsigned long nr_to_walk) | ||
41 | { | 51 | { |
52 | unsigned long count = 0; | ||
53 | int nid; | ||
54 | |||
55 | for_each_node_mask(nid, lru->active_nodes) { | ||
56 | struct list_lru_node *nlru = &lru->node[nid]; | ||
57 | |||
58 | spin_lock(&nlru->lock); | ||
59 | WARN_ON_ONCE(nlru->nr_items < 0); | ||
60 | count += nlru->nr_items; | ||
61 | spin_unlock(&nlru->lock); | ||
62 | } | ||
63 | |||
64 | return count; | ||
65 | } | ||
66 | EXPORT_SYMBOL_GPL(list_lru_count); | ||
67 | |||
68 | static unsigned long | ||
69 | list_lru_walk_node(struct list_lru *lru, int nid, list_lru_walk_cb isolate, | ||
70 | void *cb_arg, unsigned long *nr_to_walk) | ||
71 | { | ||
72 | |||
73 | struct list_lru_node *nlru = &lru->node[nid]; | ||
42 | struct list_head *item, *n; | 74 | struct list_head *item, *n; |
43 | unsigned long removed = 0; | 75 | unsigned long isolated = 0; |
44 | /* | 76 | /* |
45 | * If we don't keep state of at which pass we are, we can loop at | 77 | * If we don't keep state of at which pass we are, we can loop at |
46 | * LRU_RETRY, since we have no guarantees that the caller will be able | 78 | * LRU_RETRY, since we have no guarantees that the caller will be able |
@@ -50,18 +82,20 @@ unsigned long list_lru_walk(struct list_lru *lru, list_lru_walk_cb isolate, | |||
50 | */ | 82 | */ |
51 | bool first_pass = true; | 83 | bool first_pass = true; |
52 | 84 | ||
53 | spin_lock(&lru->lock); | 85 | spin_lock(&nlru->lock); |
54 | restart: | 86 | restart: |
55 | list_for_each_safe(item, n, &lru->list) { | 87 | list_for_each_safe(item, n, &nlru->list) { |
56 | enum lru_status ret; | 88 | enum lru_status ret; |
57 | ret = isolate(item, &lru->lock, cb_arg); | 89 | ret = isolate(item, &nlru->lock, cb_arg); |
58 | switch (ret) { | 90 | switch (ret) { |
59 | case LRU_REMOVED: | 91 | case LRU_REMOVED: |
60 | lru->nr_items--; | 92 | if (--nlru->nr_items == 0) |
61 | removed++; | 93 | node_clear(nid, lru->active_nodes); |
94 | WARN_ON_ONCE(nlru->nr_items < 0); | ||
95 | isolated++; | ||
62 | break; | 96 | break; |
63 | case LRU_ROTATE: | 97 | case LRU_ROTATE: |
64 | list_move_tail(item, &lru->list); | 98 | list_move_tail(item, &nlru->list); |
65 | break; | 99 | break; |
66 | case LRU_SKIP: | 100 | case LRU_SKIP: |
67 | break; | 101 | break; |
@@ -76,42 +110,84 @@ restart: | |||
76 | BUG(); | 110 | BUG(); |
77 | } | 111 | } |
78 | 112 | ||
79 | if (nr_to_walk-- == 0) | 113 | if ((*nr_to_walk)-- == 0) |
80 | break; | 114 | break; |
81 | 115 | ||
82 | } | 116 | } |
83 | spin_unlock(&lru->lock); | 117 | |
84 | return removed; | 118 | spin_unlock(&nlru->lock); |
119 | return isolated; | ||
120 | } | ||
121 | EXPORT_SYMBOL_GPL(list_lru_walk_node); | ||
122 | |||
123 | unsigned long list_lru_walk(struct list_lru *lru, list_lru_walk_cb isolate, | ||
124 | void *cb_arg, unsigned long nr_to_walk) | ||
125 | { | ||
126 | unsigned long isolated = 0; | ||
127 | int nid; | ||
128 | |||
129 | for_each_node_mask(nid, lru->active_nodes) { | ||
130 | isolated += list_lru_walk_node(lru, nid, isolate, | ||
131 | cb_arg, &nr_to_walk); | ||
132 | if (nr_to_walk <= 0) | ||
133 | break; | ||
134 | } | ||
135 | return isolated; | ||
85 | } | 136 | } |
86 | EXPORT_SYMBOL_GPL(list_lru_walk); | 137 | EXPORT_SYMBOL_GPL(list_lru_walk); |
87 | 138 | ||
88 | unsigned long list_lru_dispose_all(struct list_lru *lru, | 139 | static unsigned long list_lru_dispose_all_node(struct list_lru *lru, int nid, |
89 | list_lru_dispose_cb dispose) | 140 | list_lru_dispose_cb dispose) |
90 | { | 141 | { |
91 | unsigned long disposed = 0; | 142 | struct list_lru_node *nlru = &lru->node[nid]; |
92 | LIST_HEAD(dispose_list); | 143 | LIST_HEAD(dispose_list); |
144 | unsigned long disposed = 0; | ||
93 | 145 | ||
94 | spin_lock(&lru->lock); | 146 | spin_lock(&nlru->lock); |
95 | while (!list_empty(&lru->list)) { | 147 | while (!list_empty(&nlru->list)) { |
96 | list_splice_init(&lru->list, &dispose_list); | 148 | list_splice_init(&nlru->list, &dispose_list); |
97 | disposed += lru->nr_items; | 149 | disposed += nlru->nr_items; |
98 | lru->nr_items = 0; | 150 | nlru->nr_items = 0; |
99 | spin_unlock(&lru->lock); | 151 | node_clear(nid, lru->active_nodes); |
152 | spin_unlock(&nlru->lock); | ||
100 | 153 | ||
101 | dispose(&dispose_list); | 154 | dispose(&dispose_list); |
102 | 155 | ||
103 | spin_lock(&lru->lock); | 156 | spin_lock(&nlru->lock); |
104 | } | 157 | } |
105 | spin_unlock(&lru->lock); | 158 | spin_unlock(&nlru->lock); |
106 | return disposed; | 159 | return disposed; |
107 | } | 160 | } |
108 | 161 | ||
162 | unsigned long list_lru_dispose_all(struct list_lru *lru, | ||
163 | list_lru_dispose_cb dispose) | ||
164 | { | ||
165 | unsigned long disposed; | ||
166 | unsigned long total = 0; | ||
167 | int nid; | ||
168 | |||
169 | do { | ||
170 | disposed = 0; | ||
171 | for_each_node_mask(nid, lru->active_nodes) { | ||
172 | disposed += list_lru_dispose_all_node(lru, nid, | ||
173 | dispose); | ||
174 | } | ||
175 | total += disposed; | ||
176 | } while (disposed != 0); | ||
177 | |||
178 | return total; | ||
179 | } | ||
180 | |||
109 | int list_lru_init(struct list_lru *lru) | 181 | int list_lru_init(struct list_lru *lru) |
110 | { | 182 | { |
111 | spin_lock_init(&lru->lock); | 183 | int i; |
112 | INIT_LIST_HEAD(&lru->list); | ||
113 | lru->nr_items = 0; | ||
114 | 184 | ||
185 | nodes_clear(lru->active_nodes); | ||
186 | for (i = 0; i < MAX_NUMNODES; i++) { | ||
187 | spin_lock_init(&lru->node[i].lock); | ||
188 | INIT_LIST_HEAD(&lru->node[i].list); | ||
189 | lru->node[i].nr_items = 0; | ||
190 | } | ||
115 | return 0; | 191 | return 0; |
116 | } | 192 | } |
117 | EXPORT_SYMBOL_GPL(list_lru_init); | 193 | EXPORT_SYMBOL_GPL(list_lru_init); |