Android Binder:多进程读写同步机制原理探究
Great question—this is one of the trickier parts of Binder’s design, especially when you’re wading through that dense binder.c code. Let’s break this down clearly:
一、Binder驱动的锁:细粒度拆分,而非全局串行
You’re spot-on with your hypothesis—Binder never uses a single global lock to serialize all operations. Instead, it relies on a set of fine-grained locks that protect isolated resources, which is exactly how the thread pool can deliver real concurrency. Here’s how it works:
- Per-process/thread locks: Each Binder process (
binder_proc) and thread (binder_thread) has its own lock, guarding internal queues and state (like the process’s incoming transaction queue or a thread’s pending tasks). - Per-node locks: Every Binder object’s corresponding
binder_nodehas an independent lock, protecting its reference count and transaction state. Operations on different nodes can run in parallel without blocking each other. - Minimal global locks: Only tiny, fast operations (like assigning unique IDs to new nodes or updating the global process list) use global locks—these are so quick they never block core transaction flows.
For example: When the driver handles a transaction from Process A to a Binder node in Process B, it only locks Process A’s outgoing queue, Process B’s incoming queue, and the target node’s lock. Once the transaction is queued, all locks are released immediately, leaving other transactions free to proceed.
二、How the 16-thread pool leverages fine-grained locks
The default 16-thread pool on the server side exists to let multiple threads pull and execute tasks from the server’s transaction queue simultaneously. The driver’s locking model enables this perfectly:
- When the driver receives a transaction for the server, it adds it to the server’s
binder_proctransaction queue and wakes an idle thread from the pool. - The awakened thread calls
binder_thread_read()to fetch a transaction. During this step, it only locks the server’s transaction queue (and releases it right after fetching the task)—no other threads are blocked from grabbing their own transactions. - Each thread processes its assigned transaction independently. With per-node locks protecting shared Binder objects, multiple threads can even handle transactions for the same object as long as the operations aren’t mutually exclusive.
In short: Fine-grained locks keep transaction queue operations safe and non-blocking, while the thread pool lets the actual business logic of multiple transactions run in parallel.
三、No "global read/write buffer"—each transaction gets its own isolated buffer
Your concern about global buffer conflicts is based on a common misunderstanding: Binder does not use a shared global buffer. Instead, every transaction gets its own dedicated buffer, managed with strict lifecycle rules that eliminate concurrent read/write issues:
- When a transaction starts, the driver allocates a buffer in the sender’s process memory (via the
binder_allocsubsystem). The sender copies data into this buffer, which is then memory-mapped (mmap) to the receiver’s process space—no redundant copy needed. - Once the transaction is processed, the driver marks the buffer as reusable, but it will never be assigned to another transaction until the current one is fully completed.
- Transaction buffers are tied directly to individual transactions/threads, not shared globally. There’s no scenario where multiple processes or threads are writing to the same buffer at the same time.
Side note: Server threads handle transaction data from the mapped dedicated buffer, not their own thread stacks. The stack only holds local variables for the thread’s execution flow, not transaction payloads.
Quick Recap
Binder’s ability to handle multi-process concurrency boils down to three key choices:
- Fine-grained locks that avoid global bottlenecks
- A server-side thread pool for parallel execution of transaction logic
- Per-transaction isolated buffers that eliminate cross-process buffer conflicts
内容的提问来源于stack exchange,提问作者stdout




