Skip to content

Commit b38d08f

Browse files
committed
percpu: restructure locking
At first, the percpu allocator required a sleepable context for both alloc and free paths and used pcpu_alloc_mutex to protect everything. Later, pcpu_lock was introduced to protect the index data structure so that the free path can be invoked from atomic contexts. The conversion only updated what's necessary and left most of the allocation path under pcpu_alloc_mutex. The percpu allocator is planned to add support for atomic allocation and this patch restructures locking so that the coverage of pcpu_alloc_mutex is further reduced. * pcpu_alloc() now grab pcpu_alloc_mutex only while creating a new chunk and populating the allocated area. Everything else is now protected soley by pcpu_lock. After this change, multiple instances of pcpu_extend_area_map() may race but the function already implements sufficient synchronization using pcpu_lock. This also allows multiple allocators to arrive at new chunk creation. To avoid creating multiple empty chunks back-to-back, a new chunk is created iff there is no other empty chunk after grabbing pcpu_alloc_mutex. * pcpu_lock is now held while modifying chunk->populated bitmap. After this, all data structures are protected by pcpu_lock. Signed-off-by: Tejun Heo <[email protected]>
1 parent a63d4ac commit b38d08f

File tree

2 files changed

+37
-40
lines changed

2 files changed

+37
-40
lines changed

mm/percpu-km.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ static struct pcpu_chunk *pcpu_create_chunk(void)
6868
chunk->data = pages;
6969
chunk->base_addr = page_address(pages) - pcpu_group_offsets[0];
7070

71+
spin_lock_irq(&pcpu_lock);
7172
bitmap_fill(chunk->populated, nr_pages);
73+
spin_unlock_irq(&pcpu_lock);
7274

7375
return chunk;
7476
}

mm/percpu.c

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -152,31 +152,12 @@ static struct pcpu_chunk *pcpu_reserved_chunk;
152152
static int pcpu_reserved_chunk_limit;
153153

154154
/*
155-
* Synchronization rules.
156-
*
157-
* There are two locks - pcpu_alloc_mutex and pcpu_lock. The former
158-
* protects allocation/reclaim paths, chunks, populated bitmap and
159-
* vmalloc mapping. The latter is a spinlock and protects the index
160-
* data structures - chunk slots, chunks and area maps in chunks.
161-
*
162-
* During allocation, pcpu_alloc_mutex is kept locked all the time and
163-
* pcpu_lock is grabbed and released as necessary. All actual memory
164-
* allocations are done using GFP_KERNEL with pcpu_lock released. In
165-
* general, percpu memory can't be allocated with irq off but
166-
* irqsave/restore are still used in alloc path so that it can be used
167-
* from early init path - sched_init() specifically.
168-
*
169-
* Free path accesses and alters only the index data structures, so it
170-
* can be safely called from atomic context. When memory needs to be
171-
* returned to the system, free path schedules reclaim_work which
172-
* grabs both pcpu_alloc_mutex and pcpu_lock, unlinks chunks to be
173-
* reclaimed, release both locks and frees the chunks. Note that it's
174-
* necessary to grab both locks to remove a chunk from circulation as
175-
* allocation path might be referencing the chunk with only
176-
* pcpu_alloc_mutex locked.
155+
* Free path accesses and alters only the index data structures and can be
156+
* safely called from atomic context. When memory needs to be returned to
157+
* the system, free path schedules reclaim_work.
177158
*/
178-
static DEFINE_MUTEX(pcpu_alloc_mutex); /* protects whole alloc and reclaim */
179-
static DEFINE_SPINLOCK(pcpu_lock); /* protects index data structures */
159+
static DEFINE_SPINLOCK(pcpu_lock); /* all internal data structures */
160+
static DEFINE_MUTEX(pcpu_alloc_mutex); /* chunk create/destroy, [de]pop */
180161

181162
static struct list_head *pcpu_slot __read_mostly; /* chunk list slots */
182163

@@ -709,7 +690,7 @@ static void __percpu *pcpu_alloc(size_t size, size_t align, bool reserved)
709690
static int warn_limit = 10;
710691
struct pcpu_chunk *chunk;
711692
const char *err;
712-
int slot, off, new_alloc, cpu;
693+
int slot, off, new_alloc, cpu, ret;
713694
int page_start, page_end, rs, re;
714695
unsigned long flags;
715696
void __percpu *ptr;
@@ -729,7 +710,6 @@ static void __percpu *pcpu_alloc(size_t size, size_t align, bool reserved)
729710
return NULL;
730711
}
731712

732-
mutex_lock(&pcpu_alloc_mutex);
733713
spin_lock_irqsave(&pcpu_lock, flags);
734714

735715
/* serve reserved allocations from the reserved chunk if available */
@@ -745,7 +725,7 @@ static void __percpu *pcpu_alloc(size_t size, size_t align, bool reserved)
745725
spin_unlock_irqrestore(&pcpu_lock, flags);
746726
if (pcpu_extend_area_map(chunk, new_alloc) < 0) {
747727
err = "failed to extend area map of reserved chunk";
748-
goto fail_unlock_mutex;
728+
goto fail;
749729
}
750730
spin_lock_irqsave(&pcpu_lock, flags);
751731
}
@@ -771,7 +751,7 @@ static void __percpu *pcpu_alloc(size_t size, size_t align, bool reserved)
771751
if (pcpu_extend_area_map(chunk,
772752
new_alloc) < 0) {
773753
err = "failed to extend area map";
774-
goto fail_unlock_mutex;
754+
goto fail;
775755
}
776756
spin_lock_irqsave(&pcpu_lock, flags);
777757
/*
@@ -787,37 +767,53 @@ static void __percpu *pcpu_alloc(size_t size, size_t align, bool reserved)
787767
}
788768
}
789769

790-
/* hmmm... no space left, create a new chunk */
791770
spin_unlock_irqrestore(&pcpu_lock, flags);
792771

793-
chunk = pcpu_create_chunk();
794-
if (!chunk) {
795-
err = "failed to allocate new chunk";
796-
goto fail_unlock_mutex;
772+
/*
773+
* No space left. Create a new chunk. We don't want multiple
774+
* tasks to create chunks simultaneously. Serialize and create iff
775+
* there's still no empty chunk after grabbing the mutex.
776+
*/
777+
mutex_lock(&pcpu_alloc_mutex);
778+
779+
if (list_empty(&pcpu_slot[pcpu_nr_slots - 1])) {
780+
chunk = pcpu_create_chunk();
781+
if (!chunk) {
782+
err = "failed to allocate new chunk";
783+
goto fail;
784+
}
785+
786+
spin_lock_irqsave(&pcpu_lock, flags);
787+
pcpu_chunk_relocate(chunk, -1);
788+
} else {
789+
spin_lock_irqsave(&pcpu_lock, flags);
797790
}
798791

799-
spin_lock_irqsave(&pcpu_lock, flags);
800-
pcpu_chunk_relocate(chunk, -1);
792+
mutex_unlock(&pcpu_alloc_mutex);
801793
goto restart;
802794

803795
area_found:
804796
spin_unlock_irqrestore(&pcpu_lock, flags);
805797

806798
/* populate if not all pages are already there */
799+
mutex_lock(&pcpu_alloc_mutex);
807800
page_start = PFN_DOWN(off);
808801
page_end = PFN_UP(off + size);
809802

810803
pcpu_for_each_unpop_region(chunk, rs, re, page_start, page_end) {
811804
WARN_ON(chunk->immutable);
812805

813-
if (pcpu_populate_chunk(chunk, rs, re)) {
814-
spin_lock_irqsave(&pcpu_lock, flags);
806+
ret = pcpu_populate_chunk(chunk, rs, re);
807+
808+
spin_lock_irqsave(&pcpu_lock, flags);
809+
if (ret) {
810+
mutex_unlock(&pcpu_alloc_mutex);
815811
pcpu_free_area(chunk, off);
816812
err = "failed to populate";
817813
goto fail_unlock;
818814
}
819-
820815
bitmap_set(chunk->populated, rs, re - rs);
816+
spin_unlock_irqrestore(&pcpu_lock, flags);
821817
}
822818

823819
mutex_unlock(&pcpu_alloc_mutex);
@@ -832,8 +828,7 @@ static void __percpu *pcpu_alloc(size_t size, size_t align, bool reserved)
832828

833829
fail_unlock:
834830
spin_unlock_irqrestore(&pcpu_lock, flags);
835-
fail_unlock_mutex:
836-
mutex_unlock(&pcpu_alloc_mutex);
831+
fail:
837832
if (warn_limit) {
838833
pr_warning("PERCPU: allocation failed, size=%zu align=%zu, "
839834
"%s\n", size, align, err);

0 commit comments

Comments
 (0)