diff options
-rw-r--r-- | net/core/flow.c | 100 |
1 files changed, 69 insertions, 31 deletions
diff --git a/net/core/flow.c b/net/core/flow.c index 521df52a77d2..161900674009 100644 --- a/net/core/flow.c +++ b/net/core/flow.c | |||
@@ -26,7 +26,10 @@ | |||
26 | #include <linux/security.h> | 26 | #include <linux/security.h> |
27 | 27 | ||
28 | struct flow_cache_entry { | 28 | struct flow_cache_entry { |
29 | struct flow_cache_entry *next; | 29 | union { |
30 | struct hlist_node hlist; | ||
31 | struct list_head gc_list; | ||
32 | } u; | ||
30 | u16 family; | 33 | u16 family; |
31 | u8 dir; | 34 | u8 dir; |
32 | u32 genid; | 35 | u32 genid; |
@@ -35,7 +38,7 @@ struct flow_cache_entry { | |||
35 | }; | 38 | }; |
36 | 39 | ||
37 | struct flow_cache_percpu { | 40 | struct flow_cache_percpu { |
38 | struct flow_cache_entry **hash_table; | 41 | struct hlist_head *hash_table; |
39 | int hash_count; | 42 | int hash_count; |
40 | u32 hash_rnd; | 43 | u32 hash_rnd; |
41 | int hash_rnd_recalc; | 44 | int hash_rnd_recalc; |
@@ -62,6 +65,9 @@ atomic_t flow_cache_genid = ATOMIC_INIT(0); | |||
62 | static struct flow_cache flow_cache_global; | 65 | static struct flow_cache flow_cache_global; |
63 | static struct kmem_cache *flow_cachep; | 66 | static struct kmem_cache *flow_cachep; |
64 | 67 | ||
68 | static DEFINE_SPINLOCK(flow_cache_gc_lock); | ||
69 | static LIST_HEAD(flow_cache_gc_list); | ||
70 | |||
65 | #define flow_cache_hash_size(cache) (1 << (cache)->hash_shift) | 71 | #define flow_cache_hash_size(cache) (1 << (cache)->hash_shift) |
66 | #define FLOW_HASH_RND_PERIOD (10 * 60 * HZ) | 72 | #define FLOW_HASH_RND_PERIOD (10 * 60 * HZ) |
67 | 73 | ||
@@ -86,38 +92,66 @@ static int flow_entry_valid(struct flow_cache_entry *fle) | |||
86 | return 1; | 92 | return 1; |
87 | } | 93 | } |
88 | 94 | ||
89 | static void flow_entry_kill(struct flow_cache *fc, | 95 | static void flow_entry_kill(struct flow_cache_entry *fle) |
90 | struct flow_cache_percpu *fcp, | ||
91 | struct flow_cache_entry *fle) | ||
92 | { | 96 | { |
93 | if (fle->object) | 97 | if (fle->object) |
94 | fle->object->ops->delete(fle->object); | 98 | fle->object->ops->delete(fle->object); |
95 | kmem_cache_free(flow_cachep, fle); | 99 | kmem_cache_free(flow_cachep, fle); |
96 | fcp->hash_count--; | 100 | } |
101 | |||
102 | static void flow_cache_gc_task(struct work_struct *work) | ||
103 | { | ||
104 | struct list_head gc_list; | ||
105 | struct flow_cache_entry *fce, *n; | ||
106 | |||
107 | INIT_LIST_HEAD(&gc_list); | ||
108 | spin_lock_bh(&flow_cache_gc_lock); | ||
109 | list_splice_tail_init(&flow_cache_gc_list, &gc_list); | ||
110 | spin_unlock_bh(&flow_cache_gc_lock); | ||
111 | |||
112 | list_for_each_entry_safe(fce, n, &gc_list, u.gc_list) | ||
113 | flow_entry_kill(fce); | ||
114 | } | ||
115 | static DECLARE_WORK(flow_cache_gc_work, flow_cache_gc_task); | ||
116 | |||
117 | static void flow_cache_queue_garbage(struct flow_cache_percpu *fcp, | ||
118 | int deleted, struct list_head *gc_list) | ||
119 | { | ||
120 | if (deleted) { | ||
121 | fcp->hash_count -= deleted; | ||
122 | spin_lock_bh(&flow_cache_gc_lock); | ||
123 | list_splice_tail(gc_list, &flow_cache_gc_list); | ||
124 | spin_unlock_bh(&flow_cache_gc_lock); | ||
125 | schedule_work(&flow_cache_gc_work); | ||
126 | } | ||
97 | } | 127 | } |
98 | 128 | ||
99 | static void __flow_cache_shrink(struct flow_cache *fc, | 129 | static void __flow_cache_shrink(struct flow_cache *fc, |
100 | struct flow_cache_percpu *fcp, | 130 | struct flow_cache_percpu *fcp, |
101 | int shrink_to) | 131 | int shrink_to) |
102 | { | 132 | { |
103 | struct flow_cache_entry *fle, **flp; | 133 | struct flow_cache_entry *fle; |
104 | int i; | 134 | struct hlist_node *entry, *tmp; |
135 | LIST_HEAD(gc_list); | ||
136 | int i, deleted = 0; | ||
105 | 137 | ||
106 | for (i = 0; i < flow_cache_hash_size(fc); i++) { | 138 | for (i = 0; i < flow_cache_hash_size(fc); i++) { |
107 | int saved = 0; | 139 | int saved = 0; |
108 | 140 | ||
109 | flp = &fcp->hash_table[i]; | 141 | hlist_for_each_entry_safe(fle, entry, tmp, |
110 | while ((fle = *flp) != NULL) { | 142 | &fcp->hash_table[i], u.hlist) { |
111 | if (saved < shrink_to && | 143 | if (saved < shrink_to && |
112 | flow_entry_valid(fle)) { | 144 | flow_entry_valid(fle)) { |
113 | saved++; | 145 | saved++; |
114 | flp = &fle->next; | ||
115 | } else { | 146 | } else { |
116 | *flp = fle->next; | 147 | deleted++; |
117 | flow_entry_kill(fc, fcp, fle); | 148 | hlist_del(&fle->u.hlist); |
149 | list_add_tail(&fle->u.gc_list, &gc_list); | ||
118 | } | 150 | } |
119 | } | 151 | } |
120 | } | 152 | } |
153 | |||
154 | flow_cache_queue_garbage(fcp, deleted, &gc_list); | ||
121 | } | 155 | } |
122 | 156 | ||
123 | static void flow_cache_shrink(struct flow_cache *fc, | 157 | static void flow_cache_shrink(struct flow_cache *fc, |
@@ -182,7 +216,8 @@ flow_cache_lookup(struct net *net, struct flowi *key, u16 family, u8 dir, | |||
182 | { | 216 | { |
183 | struct flow_cache *fc = &flow_cache_global; | 217 | struct flow_cache *fc = &flow_cache_global; |
184 | struct flow_cache_percpu *fcp; | 218 | struct flow_cache_percpu *fcp; |
185 | struct flow_cache_entry *fle, **head; | 219 | struct flow_cache_entry *fle, *tfle; |
220 | struct hlist_node *entry; | ||
186 | struct flow_cache_object *flo; | 221 | struct flow_cache_object *flo; |
187 | unsigned int hash; | 222 | unsigned int hash; |
188 | 223 | ||
@@ -200,12 +235,13 @@ flow_cache_lookup(struct net *net, struct flowi *key, u16 family, u8 dir, | |||
200 | flow_new_hash_rnd(fc, fcp); | 235 | flow_new_hash_rnd(fc, fcp); |
201 | 236 | ||
202 | hash = flow_hash_code(fc, fcp, key); | 237 | hash = flow_hash_code(fc, fcp, key); |
203 | head = &fcp->hash_table[hash]; | 238 | hlist_for_each_entry(tfle, entry, &fcp->hash_table[hash], u.hlist) { |
204 | for (fle = *head; fle; fle = fle->next) { | 239 | if (tfle->family == family && |
205 | if (fle->family == family && | 240 | tfle->dir == dir && |
206 | fle->dir == dir && | 241 | flow_key_compare(key, &tfle->key) == 0) { |
207 | flow_key_compare(key, &fle->key) == 0) | 242 | fle = tfle; |
208 | break; | 243 | break; |
244 | } | ||
209 | } | 245 | } |
210 | 246 | ||
211 | if (unlikely(!fle)) { | 247 | if (unlikely(!fle)) { |
@@ -214,12 +250,11 @@ flow_cache_lookup(struct net *net, struct flowi *key, u16 family, u8 dir, | |||
214 | 250 | ||
215 | fle = kmem_cache_alloc(flow_cachep, GFP_ATOMIC); | 251 | fle = kmem_cache_alloc(flow_cachep, GFP_ATOMIC); |
216 | if (fle) { | 252 | if (fle) { |
217 | fle->next = *head; | ||
218 | *head = fle; | ||
219 | fle->family = family; | 253 | fle->family = family; |
220 | fle->dir = dir; | 254 | fle->dir = dir; |
221 | memcpy(&fle->key, key, sizeof(*key)); | 255 | memcpy(&fle->key, key, sizeof(*key)); |
222 | fle->object = NULL; | 256 | fle->object = NULL; |
257 | hlist_add_head(&fle->u.hlist, &fcp->hash_table[hash]); | ||
223 | fcp->hash_count++; | 258 | fcp->hash_count++; |
224 | } | 259 | } |
225 | } else if (likely(fle->genid == atomic_read(&flow_cache_genid))) { | 260 | } else if (likely(fle->genid == atomic_read(&flow_cache_genid))) { |
@@ -262,23 +297,26 @@ static void flow_cache_flush_tasklet(unsigned long data) | |||
262 | struct flow_flush_info *info = (void *)data; | 297 | struct flow_flush_info *info = (void *)data; |
263 | struct flow_cache *fc = info->cache; | 298 | struct flow_cache *fc = info->cache; |
264 | struct flow_cache_percpu *fcp; | 299 | struct flow_cache_percpu *fcp; |
265 | int i; | 300 | struct flow_cache_entry *fle; |
301 | struct hlist_node *entry, *tmp; | ||
302 | LIST_HEAD(gc_list); | ||
303 | int i, deleted = 0; | ||
266 | 304 | ||
267 | fcp = per_cpu_ptr(fc->percpu, smp_processor_id()); | 305 | fcp = per_cpu_ptr(fc->percpu, smp_processor_id()); |
268 | for (i = 0; i < flow_cache_hash_size(fc); i++) { | 306 | for (i = 0; i < flow_cache_hash_size(fc); i++) { |
269 | struct flow_cache_entry *fle; | 307 | hlist_for_each_entry_safe(fle, entry, tmp, |
270 | 308 | &fcp->hash_table[i], u.hlist) { | |
271 | fle = fcp->hash_table[i]; | ||
272 | for (; fle; fle = fle->next) { | ||
273 | if (flow_entry_valid(fle)) | 309 | if (flow_entry_valid(fle)) |
274 | continue; | 310 | continue; |
275 | 311 | ||
276 | if (fle->object) | 312 | deleted++; |
277 | fle->object->ops->delete(fle->object); | 313 | hlist_del(&fle->u.hlist); |
278 | fle->object = NULL; | 314 | list_add_tail(&fle->u.gc_list, &gc_list); |
279 | } | 315 | } |
280 | } | 316 | } |
281 | 317 | ||
318 | flow_cache_queue_garbage(fcp, deleted, &gc_list); | ||
319 | |||
282 | if (atomic_dec_and_test(&info->cpuleft)) | 320 | if (atomic_dec_and_test(&info->cpuleft)) |
283 | complete(&info->completion); | 321 | complete(&info->completion); |
284 | } | 322 | } |
@@ -320,7 +358,7 @@ void flow_cache_flush(void) | |||
320 | static void __init flow_cache_cpu_prepare(struct flow_cache *fc, | 358 | static void __init flow_cache_cpu_prepare(struct flow_cache *fc, |
321 | struct flow_cache_percpu *fcp) | 359 | struct flow_cache_percpu *fcp) |
322 | { | 360 | { |
323 | fcp->hash_table = (struct flow_cache_entry **) | 361 | fcp->hash_table = (struct hlist_head *) |
324 | __get_free_pages(GFP_KERNEL|__GFP_ZERO, fc->order); | 362 | __get_free_pages(GFP_KERNEL|__GFP_ZERO, fc->order); |
325 | if (!fcp->hash_table) | 363 | if (!fcp->hash_table) |
326 | panic("NET: failed to allocate flow cache order %lu\n", fc->order); | 364 | panic("NET: failed to allocate flow cache order %lu\n", fc->order); |
@@ -354,7 +392,7 @@ static int flow_cache_init(struct flow_cache *fc) | |||
354 | 392 | ||
355 | for (order = 0; | 393 | for (order = 0; |
356 | (PAGE_SIZE << order) < | 394 | (PAGE_SIZE << order) < |
357 | (sizeof(struct flow_cache_entry *)*flow_cache_hash_size(fc)); | 395 | (sizeof(struct hlist_head)*flow_cache_hash_size(fc)); |
358 | order++) | 396 | order++) |
359 | /* NOTHING */; | 397 | /* NOTHING */; |
360 | fc->order = order; | 398 | fc->order = order; |