[RFC PATCH v4 2/2] binder: add install_mutex to serialize page install and shrinker zap
From: Bo Zhang
Date: Mon Sep 07 2026 - 09:22:21 EST
The previous patch converted alloc->mutex to a spinlock for the hot
path (buffer alloc/free). However, page installation may sleep in
vm_insert_page(), and the shrinker may sleep in zap_vma_range(), so
these cannot be serialized by the spinlock. Without serialization, the
install side could observe and reuse a page that the shrinker is about
to zap and free.
Add a separate install_mutex to serialize page installation against the
shrinker's zap. The two locks have distinct roles:
- alloc->lock (spinlock) exclusively owns the non-sleeping metadata:
pages[], the LRU list, the rb-trees and free_async_space.
- install_mutex only serializes the sleeping PTE operations
(vm_insert_page vs zap_vma_range) for a given alloc.
pages[] and the LRU are always updated together under alloc->lock, so
binder_lru_freelist_del() always observes a consistent state.
The shrinker acquires install_mutex with mutex_trylock() and skips the
page (LRU_SKIP) on failure. This is required because the install side
may hold install_mutex while its vm_insert_page() recurses into direct
reclaim and re-enters this shrinker on the same thread; a blocking
acquire would self-deadlock. Using trylock also keeps the shrinker out
of any blocking lock cycle with mmap_lock, so the install side may take
mmap_lock while holding install_mutex without risking an ABBA deadlock.
Performance (binderThroughputTest, Qualcomm SM8850, 2 workers, 10 runs)
under concurrent drop_caches shows no regression from the install_mutex:
mutex (baseline) spinlock + install_mutex
throughput: 27k-59k iter/s 84k-89k iter/s
average: 0.031-0.068ms 0.021-0.022ms
P99: 0.088-0.148ms 0.046-0.056ms
Signed-off-by: Bo Zhang <zhangbo56@xxxxxxxxxx>
---
drivers/android/binder_alloc.c | 59 +++++++++++++++++++++-------------
drivers/android/binder_alloc.h | 3 ++
2 files changed, 39 insertions(+), 23 deletions(-)
diff --git a/drivers/android/binder_alloc.c b/drivers/android/binder_alloc.c
index 9775df3616aa..61544e3cdae1 100644
--- a/drivers/android/binder_alloc.c
+++ b/drivers/android/binder_alloc.c
@@ -325,34 +325,34 @@ static int binder_install_single_page(struct binder_alloc *alloc,
goto out;
}
- ret = binder_page_insert(alloc, addr, page);
- switch (ret) {
- case -EBUSY:
- /*
- * EBUSY is ok. Someone installed the pte first but the
- * alloc->pages[index] has not been updated yet. Discard
- * our page and look up the one already installed.
- */
- ret = 0;
+ mutex_lock(&alloc->install_mutex);
+
+ /* Someone may have installed it already; check under alloc->lock */
+ spin_lock(&alloc->lock);
+ if (binder_get_installed_page(alloc, index)) {
+ spin_unlock(&alloc->lock);
+ mutex_unlock(&alloc->install_mutex);
binder_free_page(page);
- page = binder_page_lookup(alloc, addr);
- if (!page) {
- pr_err("%d: failed to find page at offset %lx\n",
- alloc->pid, addr - alloc->vm_start);
- ret = -ESRCH;
- break;
- }
- fallthrough;
- case 0:
- /* Mark page installation complete and safe to use */
- binder_set_installed_page(alloc, index, page);
- break;
- default:
+ ret = 0;
+ goto out;
+ }
+ spin_unlock(&alloc->lock);
+
+ ret = binder_page_insert(alloc, addr, page);
+ if (ret) {
binder_free_page(page);
pr_err("%d: %s failed to insert page at offset %lx with %d\n",
alloc->pid, __func__, addr - alloc->vm_start, ret);
- break;
+ mutex_unlock(&alloc->install_mutex);
+ goto out;
}
+
+ /* Mark page installation complete under alloc->lock */
+ spin_lock(&alloc->lock);
+ binder_set_installed_page(alloc, index, page);
+ spin_unlock(&alloc->lock);
+
+ mutex_unlock(&alloc->install_mutex);
out:
mmput_async(alloc->mm);
return ret;
@@ -1161,6 +1161,14 @@ enum lru_status binder_alloc_free_page(struct list_head *item,
vma = vma_lookup(mm, page_addr);
}
+ /*
+ * Use trylock: the install side may hold install_mutex while its
+ * vm_insert_page() recurses into reclaim and re-enters this shrinker
+ * on the same thread, so blocking here would self-deadlock.
+ */
+ if (!mutex_trylock(&alloc->install_mutex))
+ goto err_get_install_mutex_failed;
+
if (!spin_trylock(&alloc->lock))
goto err_get_alloc_lock_failed;
@@ -1191,6 +1199,8 @@ enum lru_status binder_alloc_free_page(struct list_head *item,
trace_binder_unmap_user_end(alloc, index);
}
+ mutex_unlock(&alloc->install_mutex);
+
if (mm_locked)
mmap_read_unlock(mm);
else
@@ -1203,6 +1213,8 @@ enum lru_status binder_alloc_free_page(struct list_head *item,
err_invalid_vma:
spin_unlock(&alloc->lock);
err_get_alloc_lock_failed:
+ mutex_unlock(&alloc->install_mutex);
+err_get_install_mutex_failed:
if (mm_locked)
mmap_read_unlock(mm);
else
@@ -1236,6 +1248,7 @@ VISIBLE_IF_KUNIT void __binder_alloc_init(struct binder_alloc *alloc,
alloc->mm = current->mm;
mmgrab(alloc->mm);
spin_lock_init(&alloc->lock);
+ mutex_init(&alloc->install_mutex);
INIT_LIST_HEAD(&alloc->buffers);
alloc->freelist = freelist;
}
diff --git a/drivers/android/binder_alloc.h b/drivers/android/binder_alloc.h
index bea5a77bb6da..85817efdbef6 100644
--- a/drivers/android/binder_alloc.h
+++ b/drivers/android/binder_alloc.h
@@ -9,6 +9,7 @@
#include <linux/rbtree.h>
#include <linux/list.h>
#include <linux/mm.h>
+#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
@@ -81,6 +82,7 @@ static inline struct list_head *page_to_lru(struct page *p)
/**
* struct binder_alloc - per-binder proc state for binder allocator
* @lock: protects binder_alloc fields
+ * @install_mutex: serializes page installation and shrinker zap
* @mm: copy of task->mm (invariant after open)
* @vm_start: base of per-proc address space mapped via mmap
* @buffers: list of all buffers for this proc
@@ -106,6 +108,7 @@ static inline struct list_head *page_to_lru(struct page *p)
*/
struct binder_alloc {
spinlock_t lock;
+ struct mutex install_mutex;
struct mm_struct *mm;
unsigned long vm_start;
struct list_head buffers;
--
2.34.1