diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-06-13 21:54:46 -0800 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-06-13 21:54:46 -0800 |
| commit | db06d759d6cf903aeda8c107fd3abd366dd80200 (patch) | |
| tree | 6e9ddea15240dcba9dff738a345297e982ae9324 | |
| parent | 35398ee3f00a4363faa1bd0fe7bb6a90f8a6b7e4 (diff) | |
| parent | 6710e594f71ccaad8101bc64321152af7cd9ea28 (diff) | |
| download | linux-stable-db06d759d6cf903aeda8c107fd3abd366dd80200.tar.gz linux-stable-db06d759d6cf903aeda8c107fd3abd366dd80200.tar.bz2 linux-stable-db06d759d6cf903aeda8c107fd3abd366dd80200.tar.xz linux-stable-db06d759d6cf903aeda8c107fd3abd366dd80200.zip | |
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
| -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); |
