Skip to content

Commit 7710e2c

Browse files
Carlos Llamasgregkh
authored andcommitted
binder: switch alloc->mutex to spinlock_t
The alloc->mutex is a highly contended lock that causes performance issues on Android devices. When a low-priority task is given this lock and it sleeps, it becomes difficult for the task to wake up and complete its work. This delays other tasks that are also waiting on the mutex. The problem gets worse when there is memory pressure in the system, because this increases the contention on the alloc->mutex while the shrinker reclaims binder pages. Switching to a spinlock helps to keep the waiters running and avoids the overhead of waking up tasks. This significantly improves the transaction latency when the problematic scenario occurs. The performance impact of this patchset was measured by stress-testing the binder alloc contention. In this test, several clients of different priorities send thousands of transactions of different sizes to a single server. In parallel, pages get reclaimed using the shinker's debugfs. The test was run on a Pixel 8, Pixel 6 and qemu machine. The results were similar on all three devices: after: | sched | prio | average | max | min | |--------+------+---------+-----------+---------| | fifo | 99 | 0.135ms | 1.197ms | 0.022ms | | fifo | 01 | 0.136ms | 5.232ms | 0.018ms | | other | -20 | 0.180ms | 7.403ms | 0.019ms | | other | 19 | 0.241ms | 58.094ms | 0.018ms | before: | sched | prio | average | max | min | |--------+------+---------+-----------+---------| | fifo | 99 | 0.350ms | 248.730ms | 0.020ms | | fifo | 01 | 0.357ms | 248.817ms | 0.024ms | | other | -20 | 0.399ms | 249.906ms | 0.020ms | | other | 19 | 0.477ms | 297.756ms | 0.022ms | The key metrics above are the average and max latencies (wall time). These improvements should roughly translate to p95-p99 latencies on real workloads. The response time is up to 200x faster in these scenarios and there is no penalty in the regular path. Note that it is only possible to convert this lock after a series of changes made by previous patches. These mainly include refactoring the sections that might_sleep() and changing the locking order with the mmap_lock amongst others. Reviewed-by: Alice Ryhl <aliceryhl@google.com> Signed-off-by: Carlos Llamas <cmllamas@google.com> Link: https://lore.kernel.org/r/20231201172212.1813387-29-cmllamas@google.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent e50f4e6 commit 7710e2c

File tree

2 files changed

+28
-28
lines changed

2 files changed

+28
-28
lines changed

drivers/android/binder_alloc.c

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,9 @@ struct binder_buffer *binder_alloc_prepare_to_free(struct binder_alloc *alloc,
169169
{
170170
struct binder_buffer *buffer;
171171

172-
mutex_lock(&alloc->mutex);
172+
spin_lock(&alloc->lock);
173173
buffer = binder_alloc_prepare_to_free_locked(alloc, user_ptr);
174-
mutex_unlock(&alloc->mutex);
174+
spin_unlock(&alloc->lock);
175175
return buffer;
176176
}
177177

@@ -597,18 +597,18 @@ struct binder_buffer *binder_alloc_new_buf(struct binder_alloc *alloc,
597597
if (!next)
598598
return ERR_PTR(-ENOMEM);
599599

600-
mutex_lock(&alloc->mutex);
600+
spin_lock(&alloc->lock);
601601
buffer = binder_alloc_new_buf_locked(alloc, next, size, is_async);
602602
if (IS_ERR(buffer)) {
603-
mutex_unlock(&alloc->mutex);
603+
spin_unlock(&alloc->lock);
604604
goto out;
605605
}
606606

607607
buffer->data_size = data_size;
608608
buffer->offsets_size = offsets_size;
609609
buffer->extra_buffers_size = extra_buffers_size;
610610
buffer->pid = current->tgid;
611-
mutex_unlock(&alloc->mutex);
611+
spin_unlock(&alloc->lock);
612612

613613
ret = binder_install_buffer_pages(alloc, buffer, size);
614614
if (ret) {
@@ -785,17 +785,17 @@ void binder_alloc_free_buf(struct binder_alloc *alloc,
785785
* We could eliminate the call to binder_alloc_clear_buf()
786786
* from binder_alloc_deferred_release() by moving this to
787787
* binder_free_buf_locked(). However, that could
788-
* increase contention for the alloc mutex if clear_on_free
789-
* is used frequently for large buffers. The mutex is not
788+
* increase contention for the alloc->lock if clear_on_free
789+
* is used frequently for large buffers. This lock is not
790790
* needed for correctness here.
791791
*/
792792
if (buffer->clear_on_free) {
793793
binder_alloc_clear_buf(alloc, buffer);
794794
buffer->clear_on_free = false;
795795
}
796-
mutex_lock(&alloc->mutex);
796+
spin_lock(&alloc->lock);
797797
binder_free_buf_locked(alloc, buffer);
798-
mutex_unlock(&alloc->mutex);
798+
spin_unlock(&alloc->lock);
799799
}
800800

801801
/**
@@ -893,7 +893,7 @@ void binder_alloc_deferred_release(struct binder_alloc *alloc)
893893
struct binder_buffer *buffer;
894894

895895
buffers = 0;
896-
mutex_lock(&alloc->mutex);
896+
spin_lock(&alloc->lock);
897897
BUG_ON(alloc->vma);
898898

899899
while ((n = rb_first(&alloc->allocated_buffers))) {
@@ -943,7 +943,7 @@ void binder_alloc_deferred_release(struct binder_alloc *alloc)
943943
}
944944
kfree(alloc->pages);
945945
}
946-
mutex_unlock(&alloc->mutex);
946+
spin_unlock(&alloc->lock);
947947
if (alloc->mm)
948948
mmdrop(alloc->mm);
949949

@@ -966,7 +966,7 @@ void binder_alloc_print_allocated(struct seq_file *m,
966966
struct binder_buffer *buffer;
967967
struct rb_node *n;
968968

969-
mutex_lock(&alloc->mutex);
969+
spin_lock(&alloc->lock);
970970
for (n = rb_first(&alloc->allocated_buffers); n; n = rb_next(n)) {
971971
buffer = rb_entry(n, struct binder_buffer, rb_node);
972972
seq_printf(m, " buffer %d: %lx size %zd:%zd:%zd %s\n",
@@ -976,7 +976,7 @@ void binder_alloc_print_allocated(struct seq_file *m,
976976
buffer->extra_buffers_size,
977977
buffer->transaction ? "active" : "delivered");
978978
}
979-
mutex_unlock(&alloc->mutex);
979+
spin_unlock(&alloc->lock);
980980
}
981981

982982
/**
@@ -993,7 +993,7 @@ void binder_alloc_print_pages(struct seq_file *m,
993993
int lru = 0;
994994
int free = 0;
995995

996-
mutex_lock(&alloc->mutex);
996+
spin_lock(&alloc->lock);
997997
/*
998998
* Make sure the binder_alloc is fully initialized, otherwise we might
999999
* read inconsistent state.
@@ -1009,7 +1009,7 @@ void binder_alloc_print_pages(struct seq_file *m,
10091009
lru++;
10101010
}
10111011
}
1012-
mutex_unlock(&alloc->mutex);
1012+
spin_unlock(&alloc->lock);
10131013
seq_printf(m, " pages: %d:%d:%d\n", active, lru, free);
10141014
seq_printf(m, " pages high watermark: %zu\n", alloc->pages_high);
10151015
}
@@ -1025,10 +1025,10 @@ int binder_alloc_get_allocated_count(struct binder_alloc *alloc)
10251025
struct rb_node *n;
10261026
int count = 0;
10271027

1028-
mutex_lock(&alloc->mutex);
1028+
spin_lock(&alloc->lock);
10291029
for (n = rb_first(&alloc->allocated_buffers); n != NULL; n = rb_next(n))
10301030
count++;
1031-
mutex_unlock(&alloc->mutex);
1031+
spin_unlock(&alloc->lock);
10321032
return count;
10331033
}
10341034

@@ -1073,8 +1073,8 @@ enum lru_status binder_alloc_free_page(struct list_head *item,
10731073
goto err_mmget;
10741074
if (!mmap_read_trylock(mm))
10751075
goto err_mmap_read_lock_failed;
1076-
if (!mutex_trylock(&alloc->mutex))
1077-
goto err_get_alloc_mutex_failed;
1076+
if (!spin_trylock(&alloc->lock))
1077+
goto err_get_alloc_lock_failed;
10781078
if (!page->page_ptr)
10791079
goto err_page_already_freed;
10801080

@@ -1093,7 +1093,7 @@ enum lru_status binder_alloc_free_page(struct list_head *item,
10931093
trace_binder_unmap_kernel_end(alloc, index);
10941094

10951095
list_lru_isolate(lru, item);
1096-
mutex_unlock(&alloc->mutex);
1096+
spin_unlock(&alloc->lock);
10971097
spin_unlock(lock);
10981098

10991099
if (vma) {
@@ -1113,8 +1113,8 @@ enum lru_status binder_alloc_free_page(struct list_head *item,
11131113

11141114
err_invalid_vma:
11151115
err_page_already_freed:
1116-
mutex_unlock(&alloc->mutex);
1117-
err_get_alloc_mutex_failed:
1116+
spin_unlock(&alloc->lock);
1117+
err_get_alloc_lock_failed:
11181118
mmap_read_unlock(mm);
11191119
err_mmap_read_lock_failed:
11201120
mmput_async(mm);
@@ -1149,7 +1149,7 @@ void binder_alloc_init(struct binder_alloc *alloc)
11491149
alloc->pid = current->group_leader->pid;
11501150
alloc->mm = current->mm;
11511151
mmgrab(alloc->mm);
1152-
mutex_init(&alloc->mutex);
1152+
spin_lock_init(&alloc->lock);
11531153
INIT_LIST_HEAD(&alloc->buffers);
11541154
}
11551155

drivers/android/binder_alloc.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
#include <linux/rbtree.h>
1010
#include <linux/list.h>
1111
#include <linux/mm.h>
12-
#include <linux/rtmutex.h>
12+
#include <linux/spinlock.h>
1313
#include <linux/vmalloc.h>
1414
#include <linux/slab.h>
1515
#include <linux/list_lru.h>
@@ -72,7 +72,7 @@ struct binder_lru_page {
7272

7373
/**
7474
* struct binder_alloc - per-binder proc state for binder allocator
75-
* @mutex: protects binder_alloc fields
75+
* @lock: protects binder_alloc fields
7676
* @vma: vm_area_struct passed to mmap_handler
7777
* (invariant after mmap)
7878
* @mm: copy of task->mm (invariant after open)
@@ -96,7 +96,7 @@ struct binder_lru_page {
9696
* struct binder_buffer objects used to track the user buffers
9797
*/
9898
struct binder_alloc {
99-
struct mutex mutex;
99+
spinlock_t lock;
100100
struct vm_area_struct *vma;
101101
struct mm_struct *mm;
102102
unsigned long buffer;
@@ -153,9 +153,9 @@ binder_alloc_get_free_async_space(struct binder_alloc *alloc)
153153
{
154154
size_t free_async_space;
155155

156-
mutex_lock(&alloc->mutex);
156+
spin_lock(&alloc->lock);
157157
free_async_space = alloc->free_async_space;
158-
mutex_unlock(&alloc->mutex);
158+
spin_unlock(&alloc->lock);
159159
return free_async_space;
160160
}
161161

0 commit comments

Comments
 (0)