diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-06-14 01:54:46 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-06-14 01:54:46 -0400 |
commit | db06d759d6cf903aeda8c107fd3abd366dd80200 (patch) | |
tree | 6e9ddea15240dcba9dff738a345297e982ae9324 /mm | |
parent | 35398ee3f00a4363faa1bd0fe7bb6a90f8a6b7e4 (diff) | |
parent | 6710e594f71ccaad8101bc64321152af7cd9ea28 (diff) |
Merge branch 'for-4.7-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/percpu
Pull percpu fixes from Tejun Heo:
"While adding GFP_ATOMIC support to the percpu allocator, the
synchronization for the fast-path which doesn't require external
allocations was separated into pcpu_lock.
Unfortunately, it incorrectly decoupled async paths and percpu
chunks could get destroyed while still being operated on. This
contains two patches to fix the bug"
* 'for-4.7-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/percpu:
percpu: fix synchronization between synchronous map extension and chunk destruction
percpu: fix synchronization between chunk->map_extend_work and chunk destruction
Diffstat (limited to 'mm')
-rw-r--r-- | mm/percpu.c | 73 |
1 files changed, 44 insertions, 29 deletions
diff --git a/mm/percpu.c b/mm/percpu.c index 0c59684f1ff2..9903830aaebb 100644 --- a/mm/percpu.c +++ b/mm/percpu.c | |||
@@ -112,7 +112,7 @@ struct pcpu_chunk { | |||
112 | int map_used; /* # of map entries used before the sentry */ | 112 | int map_used; /* # of map entries used before the sentry */ |
113 | int map_alloc; /* # of map entries allocated */ | 113 | int map_alloc; /* # of map entries allocated */ |
114 | int *map; /* allocation map */ | 114 | int *map; /* allocation map */ |
115 | struct work_struct map_extend_work;/* async ->map[] extension */ | 115 | struct list_head map_extend_list;/* on pcpu_map_extend_chunks */ |
116 | 116 | ||
117 | void *data; /* chunk data */ | 117 | void *data; /* chunk data */ |
118 | int first_free; /* no free below this */ | 118 | int first_free; /* no free below this */ |
@@ -162,10 +162,13 @@ static struct pcpu_chunk *pcpu_reserved_chunk; | |||
162 | static int pcpu_reserved_chunk_limit; | 162 | static int pcpu_reserved_chunk_limit; |
163 | 163 | ||
164 | static DEFINE_SPINLOCK(pcpu_lock); /* all internal data structures */ | 164 | static DEFINE_SPINLOCK(pcpu_lock); /* all internal data structures */ |
165 | static DEFINE_MUTEX(pcpu_alloc_mutex); /* chunk create/destroy, [de]pop */ | 165 | static DEFINE_MUTEX(pcpu_alloc_mutex); /* chunk create/destroy, [de]pop, map ext */ |
166 | 166 | ||
167 | static struct list_head *pcpu_slot __read_mostly; /* chunk list slots */ | 167 | static struct list_head *pcpu_slot __read_mostly; /* chunk list slots */ |
168 | 168 | ||
169 | /* chunks which need their map areas extended, protected by pcpu_lock */ | ||
170 | static LIST_HEAD(pcpu_map_extend_chunks); | ||
171 | |||
169 | /* | 172 | /* |
170 | * The number of empty populated pages, protected by pcpu_lock. The | 173 | * The number of empty populated pages, protected by pcpu_lock. The |
171 | * reserved chunk doesn't contribute to the count. | 174 | * reserved chunk doesn't contribute to the count. |
@@ -395,13 +398,19 @@ static int pcpu_need_to_extend(struct pcpu_chunk *chunk, bool is_atomic) | |||
395 | { | 398 | { |
396 | int margin, new_alloc; | 399 | int margin, new_alloc; |
397 | 400 | ||
401 | lockdep_assert_held(&pcpu_lock); | ||
402 | |||
398 | if (is_atomic) { | 403 | if (is_atomic) { |
399 | margin = 3; | 404 | margin = 3; |
400 | 405 | ||
401 | if (chunk->map_alloc < | 406 | if (chunk->map_alloc < |
402 | chunk->map_used + PCPU_ATOMIC_MAP_MARGIN_LOW && | 407 | chunk->map_used + PCPU_ATOMIC_MAP_MARGIN_LOW) { |
403 | pcpu_async_enabled) | 408 | if (list_empty(&chunk->map_extend_list)) { |
404 | schedule_work(&chunk->map_extend_work); | 409 | list_add_tail(&chunk->map_extend_list, |
410 | &pcpu_map_extend_chunks); | ||
411 | pcpu_schedule_balance_work(); | ||
412 | } | ||
413 | } | ||
405 | } else { | 414 | } else { |
406 | margin = PCPU_ATOMIC_MAP_MARGIN_HIGH; | 415 | margin = PCPU_ATOMIC_MAP_MARGIN_HIGH; |
407 | } | 416 | } |
@@ -435,6 +444,8 @@ static int pcpu_extend_area_map(struct pcpu_chunk *chunk, int new_alloc) | |||
435 | size_t old_size = 0, new_size = new_alloc * sizeof(new[0]); | 444 | size_t old_size = 0, new_size = new_alloc * sizeof(new[0]); |
436 | unsigned long flags; | 445 | unsigned long flags; |
437 | 446 | ||
447 | lockdep_assert_held(&pcpu_alloc_mutex); | ||
448 | |||
438 | new = pcpu_mem_zalloc(new_size); | 449 | new = pcpu_mem_zalloc(new_size); |
439 | if (!new) | 450 | if (!new) |
440 | return -ENOMEM; | 451 | return -ENOMEM; |
@@ -467,20 +478,6 @@ out_unlock: | |||
467 | return 0; | 478 | return 0; |
468 | } | 479 | } |
469 | 480 | ||
470 | static void pcpu_map_extend_workfn(struct work_struct *work) | ||
471 | { | ||
472 | struct pcpu_chunk *chunk = container_of(work, struct pcpu_chunk, | ||
473 | map_extend_work); | ||
474 | int new_alloc; | ||
475 | |||
476 | spin_lock_irq(&pcpu_lock); | ||
477 | new_alloc = pcpu_need_to_extend(chunk, false); | ||
478 | spin_unlock_irq(&pcpu_lock); | ||
479 | |||
480 | if (new_alloc) | ||
481 | pcpu_extend_area_map(chunk, new_alloc); | ||
482 | } | ||
483 | |||
484 | /** | 481 | /** |
485 | * pcpu_fit_in_area - try to fit the requested allocation in a candidate area | 482 | * pcpu_fit_in_area - try to fit the requested allocation in a candidate area |
486 | * @chunk: chunk the candidate area belongs to | 483 | * @chunk: chunk the candidate area belongs to |
@@ -740,7 +737,7 @@ static struct pcpu_chunk *pcpu_alloc_chunk(void) | |||
740 | chunk->map_used = 1; | 737 | chunk->map_used = 1; |
741 | 738 | ||
742 | INIT_LIST_HEAD(&chunk->list); | 739 | INIT_LIST_HEAD(&chunk->list); |
743 | INIT_WORK(&chunk->map_extend_work, pcpu_map_extend_workfn); | 740 | INIT_LIST_HEAD(&chunk->map_extend_list); |
744 | chunk->free_size = pcpu_unit_size; | 741 | chunk->free_size = pcpu_unit_size; |
745 | chunk->contig_hint = pcpu_unit_size; | 742 | chunk->contig_hint = pcpu_unit_size; |
746 | 743 | ||
@@ -895,6 +892,9 @@ static void __percpu *pcpu_alloc(size_t size, size_t align, bool reserved, | |||
895 | return NULL; | 892 | return NULL; |
896 | } | 893 | } |
897 | 894 | ||
895 | if (!is_atomic) | ||
896 | mutex_lock(&pcpu_alloc_mutex); | ||
897 | |||
898 | spin_lock_irqsave(&pcpu_lock, flags); | 898 | spin_lock_irqsave(&pcpu_lock, flags); |
899 | 899 | ||
900 | /* serve reserved allocations from the reserved chunk if available */ | 900 | /* serve reserved allocations from the reserved chunk if available */ |
@@ -967,12 +967,9 @@ restart: | |||
967 | if (is_atomic) | 967 | if (is_atomic) |
968 | goto fail; | 968 | goto fail; |
969 | 969 | ||
970 | mutex_lock(&pcpu_alloc_mutex); | ||
971 | |||
972 | if (list_empty(&pcpu_slot[pcpu_nr_slots - 1])) { | 970 | if (list_empty(&pcpu_slot[pcpu_nr_slots - 1])) { |
973 | chunk = pcpu_create_chunk(); | 971 | chunk = pcpu_create_chunk(); |
974 | if (!chunk) { | 972 | if (!chunk) { |
975 | mutex_unlock(&pcpu_alloc_mutex); | ||
976 | err = "failed to allocate new chunk"; | 973 | err = "failed to allocate new chunk"; |
977 | goto fail; | 974 | goto fail; |
978 | } | 975 | } |
@@ -983,7 +980,6 @@ restart: | |||
983 | spin_lock_irqsave(&pcpu_lock, flags); | 980 | spin_lock_irqsave(&pcpu_lock, flags); |
984 | } | 981 | } |
985 | 982 | ||
986 | mutex_unlock(&pcpu_alloc_mutex); | ||
987 | goto restart; | 983 | goto restart; |
988 | 984 | ||
989 | area_found: | 985 | area_found: |
@@ -993,8 +989,6 @@ area_found: | |||
993 | if (!is_atomic) { | 989 | if (!is_atomic) { |
994 | int page_start, page_end, rs, re; | 990 | int page_start, page_end, rs, re; |
995 | 991 | ||
996 | mutex_lock(&pcpu_alloc_mutex); | ||
997 | |||
998 | page_start = PFN_DOWN(off); | 992 | page_start = PFN_DOWN(off); |
999 | page_end = PFN_UP(off + size); | 993 | page_end = PFN_UP(off + size); |
1000 | 994 | ||
@@ -1005,7 +999,6 @@ area_found: | |||
1005 | 999 | ||
1006 | spin_lock_irqsave(&pcpu_lock, flags); | 1000 | spin_lock_irqsave(&pcpu_lock, flags); |
1007 | if (ret) { | 1001 | if (ret) { |
1008 | mutex_unlock(&pcpu_alloc_mutex); | ||
1009 | pcpu_free_area(chunk, off, &occ_pages); | 1002 | pcpu_free_area(chunk, off, &occ_pages); |
1010 | err = "failed to populate"; | 1003 | err = "failed to populate"; |
1011 | goto fail_unlock; | 1004 | goto fail_unlock; |
@@ -1045,6 +1038,8 @@ fail: | |||
1045 | /* see the flag handling in pcpu_blance_workfn() */ | 1038 | /* see the flag handling in pcpu_blance_workfn() */ |
1046 | pcpu_atomic_alloc_failed = true; | 1039 | pcpu_atomic_alloc_failed = true; |
1047 | pcpu_schedule_balance_work(); | 1040 | pcpu_schedule_balance_work(); |
1041 | } else { | ||
1042 | mutex_unlock(&pcpu_alloc_mutex); | ||
1048 | } | 1043 | } |
1049 | return NULL; | 1044 | return NULL; |
1050 | } | 1045 | } |
@@ -1129,6 +1124,7 @@ static void pcpu_balance_workfn(struct work_struct *work) | |||
1129 | if (chunk == list_first_entry(free_head, struct pcpu_chunk, list)) | 1124 | if (chunk == list_first_entry(free_head, struct pcpu_chunk, list)) |
1130 | continue; | 1125 | continue; |
1131 | 1126 | ||
1127 | list_del_init(&chunk->map_extend_list); | ||
1132 | list_move(&chunk->list, &to_free); | 1128 | list_move(&chunk->list, &to_free); |
1133 | } | 1129 | } |
1134 | 1130 | ||
@@ -1146,6 +1142,25 @@ static void pcpu_balance_workfn(struct work_struct *work) | |||
1146 | pcpu_destroy_chunk(chunk); | 1142 | pcpu_destroy_chunk(chunk); |
1147 | } | 1143 | } |
1148 | 1144 | ||
1145 | /* service chunks which requested async area map extension */ | ||
1146 | do { | ||
1147 | int new_alloc = 0; | ||
1148 | |||
1149 | spin_lock_irq(&pcpu_lock); | ||
1150 | |||
1151 | chunk = list_first_entry_or_null(&pcpu_map_extend_chunks, | ||
1152 | struct pcpu_chunk, map_extend_list); | ||
1153 | if (chunk) { | ||
1154 | list_del_init(&chunk->map_extend_list); | ||
1155 | new_alloc = pcpu_need_to_extend(chunk, false); | ||
1156 | } | ||
1157 | |||
1158 | spin_unlock_irq(&pcpu_lock); | ||
1159 | |||
1160 | if (new_alloc) | ||
1161 | pcpu_extend_area_map(chunk, new_alloc); | ||
1162 | } while (chunk); | ||
1163 | |||
1149 | /* | 1164 | /* |
1150 | * Ensure there are certain number of free populated pages for | 1165 | * Ensure there are certain number of free populated pages for |
1151 | * atomic allocs. Fill up from the most packed so that atomic | 1166 | * atomic allocs. Fill up from the most packed so that atomic |
@@ -1644,7 +1659,7 @@ int __init pcpu_setup_first_chunk(const struct pcpu_alloc_info *ai, | |||
1644 | */ | 1659 | */ |
1645 | schunk = memblock_virt_alloc(pcpu_chunk_struct_size, 0); | 1660 | schunk = memblock_virt_alloc(pcpu_chunk_struct_size, 0); |
1646 | INIT_LIST_HEAD(&schunk->list); | 1661 | INIT_LIST_HEAD(&schunk->list); |
1647 | INIT_WORK(&schunk->map_extend_work, pcpu_map_extend_workfn); | 1662 | INIT_LIST_HEAD(&schunk->map_extend_list); |
1648 | schunk->base_addr = base_addr; | 1663 | schunk->base_addr = base_addr; |
1649 | schunk->map = smap; | 1664 | schunk->map = smap; |
1650 | schunk->map_alloc = ARRAY_SIZE(smap); | 1665 | schunk->map_alloc = ARRAY_SIZE(smap); |
@@ -1673,7 +1688,7 @@ int __init pcpu_setup_first_chunk(const struct pcpu_alloc_info *ai, | |||
1673 | if (dyn_size) { | 1688 | if (dyn_size) { |
1674 | dchunk = memblock_virt_alloc(pcpu_chunk_struct_size, 0); | 1689 | dchunk = memblock_virt_alloc(pcpu_chunk_struct_size, 0); |
1675 | INIT_LIST_HEAD(&dchunk->list); | 1690 | INIT_LIST_HEAD(&dchunk->list); |
1676 | INIT_WORK(&dchunk->map_extend_work, pcpu_map_extend_workfn); | 1691 | INIT_LIST_HEAD(&dchunk->map_extend_list); |
1677 | dchunk->base_addr = base_addr; | 1692 | dchunk->base_addr = base_addr; |
1678 | dchunk->map = dmap; | 1693 | dchunk->map = dmap; |
1679 | dchunk->map_alloc = ARRAY_SIZE(dmap); | 1694 | dchunk->map_alloc = ARRAY_SIZE(dmap); |