Why Binder won
Android had sockets, pipes and shared memory. It built a kernel driver instead — because IPC on a phone has to be fast, attributable, and bounded, and no general-purpose transport is all three.
Every app on your phone is a separate Linux user. That is the security model, and it is also the reason the platform needs an unusually serious answer to a question most operating systems answer with a socket: how does one sandbox call a function in another?
TL;DR#
- Android's processes are hostile to each other by design. IPC therefore has to carry not just bytes but a trustworthy answer to "who is calling?", and it has to be cheap enough to use on every frame.
- Binder is a kernel driver, not a library. It moves a transaction into the receiver's address space with one copy, attaches the sender's UID and PID, and manages object references so a remote object has a lifetime.
- The alternative — a Unix socket — works, but needs two copies, has no method dispatch, and makes identity and object lifetime your problem.
- The costs are real: a bounded per-process buffer, a thread pool per server, a kernel driver to maintain, and a hard limit on how much data one call can carry.
- Binder is not the only transport on the phone, but it is the one the framework, the system services and AIDL are built on.1
Mental model#
Imagine a large office building where every tenant is a competitor. Nobody may walk into another tenant's office. So the building provides a mailroom.
You write a request on a form, put it in an envelope, and address it to a department, not a person. The mailroom checks your badge, stamps the envelope with your identity, and carries it to the destination. The recipient knows exactly who sent it because the stamp is applied by the building, not by you. If the recipient has moved out, the mailroom tells you instead of letting you wait forever.
The mailroom is Binder. The envelope is a transaction. The badge check is the kernel reading your process credentials. The "recipient has moved out" message is a death notification.
Where the analogy breaks#
A mailroom copies the letter. Binder does not: it arranges for the bytes to be written directly where the receiver will read them, which is why a Binder call can be cheaper than a socket send despite doing more work. The analogy also suggests a central sorting office; Binder has no such thing in the data path. The driver is a referee both sides already share, and the transaction goes straight from one process's memory to the other's.
The big picture#
Text description of this diagram
Two IPC paths compared. In Binder, a sender's Parcel is copied once: the kernel maps part of the receiver's buffer into the sender and writes the transaction there, so the receiver reads the same pages. In a pipe or Unix socket, the data is copied into a kernel buffer and then copied out into the receiver, two copies, with a context switch at each end. Binder's transaction also carries the sender's UID, PID and security context, written by the kernel, and Binder tracks object references across processes.
That diagram is the whole argument in one picture, and it is worth being precise about what it does not say. Binder is not faster because it is magic. It is faster because it does less copying per byte and because it avoids the general-purpose socket layer's bookkeeping. On a modern phone the absolute difference per call is small; the difference that matters is that the framework makes thousands of these calls per second, and a per-call cost that is small but non-zero becomes a frame-time problem.
Deep dive#
The problem Binder was built for#
Start from the sandbox. Each app has its own UID, so the kernel will refuse it access to another app's files, sockets and memory. That is deliberate. But an app still needs to ask the system to start an activity, read a contact, play a sound, or query the window manager. Every one of those is a function call that has to leave the process.
There are only a few ways to do that:
| Mechanism | What it gives you | What it costs |
|---|---|---|
| Files | Simple, persistent | Polling, no notification, permissions per file |
| Pipes | Streaming, cheap | One direction, no structure, two copies |
| Unix sockets | Bidirectional, credentials available | Byte stream, two copies, no dispatch |
| Shared memory | Zero copy after setup | No synchronisation, no identity, no lifetime |
| Binder | Typed calls, identity, object lifetime | Kernel driver, bounded buffers, thread pools |
Shared memory is the fastest and the least safe: once two processes map the same pages, there is no boundary left to enforce. Sockets are safe but give you a stream, and every protocol built on a stream has to reinvent framing, method dispatch, error reporting and object lifetime. Android needed all four on day one, in a form that a framework written in Java could use.
One copy, and why it matters#
Binder's data path has three participants: the client holds a proxy, the server holds a node, and the kernel driver sits between them. The proxy writes the interface descriptor, the method code and the arguments into a — Binder's serialisation format — and issues one ioctl. The driver resolves the handle to a node, finds the process that owns it, and copies the transaction into a buffer mapped in that process.1
One copy, not two. The trick is that the kernel maps the destination buffer into the sender's address space for the duration of the write, so the data lands where the receiver will read it instead of in an intermediate queue. The receiver's thread wakes with a pointer to a buffer that already contains the call.
Identity comes for free#
The second property is the one app developers notice least and attackers notice most: the driver writes the sender's UID, PID and security context into the transaction. The receiver does not have to believe a claim in the payload, because the claim is made by the kernel.
That is what makes Binder.getCallingUid() meaningful inside a system service. A service can check
that the caller actually holds a permission, that it is the app it says it is, or that it belongs to
a particular user. Every permission check in the framework ultimately rests on this: the UID in the
transaction is the UID the kernel knows, not the UID the client typed into a struct.
A Unix socket can report peer credentials too — SO_PEERCRED exists for exactly this — so identity
alone would not justify a new driver. What a socket cannot do is attach that identity to each
message while also giving you method dispatch and object references. Binder gives you all three in
one mechanism.
Objects have a lifetime#
Send a Binder object to another process and it is not copied. The driver creates a handle in the receiver that refers back to the original node. Each process sees a different handle number for the same object, and the driver maintains the reference counts. When the last reference disappears, the object is destroyed.
This is what makes callback interfaces workable. A client can hand a callback object to a service, the service can call it later, and when the client dies the service learns about it through a death notification rather than calling into a dead process forever. Try to build the same thing on a socket and you end up inventing a reference protocol, a heartbeat, and a garbage collector.
The service registry#
Handles are process-local, so there has to be a way to find a service in the first place. That is
, a small native daemon started early in boot. It holds a
map from a name — activity, window, package — to a node, and clients ask it for the handle.
The lookup is itself a Binder transaction, which sounds circular until you notice the trick: every process can reach servicemanager because the driver creates a handle to it at open time. There is one well-known address, and everything else is discovered through it.
Text description of this diagram
A sequence diagram of a service lookup. An app calls Context.getSystemService with a string constant. ContextImpl checks a per-process cache first. On a miss it asks ServiceManager, which sends a Binder transaction to the servicemanager daemon. servicemanager returns a handle valid in the caller's process. The first real call is a separate transaction to the service itself inside system_server. The framework wraps the proxy in a typed manager object before returning it to the app.
Two caches sit on that path. ContextImpl caches manager objects per process, so the second call to
getSystemService never crosses Binder. ServiceManager caches handles it has already resolved.
A service lookup is therefore cheap after the first one, which is why the framework can afford to
hand out managers casually.
What Binder costs#
The design is not free, and the costs are the ones that show up in production incidents.
A bounded buffer. Each process has one buffer for in-flight transactions. When it fills, calls fail with or block. Sending a large bitmap through a Binder call is a classic way to crash an app that worked in testing.
A bounded thread pool. A server process serves Binder calls from a pool that grows on demand up to a maximum. libbinder's default is 15 threads, configurable per process.2 When every thread is busy, new calls queue, and a single slow method can stall every client at once.
No streaming. Binder is request/response, not a byte stream. For audio, video or large files you use shared memory, a file descriptor or a socket, and use Binder only for the control plane.
A kernel driver. Every Android device must carry it, and it must be maintained against Linux releases. That is why the driver was upstreamed, and why exists: it lets the kernel expose Binder devices dynamically instead of requiring a fixed set of device nodes at build time.3
Why not just use D-Bus?#
Android is often compared with desktop Linux, where D-Bus plays a similar role. D-Bus is a userspace message bus: a daemon routes messages between peers, and it is designed for desktop services where calls are relatively rare and payloads are small. Binder puts the routing in the kernel and makes the transaction the unit of scheduling and priority. Android's system services see call rates that a userspace bus would struggle with, and the platform needs the caller's identity attached by the kernel rather than asserted by a daemon.
How it evolved#
Binder has been in Android since the first release, and most of its history is about making the driver safer and more flexible rather than changing the programming model.
| API | Version | Released | What changed |
|---|---|---|---|
| 1 | 1.0 | 2008-09-23 | — |
| 21 | 5.0 Lollipop | 2014-11-03 | — |
| 26 | 8.0 Oreo | 2017-08-21 | — |
| 29 | 10 | 2019-09-03 | — |
| 31 | 12 | 2021-10-04 | — |
| 33 | 13 Tiramisu | 2022-08-15 | — |
| 37 | 17 Cinnamon Bun | 2026-06-16 | — |
Three of those rows are worth watching. Android 8.0 made Binder the substrate for the vendor interface, which is why AIDL eventually replaced HIDL for HALs. The cached-app freezer lets the system freeze an app without killing it — a state Binder itself has to reason about, and one Android 14 made more aggressive. Android 17 changed the runtime in ways that alter what a Binder call can do to a process's memory, covered in Binder threads and performance.
Under the hood#
- binder.cthe kernel driver: transactions, handles, refcounts, death notification
- libbinder, the C++ client and server library
- Binder.javathe Java-side Binder base class and Parcel plumbing
- the service registry daemon
The split matters: the kernel driver owns correctness and security, while libbinder owns the
convenience. Everything from BpBinder to the generated AIDL stubs is a userspace layer on top of
one ioctl interface.
Curiosity corner#
- The buffer limit is a design decision, not an oversight. A bounded buffer means the kernel can guarantee a receiver always has somewhere to put an incoming transaction. The alternative — unbounded queues — turns a slow receiver into a memory exhaustion bug.
- A Binder object is not copied when you send it. The receiver gets a handle, and the driver keeps the reference counts. That is why sending a large object graph does not duplicate it.
- The driver knows when a process dies. Death notification is implemented in the kernel, not by
a heartbeat in userspace, so it fires even if the target was killed with
SIGKILL.
Try it#
adb shell service list | head -20Service counts and sort order vary by device and Android version.
adb shell ps -A | grep servicemanagerPIDs and memory figures differ per device.
Build it#
The builder track's first IPC project is an AIDL calculator: one service process, one client, and a
.aidl file between them. It is the smallest possible Binder system, and building it makes the
lookup sequence above concrete. See I01 — AIDL calculator.
Check yourself#
Check yourself
Why does a Binder transaction copy the payload only once?
Sources#
Sources
Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.
- docsBinder overviewsource.android.comaccessed 2026-10-27↩
- docsBinder threadingsource.android.comaccessed 2026-10-27↩
- docsbinderfs — Linux kernel documentationdocs.kernel.orgaccessed 2026-10-27↩
- docsAndroid platform architecture (AOSP)source.android.comaccessed 2026-10-27↩