Chapter 9: Binder IPC¶
Binder is the heart of Android's inter-process communication. Every activity launch, every service call, every permission check, every surface composition passes through Binder. It is not merely an IPC mechanism -- it is the object-oriented middleware that makes Android's component architecture possible. Understanding Binder is prerequisite to understanding everything else in AOSP.
This chapter dissects Binder from the kernel driver through the C++ and Rust
userspace libraries, into the AIDL code-generation toolchain, and up to the
servicemanager that acts as the system's name-service. By the end you will be
able to trace a complete transaction from a client process through the kernel
into a server process, and you will have built your own Binder service.
9.1 Why Binder?¶
9.1.1 The Problem: Secure, Fast IPC for a Mobile OS¶
Android runs dozens of system services (Activity Manager, Window Manager, Package Manager, SurfaceFlinger, etc.) in separate processes. Applications in their own sandboxed processes must communicate with these services hundreds of times per second. The IPC mechanism must satisfy several hard requirements:
-
Identity-based security. The kernel must authoritatively identify the caller (UID, PID, SELinux context) so that the server can make access-control decisions. Traditional Unix IPC (pipes, Unix sockets) can pass credentials via
SO_PEERCRED, but this is per-connection, not per-transaction. -
Object-reference semantics. A client should be able to hold a reference to a specific object in a server process. When that object dies, the client should receive a death notification. When the last reference is released, the object should be cleaned up.
-
One-copy data transfer. For performance on mobile hardware, data should be copied at most once between address spaces. Traditional message passing (pipes, message queues) requires a copy from sender to kernel, then another from kernel to receiver -- two copies.
-
Synchronous and asynchronous calls. Both request-reply (synchronous) and fire-and-forget (oneway / asynchronous) patterns must be supported.
-
Thread-pool management. The kernel should be able to manage a pool of threads in the server process, spawning new threads as needed and retiring idle ones.
9.1.2 Historical Context¶
Binder's origins predate Android. It descends from OpenBinder, developed at Be Inc. (creators of BeOS) in the early 2000s by Dianne Hackborn and others. When Palm acquired Be's technology, OpenBinder continued development. When Google built Android, the team (which included Hackborn) adapted OpenBinder into what became the Android Binder.
The key insight of the original design was that mobile devices need a capability-based IPC system where object references serve as capabilities. Unix IPC mechanisms are channel-oriented (you connect to a named endpoint), not object-oriented (you hold a reference to a specific object). Binder bridges this gap by providing object-reference semantics through a kernel driver.
The kernel driver was initially out-of-tree (in the Android kernel drivers/
staging/android/ directory). Over the years, it was cleaned up and merged into
the upstream Linux kernel under drivers/android/. Modern Linux kernels (5.0+)
include the binder driver without any Android-specific patches.
9.1.3 Comparison with Traditional Unix IPC¶
| Mechanism | Copies | Identity | Object Refs | Thread Mgmt |
|---|---|---|---|---|
| Pipe | 2 (write + read) | None per-message | No | No |
| Unix Socket | 2 (send + recv) | SO_PEERCRED (per-connection) | No | No |
| Shared Memory | 0 | None | No | No |
| SysV Message Queue | 2 | Limited (uid check) | No | No |
| Binder | 1 (driver copies into recipient's mmap'd buffer) | Per-transaction (UID, PID, SELinux SID) | Yes (ref-counted, death notifications) | Yes (kernel-managed thread pool) |
Pipes and Unix sockets require two copies: one from the sender's buffer
into the kernel, and a second from the kernel into the receiver's buffer. They
provide no per-message identity -- SO_PEERCRED only tells you who opened the
connection, not who sent a particular message on a multiplexed connection.
Shared memory (ashmem or memfd) achieves zero copies but provides no
synchronization, no message framing, and no identity. It is used in
combination with Binder (for example, SurfaceFlinger uses shared-memory
buffers but Binder for the control plane).
Binder achieves a single copy through memory mapping: the kernel maps a region of the receiver's address space, then copies the sender's data directly into that region. The receiver reads the data from its own mapped memory without an additional copy.
9.1.4 The One-Copy Mechanism¶
When a process opens /dev/binder, it calls mmap() to map the binder
buffer. As defined in ProcessState.cpp:
// frameworks/native/libs/binder/ProcessState.cpp
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
This creates a ~1 MB buffer (minus two pages for guard pages). When a transaction arrives, the binder driver allocates space within the receiver's mapped region and copies the sender's data directly there. The receiver reads from its own virtual address space -- a single copy.
Sender Kernel Receiver
┌─────────┐ copy_from_user ┌──────────────┐
│ Parcel │ ─────────────────────>│ Receiver's │
│ data │ │ mmap buffer │
└─────────┘ └──────────────┘
│
│ (already in receiver's
│ address space)
v
┌──────────────┐
│ Receiver │
│ reads data │
└──────────────┘
9.1.5 Identity-Based Security¶
Every Binder transaction carries the sender's UID and PID, injected by the kernel driver (not by userspace). The sender cannot forge these values. The receiving process reads them via:
// frameworks/native/libs/binder/include/binder/IPCThreadState.h
[[nodiscard]] pid_t getCallingPid() const;
[[nodiscard]] uid_t getCallingUid() const;
[[nodiscard]] const char* getCallingSid() const; // SELinux Security ID
This per-transaction identity is the foundation of Android's permission model.
When an app calls ActivityManager.startActivity(), the system_server receives
the Binder transaction, reads the caller's UID, and checks whether that UID
has the required permission.
9.1.6 Object References and Death Notifications¶
Binder provides a distributed object model. A server creates a BBinder object
(the "node"). When it sends that object across Binder to a client, the client
receives a BpBinder (the "proxy"). The kernel driver maintains reference
counts on the node -- when all proxies are released, the node can be
garbage-collected.
If the server process dies, the kernel driver sends a BR_DEAD_BINDER
notification to every client that registered a DeathRecipient:
// frameworks/native/libs/binder/include/binder/IBinder.h
class DeathRecipient : public virtual RefBase {
public:
virtual void binderDied(const wp<IBinder>& who) = 0;
};
virtual status_t linkToDeath(const sp<DeathRecipient>& recipient,
void* cookie = nullptr,
uint32_t flags = 0) = 0;
This is how Android detects when an app crashes and triggers cleanup in
ActivityManagerService, WindowManagerService, etc.
9.1.7 The Three Binder Domains¶
Modern Android has three separate binder device nodes, each with its own context manager:
| Device | Context Manager | Purpose |
|---|---|---|
/dev/binder |
servicemanager |
Framework services (system_server <-> apps) |
/dev/hwbinder |
hwservicemanager |
HAL services (HIDL interfaces) |
/dev/vndbinder |
vndservicemanager |
Vendor-to-vendor services |
The default device node depends on the build variant:
// frameworks/native/libs/binder/ProcessState.cpp
#ifdef __ANDROID_VNDK__
const char* kDefaultDriver = "/dev/vndbinder";
#else
const char* kDefaultDriver = "/dev/binder";
#endif
9.2 The Binder Driver¶
The binder driver is a Linux kernel module (now mainlined in the upstream kernel
under drivers/android/). It implements a character device (/dev/binder)
that userspace communicates with via ioctl() and mmap().
9.2.1 Key ioctl Commands¶
The driver exposes several ioctl commands. The most important are:
| ioctl | Purpose |
|---|---|
BINDER_WRITE_READ |
Main workhorse: sends commands and receives responses in one call |
BINDER_SET_MAX_THREADS |
Configures the maximum number of kernel-managed threads |
BINDER_SET_CONTEXT_MGR |
Declares the calling process as the context manager (service manager) |
BINDER_SET_CONTEXT_MGR_EXT |
Same, but with security context flags |
BINDER_GET_NODE_DEBUG_INFO |
Retrieves debug info about binder nodes |
BINDER_GET_NODE_INFO_FOR_REF |
Gets reference count info for a handle |
The binder_module.h header bridges userspace to the kernel interface:
// frameworks/native/libs/binder/binder_module.h
#include <linux/android/binder.h>
#include <sys/ioctl.h>
9.2.2 The BINDER_WRITE_READ Structure¶
All transaction data flows through the binder_write_read structure:
struct binder_write_read {
binder_size_t write_size; /* bytes to write */
binder_size_t write_consumed; /* bytes consumed by driver */
binder_uintptr_t write_buffer; /* pointer to write commands */
binder_size_t read_size; /* bytes available to read */
binder_size_t read_consumed; /* bytes written by driver */
binder_uintptr_t read_buffer; /* pointer to read buffer */
};
A single ioctl(fd, BINDER_WRITE_READ, &bwr) can both send outgoing commands
and receive incoming responses. This is how IPCThreadState::talkWithDriver()
works:
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~1268)
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
if (mProcess->mDriverFD < 0) {
return -EBADF;
}
binder_write_read bwr;
// Is the read buffer empty?
const bool needRead = mIn.dataPosition() >= mIn.dataSize();
const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;
bwr.write_size = outAvail;
bwr.write_buffer = (uintptr_t)mOut.data();
if (doReceive && needRead) {
bwr.read_size = mIn.dataCapacity();
bwr.read_buffer = (uintptr_t)mIn.data();
} else {
bwr.read_size = 0;
bwr.read_buffer = 0;
}
// Return immediately if there is nothing to do.
if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;
bwr.write_consumed = 0;
bwr.read_consumed = 0;
status_t err;
do {
#if defined(BINDER_WITH_KERNEL_IPC)
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
else
err = -errno;
#else
err = INVALID_OPERATION;
#endif
} while (err == -EINTR);
// ...
}
9.2.3 Transaction Protocol: BC_ and BR_ Commands¶
The write buffer contains BC_ (Binder Command) codes. The read buffer
returns BR_ (Binder Return) codes. The complete set is defined in the
kernel header and echoed in IPCThreadState.cpp:
BC_ (Commands -- userspace to driver):
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~135)
static const char* kCommandStrings[] = {
"BC_TRANSACTION",
"BC_REPLY",
"BC_ACQUIRE_RESULT",
"BC_FREE_BUFFER",
"BC_INCREFS",
"BC_ACQUIRE",
"BC_RELEASE",
"BC_DECREFS",
"BC_INCREFS_DONE",
"BC_ACQUIRE_DONE",
"BC_ATTEMPT_ACQUIRE",
"BC_REGISTER_LOOPER",
"BC_ENTER_LOOPER",
"BC_EXIT_LOOPER",
"BC_REQUEST_DEATH_NOTIFICATION",
"BC_CLEAR_DEATH_NOTIFICATION",
"BC_DEAD_BINDER_DONE",
"BC_TRANSACTION_SG",
"BC_REPLY_SG",
"BC_REQUEST_FREEZE_NOTIFICATION",
"BC_CLEAR_FREEZE_NOTIFICATION",
"BC_FREEZE_NOTIFICATION_DONE",
};
BR_ (Returns -- driver to userspace):
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~109)
static const char* kReturnStrings[] = {
"BR_ERROR",
"BR_OK",
"BR_TRANSACTION/BR_TRANSACTION_SEC_CTX",
"BR_REPLY",
"BR_ACQUIRE_RESULT",
"BR_DEAD_REPLY",
"BR_TRANSACTION_COMPLETE",
"BR_INCREFS",
"BR_ACQUIRE",
"BR_RELEASE",
"BR_DECREFS",
"BR_ATTEMPT_ACQUIRE",
"BR_NOOP",
"BR_SPAWN_LOOPER",
"BR_FINISHED",
"BR_DEAD_BINDER",
"BR_CLEAR_DEATH_NOTIFICATION_DONE",
"BR_FAILED_REPLY",
"BR_FROZEN_REPLY",
"BR_ONEWAY_SPAM_SUSPECT",
"BR_TRANSACTION_PENDING_FROZEN",
"BR_FROZEN_BINDER",
"BR_CLEAR_FREEZE_NOTIFICATION_DONE",
};
9.2.4 Transaction Data Structure¶
Each BC_TRANSACTION and BR_TRANSACTION carries a binder_transaction_data:
struct binder_transaction_data {
union {
__u32 handle; /* target: handle (proxy side) */
binder_uintptr_t ptr; /* target: binder (local node) */
} target;
binder_uintptr_t cookie; /* target object cookie */
__u32 code; /* transaction command (interface-specific) */
__u32 flags; /* TF_ONE_WAY, TF_ACCEPT_FDS, etc. */
pid_t sender_pid; /* filled in by driver */
uid_t sender_euid; /* filled in by driver */
binder_size_t data_size; /* number of bytes of data */
binder_size_t offsets_size; /* number of bytes of offsets */
union {
struct {
binder_uintptr_t buffer; /* pointer to transaction data */
binder_uintptr_t offsets; /* pointer to offsets array */
} ptr;
__u8 buf[8];
} data;
};
The sender_pid and sender_euid fields are filled in by the kernel driver,
not by userspace. This is what makes Binder identity unforgeable.
9.2.5 Complete Transaction Flow¶
The following diagram shows the full lifecycle of a synchronous Binder transaction:
sequenceDiagram
participant Client as Client Process
participant KD as Kernel Binder Driver
participant Server as Server Process
Note over Client: Prepare Parcel with data
Client->>KD: ioctl(BINDER_WRITE_READ)<br/>BC_TRANSACTION {handle, code, data}
Note over KD: Copy data into Server's mmap buffer<br/>Set sender_pid, sender_euid
KD-->>Client: BR_TRANSACTION_COMPLETE
Note over Client: Blocked in waitForResponse()
KD->>Server: BR_TRANSACTION {ptr, code, data, sender_pid, sender_euid}
Note over Server: Dispatch to BBinder::onTransact()
Server->>KD: ioctl(BINDER_WRITE_READ)<br/>BC_REPLY {data}
KD-->>Server: BR_TRANSACTION_COMPLETE
KD->>Client: BR_REPLY {data}
Note over Client: Unblocked, reads reply Parcel
For oneway (asynchronous) transactions, the flow is shorter:
sequenceDiagram
participant Client as Client Process
participant KD as Kernel Binder Driver
participant Server as Server Process
Client->>KD: ioctl(BINDER_WRITE_READ)<br/>BC_TRANSACTION {handle, code, data, TF_ONE_WAY}
KD-->>Client: BR_TRANSACTION_COMPLETE
Note over Client: Returns immediately<br/>(no BR_REPLY expected)
Note over KD: Queues transaction<br/>in Server's async queue
KD->>Server: BR_TRANSACTION {ptr, code, data}
Note over Server: Processes asynchronously<br/>No reply sent
9.2.6 Memory Mapping and Buffer Management¶
When ProcessState opens the binder driver, it calls mmap():
// frameworks/native/libs/binder/ProcessState.cpp
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
This 1 MB (minus guard pages) buffer is mapped read-only in userspace -- only
the kernel can write into it. The driver allocates sub-regions within this
buffer for incoming transactions. After the receiver processes a transaction,
it must issue BC_FREE_BUFFER to release the buffer back to the driver.
This buffer size is a hard limit on the total size of all concurrent incoming
transactions. If a process has too many pending transactions, the buffer fills
up and new transactions will fail with FAILED_TRANSACTION. This is why the
system logs a warning when binder buffer utilization is high.
9.2.7 Reference Counting¶
The driver maintains reference counts on binder nodes. Four commands manage references:
| Command | Effect |
|---|---|
BC_INCREFS |
Increment weak reference count |
BC_ACQUIRE |
Increment strong reference count |
BC_RELEASE |
Decrement strong reference count |
BC_DECREFS |
Decrement weak reference count |
In IPCThreadState.cpp:
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~996)
void IPCThreadState::incStrongHandle(int32_t handle, BpBinder *proxy)
{
LOG_REMOTEREFS("IPCThreadState::incStrongHandle(%d)\n", handle);
mOut.writeInt32(BC_ACQUIRE);
mOut.writeInt32(handle);
// ...
}
void IPCThreadState::decStrongHandle(int32_t handle)
{
LOG_REMOTEREFS("IPCThreadState::decStrongHandle(%d)\n", handle);
mOut.writeInt32(BC_RELEASE);
mOut.writeInt32(handle);
flushIfNeeded();
}
When a strong reference count drops to zero and there are no weak references, the kernel driver cleans up the node.
9.2.8 Death Notifications¶
When a process dies, the kernel driver iterates all references held to binder
nodes in that process and sends BR_DEAD_BINDER to each process that
registered a death notification:
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~1050)
status_t IPCThreadState::requestDeathNotification(int32_t handle, BpBinder* proxy)
{
mOut.writeInt32(BC_REQUEST_DEATH_NOTIFICATION);
mOut.writeInt32((int32_t)handle);
mOut.writePointer((uintptr_t)proxy);
return NO_ERROR;
}
9.2.9 Frozen Process Notifications¶
Android 14+ added process freezing support. When a process is frozen (e.g., a cached app in the freezer cgroup), the driver can notify clients:
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~1066)
status_t IPCThreadState::addFrozenStateChangeCallback(int32_t handle, BpBinder* proxy) {
static bool isSupported =
ProcessState::isDriverFeatureEnabled(
ProcessState::DriverFeature::FREEZE_NOTIFICATION);
if (!isSupported) {
return INVALID_OPERATION;
}
proxy->getWeakRefs()->incWeak(proxy);
mOut.writeInt32(BC_REQUEST_FREEZE_NOTIFICATION);
mOut.writeInt32((int32_t)handle);
mOut.writePointer((uintptr_t)proxy);
// ...
}
The FrozenStateChangeCallback interface lets clients react:
// frameworks/native/libs/binder/include/binder/IBinder.h
class FrozenStateChangeCallback : public virtual RefBase {
public:
enum class State {
FROZEN,
UNFROZEN,
};
virtual void onStateChanged(const wp<IBinder>& who, State state) = 0;
};
9.2.10 Thread Pool Management¶
The driver manages a pool of threads in each process. When all existing threads
are busy handling transactions and a new transaction arrives, the driver sends
BR_SPAWN_LOOPER to tell the process to create a new thread. The maximum is
configured by:
// frameworks/native/libs/binder/ProcessState.cpp (line ~451)
status_t ProcessState::setThreadPoolMaxThreadCount(size_t maxThreads) {
LOG_ALWAYS_FATAL_IF(mThreadPoolStarted && maxThreads < mMaxThreads,
"Binder threadpool cannot be shrunk after starting");
status_t result = NO_ERROR;
if (ioctl(mDriverFD, BINDER_SET_MAX_THREADS, &maxThreads) != -1) {
mMaxThreads = maxThreads;
} else {
result = -errno;
ALOGE("Binder ioctl to set max threads failed: %s", strerror(-result));
}
return result;
}
The default maximum is 15 threads:
9.2.11 Becoming the Context Manager¶
Only one process per binder domain can become the "context manager" -- the
process that holds handle 0. This is how servicemanager registers itself:
// frameworks/native/libs/binder/ProcessState.cpp (line ~234)
bool ProcessState::becomeContextManager()
{
std::unique_lock<std::mutex> _l(mLock);
flat_binder_object obj {
.flags = FLAT_BINDER_FLAG_TXN_SECURITY_CTX,
};
int result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR_EXT, &obj);
// fallback to original method
if (result != 0) {
android_errorWriteLog(0x534e4554, "121035042");
int unused = 0;
result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR, &unused);
}
// ...
return result == 0;
}
The FLAT_BINDER_FLAG_TXN_SECURITY_CTX flag requests that the driver include
the SELinux security context in every transaction to the context manager.
9.3 libbinder (C++ and Rust)¶
Source directory: frameworks/native/libs/binder/
This directory contains approximately 80 source files implementing the userspace Binder framework. The key classes form a clear hierarchy:
classDiagram
class RefBase {
<<abstract>>
}
class IBinder {
<<abstract>>
+transact(code, data, reply, flags)*
+linkToDeath(recipient)*
+queryLocalInterface(descriptor)*
+localBinder()* BBinder*
+remoteBinder()* BpBinder*
}
class BBinder {
+transact(code, data, reply, flags)
#onTransact(code, data, reply, flags)*
+setRequestingSid(bool)
+setExtension(IBinder)
}
class BpBinder {
+transact(code, data, reply, flags)
+sendObituary()
-mHandle : Handle
-mObituaries : Vector~Obituary~
}
class IInterface {
<<abstract>>
+asBinder()*
}
class BnInterface~T~ {
+queryLocalInterface()
}
class BpInterface~T~ {
}
class BpRefBase {
#remote() IBinder*
}
RefBase <|-- IBinder
IBinder <|-- BBinder
IBinder <|-- BpBinder
RefBase <|-- IInterface
IInterface <|-- BnInterface
BBinder <|-- BnInterface
IInterface <|-- BpInterface
BpRefBase <|-- BpInterface
RefBase <|-- BpRefBase
9.3.1 IBinder -- The Base Interface¶
IBinder is the abstract base class for all binder objects. Its most important
member is transact():
// frameworks/native/libs/binder/include/binder/IBinder.h (line ~186)
virtual status_t transact(uint32_t code,
const Parcel& data,
Parcel* reply,
uint32_t flags = 0) = 0;
It also defines the well-known transaction codes:
// frameworks/native/libs/binder/include/binder/IBinder.h (line ~54)
enum {
FIRST_CALL_TRANSACTION = 0x00000001,
LAST_CALL_TRANSACTION = 0x00ffffff,
PING_TRANSACTION = B_PACK_CHARS('_', 'P', 'N', 'G'),
DUMP_TRANSACTION = B_PACK_CHARS('_', 'D', 'M', 'P'),
SHELL_COMMAND_TRANSACTION = B_PACK_CHARS('_', 'C', 'M', 'D'),
INTERFACE_TRANSACTION = B_PACK_CHARS('_', 'N', 'T', 'F'),
EXTENSION_TRANSACTION = B_PACK_CHARS('_', 'E', 'X', 'T'),
DEBUG_PID_TRANSACTION = B_PACK_CHARS('_', 'P', 'I', 'D'),
SET_RPC_CLIENT_TRANSACTION = B_PACK_CHARS('_', 'R', 'P', 'C'),
FLAG_ONEWAY = 0x00000001,
FLAG_CLEAR_BUF = 0x00000020,
FLAG_PRIVATE_VENDOR = 0x10000000,
};
The B_PACK_CHARS macro encodes four ASCII characters into a 32-bit integer,
creating human-readable-in-hex transaction codes (_PNG, _DMP, etc.). These
are "meta-transactions" understood by all binder objects.
Interface-specific transactions use codes starting from
FIRST_CALL_TRANSACTION (1). AIDL numbers methods sequentially from this base.
9.3.2 BBinder -- The Server-Side Object¶
BBinder represents a local binder object -- one that lives in the current
process. It is the server side.
// frameworks/native/libs/binder/include/binder/Binder.h (line ~31)
class BBinder : public IBinder {
public:
BBinder();
virtual const String16& getInterfaceDescriptor() const;
virtual bool isBinderAlive() const;
virtual status_t pingBinder();
// transact() is final -- it calls onTransact()
virtual status_t transact(uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags = 0) final;
protected:
virtual ~BBinder();
// Subclasses override this to handle transactions
virtual status_t onTransact(uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags = 0);
};
The transact() method is marked final -- derived classes override
onTransact() instead. This is a Template Method pattern: transact() handles
meta-transactions (ping, dump, shell command, etc.) and delegates
interface-specific calls to onTransact().
The size of these classes is carefully controlled and enforced with
static_assert:
// frameworks/native/libs/binder/Binder.cpp (line ~54)
#ifdef __LP64__
static_assert(sizeof(IBinder) == 24);
static_assert(sizeof(BBinder) == 40);
#else
static_assert(sizeof(IBinder) == 12);
static_assert(sizeof(BBinder) == 20);
#endif
These are frozen because BBinder is part of the ABI used by prebuilt vendor
libraries.
9.3.3 BpBinder -- The Client-Side Proxy¶
BpBinder is a proxy to a binder object in another process. It holds a kernel
handle (an integer) or an RPC session reference:
// frameworks/native/libs/binder/include/binder/BpBinder.h (line ~180)
struct BinderHandle {
int32_t handle;
};
struct RpcHandle {
sp<RpcSession> session;
uint64_t address;
};
using Handle = std::variant<BinderHandle, RpcHandle>;
When you call transact() on a BpBinder, it delegates to
IPCThreadState::transact(), which packages the data and sends it to the
kernel:
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~919)
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
// ...
flags |= TF_ACCEPT_FDS;
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, nullptr);
if (err != NO_ERROR) {
if (reply) reply->setError(err);
return (mLastError = err);
}
if ((flags & TF_ONE_WAY) == 0) {
// Synchronous: wait for reply
if (reply) {
err = waitForResponse(reply);
} else {
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}
} else {
// Oneway: just wait for TRANSACTION_COMPLETE
err = waitForResponse(nullptr, nullptr);
}
return err;
}
Binder Proxy Throttling¶
BpBinder includes sophisticated proxy count tracking to prevent binder proxy leaks (a common cause of system instability):
// frameworks/native/libs/binder/BpBinder.cpp (line ~71)
uint32_t BpBinder::sBinderProxyCountHighWatermark = 2500;
uint32_t BpBinder::sBinderProxyCountLowWatermark = 2000;
uint32_t BpBinder::sBinderProxyCountWarningWatermark = 2250;
When a process accumulates more than 2500 binder proxy references (typically due to a leak), the system fires a callback that can kill the offending process.
9.3.4 ProcessState -- Per-Process Singleton¶
ProcessState is a singleton that manages the binder driver connection for
the entire process:
// frameworks/native/libs/binder/ProcessState.cpp (line ~106)
sp<ProcessState> ProcessState::self()
{
return init(kDefaultDriver, false /*requireDefault*/);
}
It opens the binder driver, mmaps the buffer, and manages the handle-to-object mapping. Key responsibilities:
-
Driver initialization: Opens
/dev/binder(or/dev/vndbinder), mmaps the transaction buffer. -
Handle table: Maps kernel handles to
BpBinderobjects: -
Context object: Handle 0 is the context manager (
servicemanager): -
Thread pool: Spawns and manages binder threads:
-
Fork safety: Binder cannot be used after
fork()because the kernel driver state is per-process.ProcessStateinstallspthread_atforkhandlers:
9.3.5 IPCThreadState -- Per-Thread State¶
IPCThreadState is a thread-local object that manages the actual
communication with the binder driver:
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~374)
IPCThreadState* IPCThreadState::self()
{
if (gHaveTLS.load(std::memory_order_acquire)) {
restart:
const pthread_key_t k = gTLS;
IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);
if (st) return st;
return new IPCThreadState;
}
// ...first-time TLS setup...
}
Key members:
// frameworks/native/libs/binder/include/binder/IPCThreadState.h (line ~240)
const sp<ProcessState> mProcess;
Vector<BBinder*> mPendingStrongDerefs;
Vector<RefBase::weakref_type*> mPendingWeakDerefs;
Parcel mIn; // incoming data from driver
Parcel mOut; // outgoing data to driver
pid_t mCallingPid;
const char* mCallingSid;
uid_t mCallingUid;
int32_t mWorkSource;
The mIn and mOut Parcel objects act as write and read buffers for
BINDER_WRITE_READ ioctls. They are initialized with a 256-byte capacity:
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~1127)
IPCThreadState::IPCThreadState()
: mProcess(ProcessState::self()),
// ...
{
pthread_setspecific(gTLS, this);
clearCaller();
mIn.setDataCapacity(256);
mOut.setDataCapacity(256);
}
9.3.6 The Thread Pool Loop¶
When a thread joins the binder thread pool, it enters a loop that processes transactions:
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~839)
void IPCThreadState::joinThreadPool(bool isMain)
{
mProcess->mCurrentThreads++;
mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
mIsLooper = true;
status_t result;
do {
processPendingDerefs();
// now get the next command to be processed, waiting if necessary
result = getAndExecuteCommand();
// Let this thread exit the thread pool if it is no longer
// needed and it is not the main process thread.
if(result == TIMED_OUT && !isMain) {
break;
}
} while (result != -ECONNREFUSED && result != -EBADF);
mOut.writeInt32(BC_EXIT_LOOPER);
mIsLooper = false;
// ...
}
The difference between BC_ENTER_LOOPER (main thread) and
BC_REGISTER_LOOPER (spawned thread) tells the driver that the main thread
should never time out, while spawned threads can be retired.
9.3.7 Transaction Execution¶
When a transaction arrives, getAndExecuteCommand() reads it and dispatches:
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~730)
status_t IPCThreadState::getAndExecuteCommand()
{
status_t result;
int32_t cmd;
result = talkWithDriver();
if (result >= NO_ERROR) {
size_t IN = mIn.dataAvail();
if (IN < sizeof(int32_t)) return result;
cmd = mIn.readInt32();
size_t newThreadsCount =
mProcess->mExecutingThreadsCount.fetch_add(1) + 1;
// ...starvation detection...
result = executeCommand(cmd);
// ...thread count bookkeeping...
}
return result;
}
The starvation detection is notable: if all threads are busy for more than 100ms, the system logs an error:
if (starvationTime > 100ms) {
ALOGE("binder thread pool (%zu threads) starved for %" PRId64 " ms",
maxThreads, to_ms(starvationTime));
}
9.3.8 Caller Identity Management¶
A critical feature of Binder is the ability to temporarily clear the caller identity to perform privileged operations on behalf of a caller:
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~562)
int64_t IPCThreadState::clearCallingIdentity()
{
int64_t token = packCallingIdentity(mHasExplicitIdentity,
mCallingUid, mCallingPid);
clearCaller();
mHasExplicitIdentity = true;
return token;
}
The identity is packed into a 64-bit token:
This is the Binder.clearCallingIdentity() / Binder.restoreCallingIdentity()
pattern used ubiquitously in system_server.
9.3.9 IInterface and the Template Pattern¶
IInterface is the base class for typed Binder interfaces. The template
classes BnInterface<T> and BpInterface<T> create the server and client
sides:
// frameworks/native/libs/binder/include/binder/IInterface.h (line ~69)
template <typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder {
public:
virtual sp<IInterface> queryLocalInterface(const String16& _descriptor);
virtual const String16& getInterfaceDescriptor() const;
typedef INTERFACE BaseInterface;
protected:
virtual IBinder* onAsBinder();
};
template <typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase {
public:
explicit BpInterface(const sp<IBinder>& remote);
typedef INTERFACE BaseInterface;
protected:
virtual IBinder* onAsBinder();
};
The interface_cast<> template converts an IBinder to a typed interface:
template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
return INTERFACE::asInterface(obj);
}
9.3.10 The Parcel Class¶
Parcel is the serialization container for Binder transactions. It holds
typed data, binder object references, and file descriptors:
// frameworks/native/libs/binder/include/binder/Parcel.h (line ~64)
class Parcel {
friend class IPCThreadState;
friend class RpcState;
public:
Parcel();
~Parcel();
const uint8_t* data() const;
size_t dataSize() const;
size_t dataAvail() const;
size_t dataPosition() const;
// ...
};
Parcels support writing primitives (writeInt32, writeFloat, writeString16),
binder references (writeStrongBinder), file descriptors
(writeFileDescriptor), and complex types (writeParcelable).
9.3.11 Rust Binder¶
Source directory: frameworks/native/libs/binder/rust/
Android supports writing Binder services in Rust through a safe wrapper around the NDK binder library. The key types mirror the C++ hierarchy:
// frameworks/native/libs/binder/rust/src/proxy.rs
/// A strong reference to a Binder remote object.
/// This struct encapsulates the generic C++ `sp<IBinder>` class.
pub struct SpIBinder(ptr::NonNull<sys::AIBinder>);
// frameworks/native/libs/binder/rust/src/native.rs
/// Rust wrapper around Binder remotable objects.
/// Implements the C++ `BBinder` class.
#[repr(C)]
pub struct Binder<T: Remotable> {
ibinder: *mut sys::AIBinder,
rust_object: *mut T,
}
The Interface trait is the Rust equivalent of IInterface:
// frameworks/native/libs/binder/rust/src/binder.rs
/// Super-trait for Binder interfaces.
/// This is equivalent `IInterface` in C++.
pub trait Interface: Send + Sync + DowncastSync {
fn as_binder(&self) -> SpIBinder {
panic!("This object was not a Binder object and cannot be converted into an SpIBinder.")
}
fn dump(&self, _writer: &mut dyn Write, _args: &[&CStr]) -> Result<()> {
Ok(())
}
}
The AIDL compiler generates Rust code that uses the declare_binder_interface!
macro:
// frameworks/native/libs/binder/rust/src/lib.rs (example from docs)
declare_binder_interface! {
ITest["android.os.ITest"] {
native: BnTest(on_transact),
proxy: BpTest,
}
}
The Rust binder library is built on top of the NDK binder API
(libbinder_ndk), which makes it usable in APEX modules that cannot depend on
the platform's libbinder.so.
9.3.12 The Complete Class Hierarchy¶
graph TD
subgraph "Server Side (BBinder)"
A[IBinder] --> B[BBinder]
B --> C["BnInterface<IFoo>"]
C --> D["BnFoo (generated)"]
D --> E["FooImpl (user code)"]
end
subgraph "Client Side (BpBinder)"
A --> F[BpBinder]
G[BpRefBase] --> H["BpInterface<IFoo>"]
H --> I["BpFoo (generated)"]
end
subgraph "AIDL Interface"
J[IInterface] --> K[IFoo]
K --> C
K --> H
end
subgraph "Process Infrastructure"
L[ProcessState] --> M["Opens /dev/binder<br/>mmaps buffer<br/>handle table"]
N[IPCThreadState] --> O["Thread-local<br/>mIn / mOut parcels<br/>talkWithDriver()"]
end
9.4 AIDL Code Generation¶
Source directory: system/tools/aidl/
AIDL (Android Interface Definition Language) is the primary way to define Binder
interfaces. The AIDL compiler translates .aidl files into Java, C++, NDK
C++, and Rust stubs.
9.4.1 AIDL Compiler Architecture¶
The AIDL compiler is a single binary that handles all backend targets:
system/tools/aidl/
├── aidl.cpp # Main entry point
├── aidl_language.h # AST definitions
├── aidl_language_l.ll # Lexer (flex)
├── aidl_language_y.yy # Parser (bison)
├── aidl_to_java.cpp # Java backend
├── aidl_to_java.h
├── aidl_to_cpp.cpp # C++ backend
├── aidl_to_cpp.h
├── aidl_to_ndk.cpp # NDK C++ backend
├── aidl_to_ndk.h
├── aidl_to_rust.cpp # Rust backend
├── aidl_to_rust.h
├── generate_java.cpp # Java code generation
├── generate_cpp.cpp # C++ code generation
├── generate_ndk.cpp # NDK code generation
├── generate_rust.cpp # Rust code generation
├── aidl_checkapi.cpp # API compatibility checking
├── aidl_dumpapi.cpp # API dumping
└── ...
flowchart LR
A[".aidl file"] --> B["Lexer<br/>(flex)"]
B --> C["Parser<br/>(bison)"]
C --> D["AST<br/>(AidlDocument)"]
D --> E{"Backend?"}
E -->|Java| F["generate_java.cpp"]
E -->|C++| G["generate_cpp.cpp"]
E -->|NDK| H["generate_ndk.cpp"]
E -->|Rust| I["generate_rust.cpp"]
F --> J["IFoo.java<br/>IFoo.Stub<br/>IFoo.Stub.Proxy"]
G --> K["IFoo.h<br/>BnFoo.h<br/>BpFoo.h<br/>IFoo.cpp"]
H --> L["NDK headers<br/>+ sources"]
I --> M["IFoo.rs"]
9.4.2 AIDL Syntax¶
A typical AIDL interface definition:
// android/os/IServiceManager.aidl
package android.os;
import android.os.IServiceCallback;
interface IServiceManager {
// Get a binder by name, blocking if not found
IBinder getService(String name);
// Check without blocking
IBinder checkService(String name);
// Register a service
void addService(String name, IBinder service,
boolean allowIsolated, int dumpPriority);
// List registered services
String[] listServices(int dumpPriority);
// Register for notifications when a service is added
void registerForNotifications(String name,
IServiceCallback callback);
// Check if a service is declared in VINTF manifest
boolean isDeclared(String name);
}
9.4.3 AIDL Type Mapping¶
AIDL types map to different target types per backend:
| AIDL Type | Java | C++ | Rust |
|---|---|---|---|
boolean |
boolean |
bool |
bool |
byte |
byte |
int8_t |
i8 |
char |
char |
char16_t |
u16 |
int |
int |
int32_t |
i32 |
long |
long |
int64_t |
i64 |
float |
float |
float |
f32 |
double |
double |
double |
f64 |
String |
String |
String16 |
String |
IBinder |
IBinder |
sp<IBinder> |
SpIBinder |
FileDescriptor |
FileDescriptor |
unique_fd |
OwnedFd |
ParcelFileDescriptor |
ParcelFileDescriptor |
ParcelFileDescriptor |
ParcelFileDescriptor |
T[] |
T[] |
vector<T> |
Vec<T> |
List<T> |
List<T> |
vector<T> |
Vec<T> |
Map |
Map |
-- (not supported) | -- |
The C++ backend helpers are defined in:
// system/tools/aidl/aidl_to_cpp.h
std::string CppNameOf(const AidlTypeSpecifier& type,
const AidlTypenames& typenames);
std::string ParcelReadMethodOf(const AidlTypeSpecifier& type,
const AidlTypenames& typenames);
std::string ParcelWriteMethodOf(const AidlTypeSpecifier& type,
const AidlTypenames& typenames);
9.4.4 Direction Specifiers: in, out, inout¶
AIDL method parameters can have direction specifiers that control marshalling:
interface IFoo {
void process(in ParcelFileDescriptor input,
out ParcelFileDescriptor output,
inout Bundle data);
}
| Direction | Meaning | Generated Code |
|---|---|---|
in (default) |
Data flows from client to server | Client writes, server reads |
out |
Data flows from server to client | Server writes, client reads from reply |
inout |
Data flows both ways | Client writes, server reads + writes, client reads reply |
Primitive types are always in. The out and inout specifiers are only valid
for parcelable types, arrays, and other non-primitive types.
9.4.5 oneway Methods¶
Methods marked oneway are fire-and-forget -- the client does not wait for a
reply:
In an oneway interface, ALL methods must be oneway. Alternatively,
individual methods can be marked:
interface IFoo {
void syncMethod(); // synchronous
oneway void asyncNotify(int x); // asynchronous
}
Oneway calls:
- Return immediately after the driver queues the transaction
- Cannot return values or throw exceptions to the caller
- Are executed serially per-binder-object (the driver queues them)
- Use
TF_ONE_WAYflag in the kernel
9.4.6 Parcelable Types¶
AIDL supports structured parcelable types:
// Structured parcelable (AIDL-defined)
parcelable ConnectionInfo {
String ipAddress;
int port;
}
// Unstructured parcelable (Java-only, defined elsewhere)
parcelable Bundle;
Structured parcelables are fully defined in AIDL and the compiler generates
complete serialization code for all backends. Unstructured parcelables are
opaque references to Java classes that implement Parcelable.
9.4.7 Generated Code: Java¶
For an interface IFoo, the Java backend generates:
IFoo.java
├── interface IFoo extends android.os.IInterface
├── static class Stub extends android.os.Binder implements IFoo
│ └── static class Proxy implements IFoo
└── static class Default implements IFoo
The Stub class is the server side. Its onTransact() unmarshalls the incoming
parcel and dispatches to the appropriate method:
// Generated code (simplified)
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
switch (code) {
case TRANSACTION_getService: {
data.enforceInterface(DESCRIPTOR);
String _arg0 = data.readString();
IBinder _result = this.getService(_arg0);
reply.writeNoException();
reply.writeStrongBinder(_result);
return true;
}
// ...
}
return super.onTransact(code, data, reply, flags);
}
The Stub.Proxy class is the client side. Each method marshalls arguments into
a Parcel and calls transact():
// Generated code (simplified)
@Override
public IBinder getService(String name) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(name);
mRemote.transact(TRANSACTION_getService, _data, _reply, 0);
_reply.readException();
return _reply.readStrongBinder();
} finally {
_reply.recycle();
_data.recycle();
}
}
9.4.8 Generated Code: C++¶
The C++ backend generates header and implementation files:
IFoo.h - Pure virtual interface
BnFoo.h - Server-side stub (extends BnInterface<IFoo>)
BpFoo.h - Client-side proxy (extends BpInterface<IFoo>)
IFoo.cpp - Implementation of BnFoo::onTransact() and BpFoo methods
The code generation context is defined in:
// system/tools/aidl/aidl_to_cpp.h
struct CodeGeneratorContext {
CodeWriter& writer;
const AidlTypenames& types;
const AidlTypeSpecifier& type;
const string name;
const bool isPointer;
};
The generated BpFoo methods call remote()->transact():
// Generated code (simplified)
::android::binder::Status BpFoo::getService(
const ::std::string& name,
::android::sp<::android::IBinder>* _aidl_return) {
::android::Parcel _aidl_data;
_aidl_data.markForBinder(remoteStrong());
_aidl_data.writeInterfaceToken(getInterfaceDescriptor());
_aidl_data.writeUtf8AsUtf16(name);
::android::Parcel _aidl_reply;
::android::status_t _aidl_ret_status = remote()->transact(
BnFoo::TRANSACTION_getService, _aidl_data, &_aidl_reply, 0);
// ...read reply...
}
9.4.9 Generated Code: Rust¶
The Rust backend generates implementations of the AIDL interface trait:
// Generated code (simplified)
impl IFoo for BpFoo {
fn getService(&self, name: &str) -> binder::Result<Option<SpIBinder>> {
let _aidl_data = self.build_parcel_getService(name)?;
let _aidl_reply = self.binder.submit_transact(
transactions::getService,
_aidl_data,
binder::binder_impl::FLAG_PRIVATE_LOCAL,
);
self.read_response_getService(name, _aidl_reply)
}
}
9.4.10 NDK Backend vs CPP Backend¶
AIDL generates two different C++ backends:
CPP Backend (libbinder):
- Links against
libbinder.so(platform library) - Uses
sp<IBinder>,Parcel,BBinder,BpBinder - Can only be used in the platform (not in APEX modules)
- Has access to all libbinder features
NDK Backend (libbinder_ndk):
- Links against
libbinder_ndk.so(NDK stable library) - Uses
AIBinder,AParcel, NDK types - Can be used in APEX modules (stable ABI)
- Wraps libbinder_ndk C API in C++ wrappers
- This is what the Rust binder library uses underneath
The build system chooses the backend based on the backend configuration:
aidl_interface {
name: "android.hardware.foo",
backend: {
cpp: {
enabled: true, // generates libbinder (platform) code
},
ndk: {
enabled: true, // generates libbinder_ndk (APEX-safe) code
},
java: {
enabled: true,
},
rust: {
enabled: true,
},
},
}
For HAL services (which may live in APEX modules), the NDK backend is required. For system_server services, the CPP backend is typically used.
9.4.11 Enum and Constant Declarations¶
AIDL supports enums and constants:
@Backing(type="int")
enum Status {
OK = 0,
ERROR = 1,
UNAVAILABLE = 2,
}
interface IFoo {
const int MAX_SIZE = 1024;
const String DESCRIPTOR = "android.hardware.foo.IFoo";
Status getStatus();
}
The @Backing annotation specifies the underlying integer type. Without it,
the default backing type is byte for AIDL enums.
9.4.12 Union Types¶
AIDL supports tagged unions:
In C++, this generates a class with a tag enum and accessor methods. Only one variant is active at a time.
9.4.13 Nullable Types¶
AIDL supports nullable reference types with the @nullable annotation:
interface IFoo {
@nullable IBinder getOptionalService();
void process(@nullable String optionalName);
}
In C++, nullable types are represented as std::optional<T> or as nullable
pointers. In Java, they map to normal nullable references. In Rust, they map
to Option<T>.
9.4.14 Annotations¶
AIDL supports several annotations that affect code generation:
| Annotation | Applies To | Effect |
|---|---|---|
@nullable |
Parameters, return values | Allows null values |
@utf8InCpp |
String types | Use std::string instead of String16 |
@Backing(type=T) |
Enum | Specifies backing integer type |
@VintfStability |
Interface, parcelable | Marks as VINTF-stable |
@Hide |
Methods, fields | Hidden from SDK |
@JavaPassthrough |
Any | Pass annotation through to Java |
@Enforce("perm") |
Methods | Generate permission check |
@PropagateAllowBlocking |
Methods | Allow blocking from oneway callers |
@SuppressWarnings |
Any | Suppress AIDL warnings |
@JavaOnlyStableParcelable |
Parcelable | Java-only stable parcelable |
@JavaDefault |
Interface | Generate default implementation |
@Descriptor |
Interface | Override interface descriptor |
9.4.15 API Versioning and Stability¶
AIDL supports stable interfaces that maintain backward compatibility across Android releases. The build system tracks API surfaces:
aidl_api/
└── android.os.IServiceManager/
├── 1/
│ └── android/os/IServiceManager.aidl
├── 2/
│ └── android/os/IServiceManager.aidl
└── current/
└── android/os/IServiceManager.aidl
The aidl_checkapi.cpp tool verifies that new versions are backward-compatible:
- Methods can only be added (never removed or reordered)
- Method signatures cannot change
- Parcelable fields can only be appended
- Enum values can only be added
9.4.16 Transaction ID Assignment¶
Each method in an AIDL interface gets a transaction code starting from
FIRST_CALL_TRANSACTION:
// system/tools/aidl/aidl_to_cpp.h
std::string GetTransactionIdFor(const std::string& clazz,
const AidlMethod& method);
Methods are numbered sequentially in declaration order:
| Method | Transaction Code |
|---|---|
| First method | FIRST_CALL_TRANSACTION + 0 = 1 |
| Second method | FIRST_CALL_TRANSACTION + 1 = 2 |
| Third method | FIRST_CALL_TRANSACTION + 2 = 3 |
| ... | ... |
This sequential numbering is why AIDL stable interfaces cannot reorder methods.
9.4.17 The AIDL Compilation Pipeline¶
flowchart TD
A["IFoo.aidl"] --> B["AIDL Compiler"]
B --> C{"Language"}
C -->|Java| D["IFoo.java"]
C -->|CPP| E["IFoo.h + BnFoo + BpFoo + IFoo.cpp"]
C -->|NDK| F["aidl/IFoo.h + IFoo.cpp (NDK)"]
C -->|Rust| G["IFoo.rs"]
D --> H["javac"] --> I["IFoo.class"]
E --> J["clang++"] --> K["libbinder service"]
F --> L["clang++ (NDK)"] --> M["APEX module"]
G --> N["rustc"] --> O["Rust binder service"]
subgraph "Build System (Soong)"
P["aidl_interface { }"] --> B
P --> Q["API freeze / check"]
end
9.5 servicemanager¶
Source directory: frameworks/native/cmds/servicemanager/
The servicemanager is the first service that starts in Android. It is the
name-server for all Binder services: processes register services by name, and
clients look them up by name.
9.5.1 Architecture Overview¶
graph TD
subgraph "servicemanager process"
SM["ServiceManager<br/>(BnServiceManager)"]
AC["Access Control<br/>(SELinux)"]
LO["Looper"]
BC["BinderCallback"]
CC["ClientCallbackCallback"]
end
subgraph "Kernel"
BD["/dev/binder<br/>(context manager)"]
end
subgraph "Server Process"
SRV["Service Implementation"]
end
subgraph "Client Process"
CLI["Client App"]
end
SRV -->|"addService(name, binder)"| BD
BD -->|"BR_TRANSACTION"| SM
SM -->|"canAdd() check"| AC
CLI -->|"getService(name)"| BD
BD -->|"BR_TRANSACTION"| SM
SM -->|"canFind() check"| AC
SM -->|"return binder handle"| BD
BD -->|"BR_REPLY"| CLI
LO --> BC
LO --> CC
9.5.2 Startup Sequence¶
The servicemanager is started by init very early in boot. Its init.rc:
# frameworks/native/cmds/servicemanager/servicemanager.rc
service servicemanager /system/bin/servicemanager
class core animation
user system
group system readproc
critical
file /dev/kmsg w
onrestart setprop servicemanager.ready false
onrestart restart --only-if-running apexd
onrestart restart audioserver
onrestart restart gatekeeperd
onrestart class_restart --only-enabled main
onrestart class_restart --only-enabled hal
onrestart class_restart --only-enabled early_hal
task_profiles ProcessCapacityHigh
shutdown critical
The critical flag means the system will reboot if servicemanager crashes
too many times. The onrestart triggers restart all dependent services.
The main() function in main.cpp:
// frameworks/native/cmds/servicemanager/main.cpp (line ~146)
int main(int argc, char** argv) {
android::base::InitLogging(argv, android::base::KernelLogger);
const char* driver = argc == 2 ? argv[1] : "/dev/binder";
sp<ProcessState> ps = ProcessState::initWithDriver(driver);
ps->setThreadPoolMaxThreadCount(0);
ps->setCallRestriction(ProcessState::CallRestriction::FATAL_IF_NOT_ONEWAY);
IPCThreadState::self()->disableBackgroundScheduling(true);
sp<ServiceManager> manager =
sp<ServiceManager>::make(std::make_unique<Access>());
manager->setRequestingSid(true);
if (!manager->addService("manager", manager,
false /*allowIsolated*/,
IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()) {
LOG(ERROR) << "Could not self register servicemanager";
}
IPCThreadState::self()->setTheContextObject(manager);
if (!ps->becomeContextManager()) {
LOG(FATAL) << "Could not become context manager";
}
sp<Looper> looper = Looper::prepare(false /*allowNonCallbacks*/);
sp<BinderCallback> binderCallback = BinderCallback::setupTo(looper);
ClientCallbackCallback::setupTo(looper, manager, binderCallback);
if (!SetProperty("servicemanager.ready", "true")) {
LOG(ERROR) << "Failed to set servicemanager ready property";
}
while(true) {
looper->pollAll(-1);
}
}
Key initialization steps:
- Open the driver with
ProcessState::initWithDriver("/dev/binder") - Set max threads to 0 -- servicemanager uses a single-threaded event loop
- Set FATAL_IF_NOT_ONEWAY -- servicemanager must never make blocking calls
- Create the ServiceManager with an
Accessobject for SELinux checks - Enable SID requests (
setRequestingSid(true)) so every transaction includes the caller's SELinux context - Become the context manager via
becomeContextManager() - Enter the event loop using
Looper::pollAll(-1)
The BinderCallback uses IPCThreadState::setupPolling() to get a file
descriptor for the binder driver, then adds it to the Looper:
// frameworks/native/cmds/servicemanager/main.cpp (line ~59)
class BinderCallback : public LooperCallback {
public:
static sp<BinderCallback> setupTo(const sp<Looper>& looper) {
sp<BinderCallback> cb = sp<BinderCallback>::make();
cb->mLooper = looper;
IPCThreadState::self()->setupPolling(&cb->mBinderFd);
LOG_ALWAYS_FATAL_IF(cb->mBinderFd < 0,
"Failed to setupPolling: %d", cb->mBinderFd);
int ret = looper->addFd(cb->mBinderFd, Looper::POLL_CALLBACK,
Looper::EVENT_INPUT, cb, nullptr);
LOG_ALWAYS_FATAL_IF(ret != 1,
"Failed to add binder FD to Looper");
return cb;
}
int handleEvent(int, int, void*) override {
IPCThreadState::self()->handlePolledCommands();
return 1; // Continue receiving callbacks.
}
};
9.5.3 The ServiceManager Class¶
The ServiceManager class extends BnServiceManager (generated from AIDL)
and implements the DeathRecipient interface:
// frameworks/native/cmds/servicemanager/ServiceManager.h (line ~41)
class ServiceManager : public os::BnServiceManager,
public IBinder::DeathRecipient {
public:
ServiceManager(std::unique_ptr<Access>&& access);
~ServiceManager();
binder::Status getService(const std::string& name,
sp<IBinder>* outBinder) override;
binder::Status checkService(const std::string& name,
sp<IBinder>* outBinder) override;
binder::Status addService(const std::string& name,
const sp<IBinder>& binder,
bool allowIsolated,
int32_t dumpPriority) override;
binder::Status listServices(int32_t dumpPriority,
std::vector<std::string>* outList) override;
binder::Status registerForNotifications(
const std::string& name,
const sp<IServiceCallback>& callback) override;
binder::Status isDeclared(const std::string& name,
bool* outReturn) override;
// ...
void binderDied(const wp<IBinder>& who) override;
private:
struct Service {
sp<IBinder> binder; // not null
bool allowIsolated;
int32_t dumpPriority;
bool hasClients = false;
bool guaranteeClient = false;
Access::CallingContext ctx; // process that registered this
ssize_t getNodeStrongRefCount();
~Service();
};
using ServiceMap = std::map<std::string, Service>;
ServiceMap mNameToService;
// ...
std::unique_ptr<Access> mAccess;
};
9.5.4 Service Registration (addService)¶
When a server process calls addService():
// frameworks/native/cmds/servicemanager/ServiceManager.cpp (line ~512)
Status ServiceManager::addService(const std::string& name,
const sp<IBinder>& binder,
bool allowIsolated,
int32_t dumpPriority) {
auto ctx = mAccess->getCallingContext();
// Security: Only system UIDs can register services
if (multiuser_get_app_id(ctx.uid) >= AID_APP) {
return Status::fromExceptionCode(Status::EX_SECURITY,
"App UIDs cannot add services.");
}
// SELinux: Check if this caller can add this service name
std::optional<std::string> accessorName;
if (auto status = canAddService(ctx, name, &accessorName);
!status.isOk()) {
return status;
}
if (binder == nullptr) {
return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
"Null binder.");
}
if (!isValidServiceName(name)) {
return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
"Invalid service name.");
}
// VINTF: For HAL services, verify VINTF manifest declaration
if (!meetsDeclarationRequirements(ctx, binder, name)) {
return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT,
"VINTF declaration error.");
}
// Register for death notification to clean up when server dies
if (binder->remoteBinder() != nullptr &&
binder->linkToDeath(sp<ServiceManager>::fromExisting(this)) != OK) {
return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE,
"Couldn't linkToDeath.");
}
// Store the service
mNameToService[name] = Service{
.binder = binder,
.allowIsolated = allowIsolated,
.dumpPriority = dumpPriority,
.ctx = ctx,
};
// Notify any processes waiting for this service
// (via registerForNotifications)
// ...
return Status::ok();
}
Service name validation is strict:
// frameworks/native/cmds/servicemanager/ServiceManager.cpp (line ~494)
bool isValidServiceName(const std::string& name) {
if (name.size() == 0) return false;
if (name.size() > 127) return false;
for (char c : name) {
if (c == '_' || c == '-' || c == '.' || c == '/') continue;
if (c >= 'a' && c <= 'z') continue;
if (c >= 'A' && c <= 'Z') continue;
if (c >= '0' && c <= '9') continue;
return false;
}
return true;
}
9.5.5 Service Lookup (getService / checkService)¶
// frameworks/native/cmds/servicemanager/ServiceManager.cpp (line ~395)
Status ServiceManager::getService(const std::string& name,
sp<IBinder>* outBinder) {
*outBinder = tryGetBinder(name, true).service;
return Status::ok();
}
Status ServiceManager::checkService(const std::string& name,
sp<IBinder>* outBinder) {
*outBinder = tryGetBinder(name, false).service;
return Status::ok();
}
The difference: getService() passes startIfNotFound=true, which tries to
start the service via init if it is not running. checkService() returns
immediately (null if not found).
9.5.6 SELinux Access Control¶
Every service manager operation is gated by SELinux:
// frameworks/native/cmds/servicemanager/Access.cpp (line ~130)
bool Access::canFind(const CallingContext& ctx, const std::string& name) {
return actionAllowedFromLookup(ctx, name, "find");
}
bool Access::canAdd(const CallingContext& ctx, const std::string& name) {
return actionAllowedFromLookup(ctx, name, "add");
}
bool Access::canList(const CallingContext& ctx) {
return actionAllowed(ctx, mThisProcessContext, "list", "service_manager");
}
The actual check uses selinux_check_access():
// frameworks/native/cmds/servicemanager/Access.cpp (line ~142)
bool Access::actionAllowed(const CallingContext& sctx, const char* tctx,
const char* perm, const std::string& tname) {
const char* tclass = "service_manager";
AuditCallbackData data = {
.context = &sctx,
.tname = &tname,
};
return 0 == selinux_check_access(sctx.sid.c_str(), tctx, tclass, perm,
reinterpret_cast<void*>(&data));
}
The calling context is obtained from IPCThreadState:
// frameworks/native/cmds/servicemanager/Access.cpp (line ~113)
Access::CallingContext Access::getCallingContext() {
IPCThreadState* ipc = IPCThreadState::self();
const char* callingSid = ipc->getCallingSid();
pid_t callingPid = ipc->getCallingPid();
return CallingContext {
.debugPid = callingPid,
.uid = ipc->getCallingUid(),
.sid = callingSid ? std::string(callingSid)
: getPidcon(callingPid),
};
}
9.5.7 VINTF Manifest Integration¶
For HAL services, servicemanager verifies that the service is declared in the
VINTF manifest:
// frameworks/native/cmds/servicemanager/ServiceManager.cpp (line ~342)
static bool meetsDeclarationRequirements(const Access::CallingContext& ctx,
const sp<IBinder>& binder,
const std::string& name) {
if (!Stability::requiresVintfDeclaration(binder)) {
return true;
}
return isVintfDeclared(ctx, name);
}
This ensures that HAL services are properly declared in device manifest files, preventing ad-hoc service registration.
9.5.8 Client Callback Support¶
Servicemanager includes a timer-based system to track whether services have active clients (used for lazy services):
// frameworks/native/cmds/servicemanager/main.cpp (line ~92)
class ClientCallbackCallback : public LooperCallback {
// Fires every 5 seconds
int handleEvent(int fd, int, void*) override {
uint64_t expirations;
int ret = read(fd, &expirations, sizeof(expirations));
mManager->handleClientCallbacks();
mBinderCallback->repoll();
return 1;
}
};
9.5.9 dumpsys Integration¶
The dumpsys command-line tool communicates with servicemanager to list
services and dump their state. When you run:
This:
- Calls
servicemanager.getService("activity")to get the ActivityManager binder - Calls
IBinder::DUMP_TRANSACTIONon that binder - The service writes its state to the provided file descriptor
The listServices() call in servicemanager returns services filtered by
dump priority:
// From ServiceManager.h
binder::Status listServices(int32_t dumpPriority,
std::vector<std::string>* outList) override;
Dump priorities allow dumpsys to dump critical services first:
static const int DUMP_FLAG_PRIORITY_CRITICAL = 1 << 0;
static const int DUMP_FLAG_PRIORITY_HIGH = 1 << 1;
static const int DUMP_FLAG_PRIORITY_NORMAL = 1 << 2;
static const int DUMP_FLAG_PRIORITY_DEFAULT = 1 << 3;
9.5.10 Service Registration Flow (Complete)¶
sequenceDiagram
participant SP as Server Process
participant BD as /dev/binder
participant SM as servicemanager
participant SE as SELinux
SP->>BD: ProcessState::initWithDriver("/dev/binder")
Note over SP: Opens /dev/binder, mmaps buffer
SP->>SP: Create MyService : BnMyService
SP->>BD: transact(handle=0, addService)<br/>name="my.service", binder=MyService
BD->>SM: BR_TRANSACTION (addService)
SM->>SM: getCallingContext()<br/>Extract UID, PID, SID
SM->>SE: selinux_check_access(sid, "add", "my.service")
SE-->>SM: ALLOWED
SM->>SM: isValidServiceName("my.service") = true
SM->>SM: meetsDeclarationRequirements() = true
SM->>SM: linkToDeath(MyService)
SM->>SM: mNameToService["my.service"] = Service{binder}
SM->>SM: Notify registered callbacks
SM->>BD: BC_REPLY (Status::ok())
BD->>SP: BR_REPLY (success)
SP->>SP: ProcessState::startThreadPool()
SP->>SP: IPCThreadState::joinThreadPool()
Note over SP: Ready to receive transactions
9.5.11 Service Lookup Flow (Complete)¶
sequenceDiagram
participant CP as Client Process
participant BD as /dev/binder
participant SM as servicemanager
participant SE as SELinux
participant SP as Server Process
CP->>CP: defaultServiceManager()
Note over CP: Gets BpServiceManager for handle 0
CP->>BD: transact(handle=0, getService)<br/>name="my.service"
BD->>SM: BR_TRANSACTION (getService)
SM->>SM: getCallingContext()
SM->>SE: selinux_check_access(sid, "find", "my.service")
SE-->>SM: ALLOWED
SM->>SM: lookup mNameToService["my.service"]
SM->>SM: Found! Get binder handle
SM->>BD: BC_REPLY (binder handle for my.service)
BD->>CP: BR_REPLY (handle=N for my.service)
Note over CP: ProcessState::getStrongProxyForHandle(N)
Note over CP: Creates BpBinder(N)
Note over CP: interface_cast<IMyService>(binder)<br/>Returns BpMyService
CP->>BD: transact(handle=N, myMethod, data)
BD->>SP: BR_TRANSACTION (myMethod, data)
SP->>SP: BnMyService::onTransact() -> myMethod()
SP->>BD: BC_REPLY (result)
BD->>CP: BR_REPLY (result)
9.5.12 vndservicemanager¶
The vendor service manager is the same binary compiled with different flags:
# frameworks/native/cmds/servicemanager/vndservicemanager.rc
service vndservicemanager /vendor/bin/vndservicemanager /dev/vndbinder
class core
user system
group system readproc
file /dev/kmsg w
task_profiles ServiceCapacityLow
onrestart class_restart main
onrestart class_restart hal
onrestart class_restart early_hal
shutdown critical
It uses /dev/vndbinder instead of /dev/binder, creating a completely
separate namespace for vendor services. The VNDK (Vendor NDK) build
configuration ensures vendor libraries use /dev/vndbinder by default:
#ifdef __ANDROID_VNDK__
const char* kDefaultDriver = "/dev/vndbinder";
#else
const char* kDefaultDriver = "/dev/binder";
#endif
9.5.13 LazyServiceRegistrar¶
For services that should only run when they have clients, Android provides
LazyServiceRegistrar:
// frameworks/native/libs/binder/include/binder/LazyServiceRegistrar.h
class LazyServiceRegistrar {
public:
static LazyServiceRegistrar& getInstance();
status_t registerService(
const sp<IBinder>& service,
const std::string& name = "default",
bool allowIsolated = false,
int dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT);
void forcePersist(bool persist);
void setActiveServicesCallback(
const std::function<bool(bool)>& activeServicesCallback);
bool tryUnregister();
void reRegister();
};
When all clients disconnect, the lazy service shuts down. When a client requests the service, init restarts it. This is used for HAL services that are expensive to keep running when idle.
9.5.14 waitForService and Efficient Polling¶
The recommended way to obtain a service is waitForService, which uses
registerForNotifications to block efficiently rather than polling:
// frameworks/native/libs/binder/include/binder/IServiceManager.h
template<typename INTERFACE>
sp<INTERFACE> waitForService(const String16& name) {
const sp<IServiceManager> sm = defaultServiceManager();
return interface_cast<INTERFACE>(sm->waitForService(name));
}
For VINTF-declared services:
template<typename INTERFACE>
sp<INTERFACE> waitForVintfService(
const String16& instance = String16("default")) {
return waitForDeclaredService<INTERFACE>(
INTERFACE::descriptor + String16("/") + instance);
}
9.6 hwservicemanager and HIDL Binder¶
Source directory: system/hwservicemanager/
9.6.1 The Three Binder Domains¶
Android uses three separate binder driver instances to enforce isolation between the framework, HAL, and vendor components:
graph TB
subgraph "Framework Domain"
A[Apps] <-->|"/dev/binder"| B[system_server]
B <-->|"/dev/binder"| C[servicemanager]
end
subgraph "HAL Domain (deprecated)"
D["Framework<br/>clients"] <-->|"/dev/hwbinder"| E[HAL services]
E <-->|"/dev/hwbinder"| F[hwservicemanager]
end
subgraph "Vendor Domain"
G["Vendor<br/>processes"] <-->|"/dev/vndbinder"| H[Vendor services]
H <-->|"/dev/vndbinder"| I[vndservicemanager]
end
style A fill:#e1f5fe
style B fill:#e1f5fe
style C fill:#e1f5fe
style D fill:#fff3e0
style E fill:#fff3e0
style F fill:#fff3e0
style G fill:#e8f5e9
style H fill:#e8f5e9
style I fill:#e8f5e9
| Domain | Device | Context Manager | Interface Language | Status |
|---|---|---|---|---|
| Framework | /dev/binder |
servicemanager |
AIDL | Active |
| HAL | /dev/hwbinder |
hwservicemanager |
HIDL | Deprecated (Android 13+) |
| Vendor | /dev/vndbinder |
vndservicemanager |
AIDL | Active |
9.6.2 Why Three Domains?¶
The three-domain architecture was introduced with Project Treble (Android 8.0) to enforce the vendor/framework boundary:
-
Framework domain (
/dev/binder): Used for all communication between apps and system services. Only framework processes should register here. -
HAL domain (
/dev/hwbinder): Used for communication between the framework and vendor HAL implementations. Uses HIDL (HAL Interface Definition Language) for stable binary interfaces. -
Vendor domain (
/dev/vndbinder): Used for vendor-internal communication that does not cross the Treble boundary.
SELinux policy enforces these boundaries -- a vendor process cannot open
/dev/binder, and a framework process should not open /dev/vndbinder.
9.6.3 hwservicemanager¶
The hwservicemanager manages HIDL services on /dev/hwbinder:
# system/hwservicemanager/hwservicemanager.rc
service hwservicemanager /system/system_ext/bin/hwservicemanager
user system
disabled
group system readproc
critical
onrestart setprop hwservicemanager.ready false
onrestart class_restart --only-enabled main
onrestart class_restart --only-enabled hal
onrestart class_restart --only-enabled early_hal
task_profiles ServiceCapacityLow HighPerformance
class animation
shutdown critical
Note the disabled keyword -- on newer devices that have migrated all HALs to
AIDL, hwservicemanager is not started at all.
The hwservicemanager uses the HIDL IServiceManager interface:
// system/hwservicemanager/ServiceManager.h
struct ServiceManager : public V1_2::IServiceManager,
hidl_death_recipient {
Return<sp<IBase>> get(const hidl_string& fqName,
const hidl_string& name) override;
Return<bool> add(const hidl_string& name,
const sp<IBase>& service) override;
Return<Transport> getTransport(const hidl_string& fqName,
const hidl_string& name);
Return<void> list(list_cb _hidl_cb) override;
Return<void> listByInterface(const hidl_string& fqInstanceName,
listByInterface_cb _hidl_cb) override;
Return<bool> registerForNotifications(
const hidl_string& fqName,
const hidl_string& name,
const sp<IServiceNotification>& callback) override;
// ...
};
9.6.4 HIDL vs AIDL¶
| Feature | HIDL | AIDL |
|---|---|---|
| Transport | /dev/hwbinder |
/dev/binder or /dev/vndbinder |
| Service naming | package@version::IInterface/instance |
package.IInterface/instance |
| Versioning | Package-level (@1.0, @1.1) |
Method-level (append only) |
| Language support | C++, Java | C++, Java, NDK C++, Rust |
| Status | Deprecated | Active, recommended |
| Passthrough mode | Supported | Not applicable |
HIDL used Fully Qualified Names (FQN) like:
AIDL uses dot-separated names:
9.6.5 The Migration from HIDL to AIDL¶
Starting with Android 13, all new HAL interfaces must use AIDL. Existing HIDL interfaces are being migrated to AIDL over successive releases. The migration path:
- Define the new AIDL interface in
hardware/interfaces/ - Implement the service using AIDL
- Register with
servicemanagerinstead ofhwservicemanager - Update VINTF manifest from
hidlformat toaidlformat - Eventually remove the HIDL interface
Services that have migrated from HIDL to AIDL use /dev/binder and register
with the regular servicemanager, but their names are validated against the
VINTF manifest.
9.6.6 Passthrough HALs¶
HIDL supported a "passthrough" mode where the HAL was loaded directly into the client process as a shared library (no IPC). This was used for performance- critical HALs like the graphics HAL. AIDL does not support passthrough mode -- all communication is via Binder IPC. The passthrough functionality is replaced by a direct dlopen mechanism:
// frameworks/native/libs/binder/include/binder/IServiceManager.h
void* openDeclaredPassthroughHal(const String16& interface,
const String16& instance, int flag);
9.6.7 RPC Binder¶
Android 12+ introduced RPC Binder (/dev/binder over sockets) for
cross-device and VM communication. This uses the same libbinder interfaces
but transports data over TCP/Unix sockets instead of the kernel driver:
// frameworks/native/libs/binder/include/binder/RpcServer.h
class RpcServer : public virtual RefBase {
public:
static sp<RpcServer> make(
std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory = nullptr);
// ...
};
BpBinder uses the std::variant<BinderHandle, RpcHandle> to transparently
support both kernel binder and RPC binder:
// frameworks/native/libs/binder/include/binder/BpBinder.h
struct BinderHandle {
int32_t handle;
};
struct RpcHandle {
sp<RpcSession> session;
uint64_t address;
};
using Handle = std::variant<BinderHandle, RpcHandle>;
9.7 Try It: Write a Binder Service¶
This section walks through creating a complete Binder service and client. We will create a simple "echo" service that demonstrates the full lifecycle.
9.7.1 Step 1: Define the AIDL Interface¶
Create the AIDL file:
// hardware/interfaces/example/echo/aidl/android/hardware/echo/IEchoService.aidl
package android.hardware.echo;
interface IEchoService {
/** Echo back the input string */
String echo(in String input);
/** Return the number of echo calls made */
int getCallCount();
/** Fire-and-forget notification */
oneway void ping();
}
9.7.2 Step 2: Build Configuration¶
Create the Android.bp for the AIDL interface:
// hardware/interfaces/example/echo/aidl/Android.bp
aidl_interface {
name: "android.hardware.echo",
vendor_available: true,
srcs: ["android/hardware/echo/*.aidl"],
stability: "vintf",
backend: {
cpp: {
enabled: true,
},
java: {
enabled: true,
},
rust: {
enabled: true,
},
},
}
9.7.3 Step 3: Implement the Service (C++)¶
// hardware/interfaces/example/echo/aidl/default/EchoService.h
#pragma once
#include <aidl/android/hardware/echo/BnEchoService.h>
#include <atomic>
namespace aidl::android::hardware::echo {
class EchoService : public BnEchoService {
public:
// Synchronous: echo back the input
ndk::ScopedAStatus echo(const std::string& input,
std::string* _aidl_return) override {
mCallCount++;
*_aidl_return = "Echo: " + input;
return ndk::ScopedAStatus::ok();
}
// Synchronous: return call count
ndk::ScopedAStatus getCallCount(int32_t* _aidl_return) override {
*_aidl_return = mCallCount.load();
return ndk::ScopedAStatus::ok();
}
// Oneway: no reply needed
ndk::ScopedAStatus ping() override {
ALOGI("Ping received! Call count: %d", mCallCount.load());
return ndk::ScopedAStatus::ok();
}
private:
std::atomic<int32_t> mCallCount{0};
};
} // namespace aidl::android::hardware::echo
9.7.4 Step 4: Service Main Entry Point¶
// hardware/interfaces/example/echo/aidl/default/main.cpp
#include "EchoService.h"
#include <android-base/logging.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
using aidl::android::hardware::echo::EchoService;
int main() {
// Initialize the binder thread pool
ABinderProcess_setThreadPoolMaxThreadCount(0);
// Create the service
std::shared_ptr<EchoService> echo =
ndk::SharedRefBase::make<EchoService>();
// Register with servicemanager
const std::string instance =
std::string() + EchoService::descriptor + "/default";
binder_status_t status = AServiceManager_addService(
echo->asBinder().get(), instance.c_str());
CHECK_EQ(status, STATUS_OK)
<< "Failed to register " << instance;
LOG(INFO) << "EchoService registered as " << instance;
// Join the thread pool (blocks forever)
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
// Should not reach here
LOG(FATAL) << "EchoService exited unexpectedly";
return EXIT_FAILURE;
}
9.7.5 Step 5: Build Configuration for the Service¶
// hardware/interfaces/example/echo/aidl/default/Android.bp
cc_binary {
name: "android.hardware.echo-service",
relative_install_path: "hw",
vendor: true,
srcs: ["main.cpp"],
shared_libs: [
"libbase",
"libbinder_ndk",
"android.hardware.echo-V1-ndk",
],
}
9.7.6 Step 6: Init Configuration¶
// hardware/interfaces/example/echo/aidl/default/echo-service.rc
service vendor.echo /vendor/bin/hw/android.hardware.echo-service
class hal
user system
group system
9.7.7 Step 7: VINTF Manifest Entry¶
Add to the device manifest:
<hal format="aidl">
<name>android.hardware.echo</name>
<version>1</version>
<fqname>IEchoService/default</fqname>
</hal>
9.7.8 Step 8: Write the Client¶
// A simple client that calls the echo service
#include <aidl/android/hardware/echo/IEchoService.h>
#include <android/binder_manager.h>
#include <android-base/logging.h>
using aidl::android::hardware::echo::IEchoService;
int main() {
// Get the service
const std::string instance =
std::string() + IEchoService::descriptor + "/default";
std::shared_ptr<IEchoService> service =
IEchoService::fromBinder(
ndk::SpAIBinder(AServiceManager_waitForService(
instance.c_str())));
CHECK(service != nullptr) << "Failed to get " << instance;
// Make an echo call
std::string result;
auto status = service->echo("Hello, Binder!", &result);
CHECK(status.isOk()) << "echo failed: "
<< status.getDescription();
LOG(INFO) << "Echo result: " << result;
// Get call count
int32_t count;
status = service->getCallCount(&count);
CHECK(status.isOk());
LOG(INFO) << "Call count: " << count;
// Send a oneway ping (returns immediately)
status = service->ping();
CHECK(status.isOk());
LOG(INFO) << "Ping sent (oneway)";
return 0;
}
9.7.9 Step 9: Implement in Rust¶
The same service in Rust:
// Rust service implementation
use binder::BinderFeatures;
use android_hardware_echo::aidl::android::hardware::echo::IEchoService::{
BnEchoService, IEchoService,
};
use std::sync::atomic::{AtomicI32, Ordering};
struct EchoService {
call_count: AtomicI32,
}
impl binder::Interface for EchoService {}
impl IEchoService for EchoService {
fn echo(&self, input: &str) -> binder::Result<String> {
self.call_count.fetch_add(1, Ordering::Relaxed);
Ok(format!("Echo: {}", input))
}
fn getCallCount(&self) -> binder::Result<i32> {
Ok(self.call_count.load(Ordering::Relaxed))
}
fn ping(&self) -> binder::Result<()> {
log::info!("Ping received! Count: {}",
self.call_count.load(Ordering::Relaxed));
Ok(())
}
}
fn main() {
binder::ProcessState::start_thread_pool();
let service = EchoService {
call_count: AtomicI32::new(0),
};
let service_binder = BnEchoService::new_binder(
service,
BinderFeatures::default(),
);
binder::add_service(
&format!("{}/default", <BnEchoService as IEchoService>::get_descriptor()),
service_binder.as_binder(),
).expect("Failed to register service");
binder::ProcessState::join_thread_pool();
}
9.7.10 Step 10: Implement the Client in Java¶
// Java client for the echo service
import android.hardware.echo.IEchoService;
import android.os.IBinder;
import android.os.ServiceManager;
import android.util.Log;
public class EchoClient {
private static final String TAG = "EchoClient";
private static final String SERVICE_NAME =
"android.hardware.echo.IEchoService/default";
public static void main(String[] args) {
// Get the service from service manager
IBinder binder = ServiceManager.waitForService(SERVICE_NAME);
if (binder == null) {
Log.e(TAG, "Failed to get echo service");
return;
}
// Convert to typed interface
IEchoService service = IEchoService.Stub.asInterface(binder);
if (service == null) {
Log.e(TAG, "Failed to cast to IEchoService");
return;
}
try {
// Make a synchronous echo call
String result = service.echo("Hello from Java!");
Log.i(TAG, "Echo result: " + result);
// Get the call count
int count = service.getCallCount();
Log.i(TAG, "Call count: " + count);
// Send a oneway ping
service.ping();
Log.i(TAG, "Ping sent");
} catch (android.os.RemoteException e) {
Log.e(TAG, "Remote exception: " + e.getMessage());
}
}
}
Under the hood, IEchoService.Stub.asInterface(binder) checks if the binder
is a local object (same process) or a remote proxy:
- If local, it returns the actual
IEchoServiceimplementation directly (zero-copy, no IPC) - If remote, it wraps it in
IEchoService.Stub.Proxythat marshalls calls through binder
This is the queryLocalInterface() optimization that avoids unnecessary
serialization for in-process calls.
9.7.11 Step 11: Handle Death Notifications¶
// C++ example: Register for death notifications
class MyDeathRecipient : public android::IBinder::DeathRecipient {
public:
void binderDied(const android::wp<android::IBinder>& who) override {
ALOGE("Echo service died! Attempting to reconnect...");
// Reconnect logic here
}
};
// In client code:
sp<MyDeathRecipient> deathRecipient = sp<MyDeathRecipient>::make();
status_t status = binder->linkToDeath(deathRecipient);
if (status != OK) {
ALOGE("Failed to link to death: %d", status);
}
Death notifications are essential for robust client implementations. When the server process crashes, the client receives the notification and can attempt to reconnect or clean up resources.
9.7.12 Step 12: Debugging Your Service¶
List all registered services:
Check if your service is registered:
Call a service method from the command line:
adb shell service call android.hardware.echo.IEchoService/default \
1 s16 "Hello"
# 1 = FIRST_CALL_TRANSACTION (echo method)
# s16 = String16 argument
Dump service state:
View binder debug info:
adb shell cat /sys/kernel/debug/binder/stats
adb shell cat /sys/kernel/debug/binder/transactions
adb shell cat /sys/kernel/debug/binder/state
View binder calls with systrace/perfetto:
adb shell perfetto -o /data/misc/perfetto-traces/trace \
-c - <<EOF
buffers: {
size_kb: 63488
}
data_sources: {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "binder/*"
}
}
}
duration_ms: 5000
EOF
9.7.13 Common Pitfalls¶
-
Binder thread pool not started. If you forget
ABinderProcess_startThreadPool(), your service will register but never respond to transactions. -
Blocking in oneway methods. Oneway methods should return quickly. Long-running work should be posted to a separate worker thread.
-
Binder buffer overflow. The 1 MB mmap buffer is shared among all pending incoming transactions. Sending large data (e.g., big bitmaps) through Binder is an anti-pattern -- use
ashmemorParcelFileDescriptorinstead. -
Binder proxy leak. Accumulating too many
BpBinderreferences without releasing them triggers the proxy throttle (watermark at 2500). This typically manifests asJavaBinder: !!! FAILED BINDER TRANSACTION !!!. -
Missing VINTF declaration. HAL services that do not have a VINTF manifest entry will fail to register with an
EX_ILLEGAL_ARGUMENT. -
Wrong binder domain. Vendor processes default to
/dev/vndbinder. If you accidentally register on the wrong domain, clients in other domains cannot find your service. -
Fork after binder use.
ProcessStateinstalls fork handlers that invalidate the binder FD in the child. Using Binder afterfork()will crash:
9.7.14 Architecture of a Complete Binder Service¶
graph TD
subgraph "Service Process"
direction TB
M["main()"] --> PS["ProcessState::initWithDriver()"]
PS --> TB["Open /dev/binder<br/>mmap 1MB buffer"]
M --> SVC["Create EchoService<br/>(extends BnEchoService)"]
SVC --> REG["addService('echo', binder)"]
REG --> SM_CALL["Transact to handle 0<br/>(servicemanager)"]
M --> TP["startThreadPool()"]
TP --> JT["joinThreadPool()"]
JT --> LOOP["Loop: getAndExecuteCommand()"]
LOOP --> TW["talkWithDriver()<br/>ioctl(BINDER_WRITE_READ)"]
TW --> EX["executeCommand(BR_TRANSACTION)"]
EX --> OT["BnEchoService::onTransact()"]
OT --> EC["EchoService::echo()"]
EC --> REP["sendReply()"]
REP --> LOOP
end
subgraph "Client Process"
direction TB
CM["main()"] --> DSM["defaultServiceManager()"]
DSM --> WS["waitForService('echo')"]
WS --> IC["interface_cast<IEchoService>()"]
IC --> BP["BpEchoService::echo()"]
BP --> TR["remote()->transact()"]
TR --> IPT["IPCThreadState::transact()"]
IPT --> WTD["writeTransactionData()<br/>BC_TRANSACTION"]
WTD --> WFR["waitForResponse()"]
WFR --> RES["Read BR_REPLY<br/>Return result"]
end
9.8 Binder Internals: Deep Dive¶
This section provides a detailed walkthrough of the internal data flows and
state machines within libbinder, aimed at kernel and framework developers who
need to understand the exact code paths involved in a Binder transaction.
9.8.1 The writeTransactionData Function¶
This is where outgoing transaction data is formatted:
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~1387)
status_t IPCThreadState::writeTransactionData(int32_t cmd,
uint32_t binderFlags, int32_t handle, uint32_t code,
const Parcel& data, status_t* statusBuffer)
{
binder_transaction_data tr;
tr.target.ptr = 0;
tr.target.handle = handle;
tr.code = code;
tr.flags = binderFlags;
tr.cookie = 0;
tr.sender_pid = 0;
tr.sender_euid = 0;
const status_t err = data.errorCheck();
if (err == NO_ERROR) {
tr.data_size = data.ipcDataSize();
tr.data.ptr.buffer = data.ipcData();
tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
tr.data.ptr.offsets = data.ipcObjects();
} else if (statusBuffer) {
tr.flags |= TF_STATUS_CODE;
*statusBuffer = err;
tr.data_size = sizeof(status_t);
tr.data.ptr.buffer = reinterpret_cast<uintptr_t>(statusBuffer);
tr.offsets_size = 0;
tr.data.ptr.offsets = 0;
} else {
return (mLastError = err);
}
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}
Note that sender_pid and sender_euid are set to 0 -- the kernel driver
fills these in with the actual values.
9.8.2 The executeCommand Function (BR_TRANSACTION)¶
When a transaction arrives at the server, executeCommand() processes the
BR_TRANSACTION command. This is the most complex case:
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~1510)
case BR_TRANSACTION_SEC_CTX:
case BR_TRANSACTION:
{
binder_transaction_data_secctx tr_secctx;
binder_transaction_data& tr = tr_secctx.transaction_data;
if (cmd == (int) BR_TRANSACTION_SEC_CTX) {
result = mIn.read(&tr_secctx, sizeof(tr_secctx));
} else {
result = mIn.read(&tr, sizeof(tr));
tr_secctx.secctx = 0;
}
Parcel buffer;
buffer.ipcSetDataReference(
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t), freeBuffer);
// Save and set the caller identity
const pid_t origPid = mCallingPid;
const char* origSid = mCallingSid;
const uid_t origUid = mCallingUid;
mCallingPid = tr.sender_pid;
mCallingSid = reinterpret_cast<const char*>(tr_secctx.secctx);
mCallingUid = tr.sender_euid;
// Dispatch to the target binder object
if (tr.target.ptr) {
if (reinterpret_cast<RefBase::weakref_type*>(tr.target.ptr)
->attemptIncStrong(this)) {
BBinder* binder = reinterpret_cast<BBinder*>(tr.cookie);
error = doTransactBinder(binder, tr.code, buffer, &reply, tr.flags);
binder->decStrong(this);
}
} else {
// target.ptr == 0 means this is for the context manager
BBinder* binder = the_context_object.get();
error = doTransactBinder(binder, tr.code, buffer, &reply, tr.flags);
}
// For synchronous calls, send the reply
if ((tr.flags & TF_ONE_WAY) == 0) {
buffer.setDataSize(0); // Free buffer before reply
sendReply(reply, (tr.flags & kForwardReplyFlags));
}
// Restore caller identity
mCallingPid = origPid;
mCallingSid = origSid;
mCallingUid = origUid;
}
Key observations:
-
Identity setup: The caller's PID, UID, and SELinux SID are extracted from the transaction data and stored in thread-local state. This is what
getCallingPid(),getCallingUid(), andgetCallingSid()return. -
Strong reference acquisition: Before calling into the BBinder, the code attempts to promote a weak reference to a strong reference. This handles the race where the BBinder might be in the process of being destroyed.
-
Buffer management: The reply buffer is cleared (
buffer.setDataSize(0)) before sending the reply to avoid a race condition where the client receives the reply and sends another transaction before the original buffer is freed. -
Context manager dispatch: When
tr.target.ptris null, the transaction is directed to the context manager (the_context_object), which is theservicemanager.
The reference counting commands are also handled in executeCommand():
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~1444)
case BR_ACQUIRE:
refs = (RefBase::weakref_type*)mIn.readPointer();
obj = (BBinder*)mIn.readPointer();
obj->incStrong(mProcess.get());
mOut.writeInt32(BC_ACQUIRE_DONE);
mOut.writePointer((uintptr_t)refs);
mOut.writePointer((uintptr_t)obj);
break;
case BR_RELEASE:
refs = (RefBase::weakref_type*)mIn.readPointer();
obj = (BBinder*)mIn.readPointer();
mPendingStrongDerefs.push(obj);
break;
case BR_INCREFS:
refs = (RefBase::weakref_type*)mIn.readPointer();
obj = (BBinder*)mIn.readPointer();
refs->incWeak(mProcess.get());
mOut.writeInt32(BC_INCREFS_DONE);
mOut.writePointer((uintptr_t)refs);
mOut.writePointer((uintptr_t)obj);
break;
case BR_DECREFS:
refs = (RefBase::weakref_type*)mIn.readPointer();
obj = (BBinder*)mIn.readPointer();
mPendingWeakDerefs.push(refs);
break;
Notice that BR_RELEASE and BR_DECREFS do not immediately decrement the
reference counts. Instead, they are queued in mPendingStrongDerefs and
mPendingWeakDerefs and processed later by processPendingDerefs(). This
avoids potential deadlocks and ensures that destructors do not run while
the thread is in the middle of processing driver commands.
9.8.3 BBinder::transact and the Template Method Pattern¶
When a transaction reaches a BBinder, the transact() method (which is final)
handles meta-transactions and delegates to onTransact():
// frameworks/native/libs/binder/Binder.cpp (simplified)
status_t BBinder::transact(uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
data.setDataPosition(0);
if (reply != nullptr && (flags & FLAG_CLEAR_BUF)) {
reply->markSensitive();
}
switch (code) {
case PING_TRANSACTION:
err = pingBinder();
break;
case EXTENSION_TRANSACTION:
CHECK(googReply != nullptr);
err = reply->writeStrongBinder(getExtension());
break;
case DEBUG_PID_TRANSACTION:
err = reply->writeInt32(getDebugPid());
break;
case INTERFACE_TRANSACTION:
reply->writeString16(getInterfaceDescriptor());
err = NO_ERROR;
break;
case DUMP_TRANSACTION: {
int fd = data.readFileDescriptor();
// ...read args...
err = dump(fd, args);
break;
}
case SHELL_COMMAND_TRANSACTION: {
// ...handle shell command...
break;
}
default:
err = onTransact(code, data, reply, flags);
break;
}
if (reply != nullptr) {
reply->setDataPosition(0);
if (reply->dataSize() > LOG_SIZE) {
// ...log warning about large replies...
}
}
return err;
}
The AIDL-generated BnFoo::onTransact() is what dispatches to your specific
interface methods.
9.8.4 BBinder::Extras and the Lazy Initialization Pattern¶
BBinder uses lazy initialization for its "extras" -- optional metadata that most binder objects never need:
// frameworks/native/libs/binder/Binder.cpp (line ~294)
class BBinder::Extras {
public:
sp<IBinder> mExtension;
int mPolicy = SCHED_NORMAL;
int mPriority = 0;
bool mRequestingSid = false;
bool mInheritRt = false;
bool mRecordingOn = false;
RpcMutex mLock;
std::set<sp<RpcServerLink>> mRpcServerLinks;
BpBinder::ObjectManager mObjectMgr;
uint16_t mMinThreads = kDefaultMinThreads;
unique_fd mRecordingFd;
};
The Extras pointer is stored as an std::atomic<Extras*> and allocated on
first access via getOrCreateExtras(). This keeps the BBinder base class
small (40 bytes on LP64) since most binder objects never use extensions,
custom scheduling, or recording.
9.8.5 The waitForResponse Loop (Continued)¶
After sending a transaction, the thread enters a loop waiting for the reply:
// frameworks/native/libs/binder/IPCThreadState.cpp (line ~1163)
status_t IPCThreadState::waitForResponse(Parcel *reply,
status_t *acquireResult)
{
uint32_t cmd;
int32_t err;
while (1) {
if ((err=talkWithDriver()) < NO_ERROR) break;
err = mIn.errorCheck();
if (err < NO_ERROR) break;
if (mIn.dataAvail() == 0) continue;
cmd = (uint32_t)mIn.readInt32();
switch (cmd) {
case BR_TRANSACTION_COMPLETE:
if (!reply && !acquireResult) goto finish;
break;
case BR_DEAD_REPLY:
err = DEAD_OBJECT;
goto finish;
case BR_FAILED_REPLY:
err = FAILED_TRANSACTION;
goto finish;
case BR_FROZEN_REPLY:
err = FAILED_TRANSACTION;
goto finish;
case BR_REPLY: {
binder_transaction_data tr;
err = mIn.read(&tr, sizeof(tr));
if (reply) {
if ((tr.flags & TF_STATUS_CODE) == 0) {
reply->ipcSetDataReference(
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t),
freeBuffer);
} else {
err = *reinterpret_cast<const status_t*>(
tr.data.ptr.buffer);
freeBuffer(/*...*/);
}
}
goto finish;
}
default:
err = executeCommand(cmd);
if (err != NO_ERROR) goto finish;
break;
}
}
// ...
}
The default case is important: while waiting for a reply, the thread may
receive other commands from the driver (like BR_DEAD_BINDER death
notifications or nested BR_TRANSACTION calls). These are handled by
executeCommand().
9.8.6 Nested Transactions¶
Binder supports re-entrant calls. If process A calls process B, and B calls
back into A during the handling of A's request, the driver delivers the
callback to the same thread in A that is waiting for B's reply. This is
detected in waitForResponse() by the default case calling
executeCommand().
sequenceDiagram
participant A_T1 as Process A (Thread 1)
participant KD as Kernel Driver
participant B_T1 as Process B (Thread 1)
A_T1->>KD: BC_TRANSACTION (call B.foo())
KD->>B_T1: BR_TRANSACTION (foo)
Note over B_T1: B.foo() calls A.bar()
B_T1->>KD: BC_TRANSACTION (call A.bar())
Note over KD: Detects A_T1 is waiting<br/>Delivers to same thread
KD->>A_T1: BR_TRANSACTION (bar)
Note over A_T1: Handles bar() in<br/>waitForResponse() loop
A_T1->>KD: BC_REPLY (bar result)
KD->>B_T1: BR_REPLY (bar result)
Note over B_T1: foo() continues
B_T1->>KD: BC_REPLY (foo result)
KD->>A_T1: BR_REPLY (foo result)
Note over A_T1: Original call returns
9.8.7 Binder Context Object (Handle 0)¶
Handle 0 is special -- it always refers to the context manager
(servicemanager). When a process first needs to talk to servicemanager, it
calls:
// frameworks/native/libs/binder/ProcessState.cpp (line ~183)
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
sp<IBinder> context = getStrongProxyForHandle(0);
if (context) {
internal::Stability::markCompilationUnit(context.get());
}
return context;
}
The getStrongProxyForHandle(0) path has special handling -- it sends a
PING_TRANSACTION to ensure the context manager is alive before creating the
proxy:
// frameworks/native/libs/binder/ProcessState.cpp (line ~361)
if (handle == 0) {
// Special case for context manager...
IPCThreadState* ipc = IPCThreadState::self();
Parcel data;
status_t status = ipc->transact(
0, IBinder::PING_TRANSACTION, data, nullptr, 0);
if (status == DEAD_OBJECT)
return nullptr;
}
9.8.8 Stability Enforcement¶
The Stability class ensures that binder objects are not used across
incompatible domains:
// frameworks/native/libs/binder/include/binder/Stability.h
class Stability {
enum Level : int32_t {
UNDECLARED = 0, // Within a compilation unit
VENDOR = 0b000011, // Vendor stability
SYSTEM = 0b001100, // System stability
VINTF = 0b111111, // VINTF-stable (cross-partition)
};
};
A VINTF-stable binder can be used across the framework/vendor boundary. A
SYSTEM-stable binder can only be used within the system partition. This
prevents accidental use of unstable interfaces across partitions.
9.8.9 Parcel Internals¶
The Parcel class manages a flat byte buffer with an "objects" array that
tracks embedded binder references and file descriptors:
┌──────────────────────────────────────────┐
│ Parcel │
│ │
│ data: [int32 | string | binder | int32] │
│ ↑ ↑ │
│ objects: [ offset=12 ] │
│ │
│ The objects array stores offsets into │
│ the data buffer where flat_binder_obj │
│ structs are embedded. │
└──────────────────────────────────────────┘
When the kernel driver copies a Parcel, it processes the objects array to:
- Translate binder node references to handles (and vice versa)
- Duplicate file descriptors into the target process
- Maintain reference counts on binder nodes
9.8.10 The ProcessState Constructor¶
The full initialization of ProcessState opens the driver and mmaps:
// frameworks/native/libs/binder/ProcessState.cpp
ProcessState::ProcessState(const char* driver)
: mDriverName(String8(driver))
, mDriverFD(-1)
, mVMStart(MAP_FAILED)
, mExecutingThreadsCount(0)
, mMaxThreads(DEFAULT_MAX_BINDER_THREADS)
, mCurrentThreads(0)
, mKernelStartedThreads(0)
, mStarvationStartTime(never())
, mForked(false)
, mThreadPoolStarted(false)
, mThreadPoolSeq(1)
, mCallRestriction(CallRestriction::NONE)
{
base_fd fd(open(driver, O_RDWR | O_CLOEXEC));
if (fd.ok()) {
// ...
mVMStart = mmap(nullptr, BINDER_VM_SIZE,
PROT_READ,
MAP_PRIVATE | MAP_NORESERVE,
fd.get(), 0);
// ...
mDriverFD = fd.release();
}
}
The buffer is mapped PROT_READ only -- only the kernel can write to it.
9.8.11 Binder Caching¶
Recent versions of AOSP include a BinderCacheWithInvalidation that caches
service lookups to avoid repeated roundtrips to servicemanager:
// frameworks/native/libs/binder/BackendUnifiedServiceManager.h
class BinderCacheWithInvalidation
: public std::enable_shared_from_this<BinderCacheWithInvalidation> {
class BinderInvalidation : public IBinder::DeathRecipient {
public:
void binderDied(const wp<IBinder>& who) override {
sp<IBinder> binder = who.promote();
if (std::shared_ptr<BinderCacheWithInvalidation> cache =
mCache.lock()) {
cache->removeItem(mKey, binder);
}
}
};
struct Entry {
sp<IBinder> service;
sp<BinderInvalidation> deathRecipient;
};
public:
sp<IBinder> getItem(const std::string& key) const {
std::lock_guard<std::mutex> lock(mCacheMutex);
if (auto it = mCache.find(key); it != mCache.end()) {
return it->second.service;
}
return nullptr;
}
// ...
};
The cache automatically invalidates entries when the target service dies
(using linkToDeath). This is a significant performance optimization since
getService() calls are extremely frequent.
9.8.12 The defaultServiceManager() Singleton¶
The defaultServiceManager() function returns a cached reference to the
service manager:
// From IServiceManager.cpp
sp<IServiceManager> defaultServiceManager()
{
std::call_once(gSmOnce, []() {
sp<AidlServiceManager> sm = nullptr;
while (sm == nullptr) {
sm = interface_cast<AidlServiceManager>(
ProcessState::self()->getContextObject(nullptr));
if (sm == nullptr) {
ALOGE("Waiting 1s on context object on %s.",
ProcessState::self()->getDriverName().c_str());
sleep(1);
}
}
gDefaultServiceManager = sp<CppBackendShim>::make(
sp<BackendUnifiedServiceManager>::make(sm));
});
return gDefaultServiceManager;
}
This blocks until the service manager is available, with a 1-second retry loop.
This is why it is safe to call defaultServiceManager() very early in boot --
it will wait for servicemanager to start.
9.8.13 Flat Binder Objects¶
When a binder reference is serialized into a Parcel, it is written as a
flat_binder_object:
struct flat_binder_object {
struct binder_object_header hdr;
__u32 flags;
union {
binder_uintptr_t binder; /* local object */
__u32 handle; /* remote handle */
};
binder_uintptr_t cookie;
};
The kernel driver translates between local objects and remote handles during
copy: when process A sends a flat_binder_object containing a local BBinder
pointer, the driver converts it to a handle in process B's handle table. When
process B sends that handle back, the driver converts it back to the original
BBinder pointer.
This translation is transparent to userspace -- Parcel's writeStrongBinder()
and readStrongBinder() methods handle the serialization, and the kernel
handles the handle-to-pointer translation.
9.8.14 The Parcel Objects Array¶
A Parcel's "objects array" tracks the byte offsets of all embedded
flat_binder_object structures within the data buffer. When the kernel driver
copies a Parcel from one process to another, it:
- Copies the raw data buffer
- Walks the objects array
- For each offset, reads the
flat_binder_objectat that location - Translates binder references (local ptr <-> remote handle)
- Duplicates file descriptors into the target process
This is why Parcel::ipcObjectsCount() and Parcel::ipcObjects() exist:
// From writeTransactionData():
tr.offsets_size = data.ipcObjectsCount() * sizeof(binder_size_t);
tr.data.ptr.offsets = data.ipcObjects();
9.8.15 Transaction Flags¶
Several flags control transaction behavior:
| Flag | Value | Meaning |
|---|---|---|
TF_ONE_WAY |
0x01 | Asynchronous (fire-and-forget) |
TF_ROOT_OBJECT |
0x04 | Contents are the root object of a binder RPC |
TF_STATUS_CODE |
0x08 | Data is a status code (error reply) |
TF_ACCEPT_FDS |
0x10 | Allow file descriptors in the transaction |
TF_CLEAR_BUF |
0x20 | Clear the transaction buffer after use (for sensitive data) |
TF_UPDATE_TXN |
0x40 | Update an existing pending async transaction |
The TF_ACCEPT_FDS flag is always set by IPCThreadState::transact():
The TF_CLEAR_BUF flag is used for transactions containing sensitive data
(like passwords or encryption keys) -- it tells the kernel to zero out the
buffer after the transaction completes.
9.9 Advanced Topics¶
9.9.1 Binder Observers¶
The BinderObserver infrastructure (enabled via BINDER_WITH_OBSERVERS)
provides telemetry for binder transactions:
// frameworks/native/libs/binder/include/binder/ProcessState.h
#if defined(LIBBINDER_BINDER_OBSERVER) && defined(BINDER_WITH_KERNEL_IPC)
#define BINDER_WITH_OBSERVERS
#endif
When enabled, each IPCThreadState has a stats queue:
// frameworks/native/libs/binder/include/binder/IPCThreadState.h
#ifdef BINDER_WITH_OBSERVERS
std::shared_ptr<BinderStatsSpscQueue> mBinderStatsQueue;
#endif
9.9.2 Call Restrictions¶
ProcessState supports call restrictions to catch incorrect usage:
// frameworks/native/libs/binder/include/binder/ProcessState.h
enum class CallRestriction {
NONE, // all calls okay
ERROR_IF_NOT_ONEWAY, // log when calls are blocking
FATAL_IF_NOT_ONEWAY, // abort process on blocking calls
};
servicemanager uses FATAL_IF_NOT_ONEWAY because it must never make
blocking binder calls (to avoid deadlocks -- since all processes need
servicemanager, a blocking call from servicemanager could deadlock the system).
9.9.3 Background Scheduling¶
When a binder call arrives, the kernel may move the receiving thread to the background scheduling group to prevent priority inversion. This can be disabled:
// frameworks/native/libs/binder/IPCThreadState.cpp
void IPCThreadState::disableBackgroundScheduling(bool disable)
{
gDisableBackgroundScheduling.store(disable, std::memory_order_relaxed);
}
servicemanager disables background scheduling because it should always run
at high priority.
9.9.4 Scheduler Policy Inheritance¶
BBinder supports inheriting the caller's scheduler policy:
// frameworks/native/libs/binder/include/binder/Binder.h
void setMinSchedulerPolicy(int policy, int priority);
bool isInheritRt();
void setInheritRt(bool inheritRt);
When inheritRt is true and the caller is a real-time thread, the receiving
thread temporarily inherits the real-time scheduling policy for the duration
of the transaction. This is critical for audio and display pipelines.
9.9.5 Extensions¶
The extension mechanism allows attaching additional interfaces to a binder object without modifying its original interface:
// frameworks/native/libs/binder/include/binder/IBinder.h (line ~157)
status_t getExtension(sp<IBinder>* out);
Usage pattern (from the IBinder.h documentation):
// Server side:
sp<MyFoo> foo = new MyFoo; // AOSP class
sp<MyBar> bar = new MyBar; // custom extension
foo->setExtension(bar);
// Client side:
sp<IBinder> barBinder;
binder->getExtension(&barBinder);
sp<IBar> bar = interface_cast<IBar>(barBinder);
// bar is null if no extension or wrong type
This is the recommended way for downstream vendors to extend AOSP interfaces without modifying them.
9.9.6 Binder Recording¶
BBinder supports recording all transactions to a file descriptor for debugging and replay:
// frameworks/native/libs/binder/include/binder/BpBinder.h
status_t startRecordingBinder(const binder::unique_fd& fd);
status_t stopRecordingBinder();
This is gated to root-only access and must be explicitly enabled at build time
with BINDER_ENABLE_RECORDING. The recorded transactions can be replayed using
the RecordedTransaction class for testing and debugging.
9.9.7 RPC Binder Overview¶
RPC Binder enables Binder-like communication over sockets instead of the
/dev/binder kernel driver. See section 9.10 for full coverage.
9.9.8 Binder Interface Stability Levels¶
The stability system prevents accidental cross-boundary usage of unstable interfaces:
graph TD
V["VINTF Stability<br/>Cross-partition safe"] --> S["System Stability<br/>Within system partition"]
S --> U["Undeclared Stability<br/>Within compilation unit"]
style V fill:#e8f5e9
style S fill:#fff3e0
style U fill:#ffebee
When a binder object crosses a partition boundary (e.g., from system to vendor), the stability level is checked. A VINTF-stable interface can cross any boundary. A system-stable interface can only be used within the system partition. An undeclared interface (the default) can only be used within its compilation unit.
This is enforced at runtime by the Stability class, which stamps each binder
object with its stability level when it is created.
9.9.9 Binder Thread Pool Configuration Patterns¶
Different services use different thread pool configurations:
| Service | Max Threads | Pattern |
|---|---|---|
| servicemanager | 0 | Single-threaded event loop with Looper |
| system_server | 31 | Large pool for many concurrent clients |
| SurfaceFlinger | 4 | Moderate pool for display clients |
| Typical HAL | 0 | Single main thread + spawned as needed |
| Media services | Variable | Depends on concurrent stream count |
The thread count is the kernel-managed maximum. The total thread count is:
Where:
startThreadPool()always spawns 1 thread- The kernel can spawn up to N additional threads on demand
- M additional threads join via
joinThreadPool()directly
9.10 RPC Binder¶
Traditional Binder relies on the /dev/binder kernel driver, which requires
both communicating processes to share the same Linux kernel. RPC Binder
(introduced in Android 12) replaces the kernel driver with socket-based
transport, enabling Binder communication across kernel boundaries — between
virtual machines, over network connections, or into trusted execution
environments.
9.10.1 Why RPC Binder?¶
The kernel binder driver has a fundamental constraint: both client and server
must run on the same kernel with access to the same /dev/binder device. This
breaks down in several scenarios:
| Scenario | Problem | RPC Binder Solution |
|---|---|---|
| Protected VMs (pKVM) | Guest VM has no access to host's /dev/binder |
vsock transport |
| Microdroid | Lightweight VM running isolated workloads | Unix domain socket bootstrap |
| Trusty TEE | Secure world has separate kernel | TIPC transport |
| Remote debugging | Developer machine ≠ device kernel | TCP/inet transport |
| CompOS | Compilation in isolated VM | vsock to host services |
9.10.2 Architecture¶
RPC Binder mirrors the kernel binder's BBinder/BpBinder model but replaces the driver with a userspace wire protocol over sockets:
graph TB
subgraph Server["Server Process"]
BB["BBinder<br/>Service implementation"] --> RS["RpcServer<br/>Accepts connections"]
RS --> TF["TransportFactory<br/>Raw / TLS / TIPC"]
end
subgraph Transport["Socket Transport"]
direction LR
UDS["Unix Domain<br/>Socket"]
VSOCK["vsock<br/>VM ↔ Host"]
TCP["TCP/IP<br/>Network"]
TIPC["Trusty IPC<br/>TEE"]
end
subgraph Client["Client Process"]
SESS["RpcSession<br/>Manages connections"] --> BP["BpBinder<br/>Proxy object"]
CTF["TransportFactory"] --> SESS
end
TF --> UDS
TF --> VSOCK
TF --> TCP
TF --> TIPC
UDS --> CTF
VSOCK --> CTF
TCP --> CTF
TIPC --> CTF
The key insight is that AIDL interfaces work unchanged over RPC Binder.
A service implemented with BnFoo (extending BBinder) can be exposed via
RpcServer without any code changes to the service itself. Clients obtain a
BpBinder proxy through RpcSession and call it exactly as they would a
kernel binder proxy.
9.10.3 Core Classes¶
RpcServer¶
RpcServer listens for incoming connections and dispatches them to handler
threads. It supports multiple transport setup methods:
// Source: frameworks/native/libs/binder/include/binder/RpcServer.h:57-104
sp<RpcServer> server = RpcServer::make();
// Choose ONE transport:
server->setupUnixDomainServer("/path/to/socket");
server->setupVsockServer(VMADDR_CID_ANY, port, &assignedPort);
server->setupInetServer("0.0.0.0", port, &assignedPort);
server->setupUnixDomainSocketBootstrapServer(bootstrapFd);
// Configure:
server->setRootObject(myService); // Single root object
server->setPerSessionRootObject(factory); // Per-session factory
server->setMaxThreads(4); // Thread pool size
// Start accepting connections:
server->join(); // Blocking
The setPerSessionRootObject() factory function creates a fresh root binder
object for each client session — useful when the server needs per-client state
or isolation.
RpcSession¶
RpcSession establishes outgoing connections to an RpcServer and provides
the client-side binder proxy:
// Source: frameworks/native/libs/binder/include/binder/RpcSession.h:125-141
sp<RpcSession> session = RpcSession::make();
session->setupUnixDomainClient("/path/to/socket");
// or: session->setupVsockClient(cid, port);
// or: session->setupInetClient("10.0.0.1", port);
sp<IBinder> root = session->getRootObject();
sp<IMyService> service = IMyService::asInterface(root);
service->doSomething(); // RPC call over socket
RpcState¶
RpcState implements the wire protocol state machine — serializing
transactions into RpcWireTransaction structs, managing binder reference
counts across the socket, and handling async (oneway) transaction ordering.
9.10.4 Wire Protocol¶
The RPC wire protocol is defined in RpcWireFormat.h and consists of
length-prefixed messages:
Connection Handshake¶
sequenceDiagram
participant C as Client
participant S as Server
C->>S: RpcConnectionHeader (16 bytes)<br/>version, options, sessionIdSize
Note over S: New session if sessionIdSize == 0
S->>C: RpcNewSessionResponse (8 bytes)<br/>negotiated version
C->>S: RpcOutgoingConnectionInit (8 bytes)<br/>"cci" + reserved
Note over C,S: Session established, ready for transactions
// Source: frameworks/native/libs/binder/RpcWireFormat.h:47-56
struct RpcConnectionHeader {
uint32_t version; // max supported by caller
uint8_t options; // RPC_CONNECTION_OPTION_INCOMING
uint8_t fileDescriptorTransportMode;
uint8_t reserved[8];
uint16_t sessionIdSize; // 0 = new session, 32 = existing
};
static_assert(sizeof(RpcConnectionHeader) == 16);
Session IDs are 32 bytes (kSessionIdBytes), generated randomly by the server
when a new session is created.
Transaction Format¶
Every message over the wire starts with an RpcWireHeader:
// Source: frameworks/native/libs/binder/RpcWireFormat.h:123-129
struct RpcWireHeader {
uint32_t command; // RPC_COMMAND_TRANSACT / REPLY / DEC_STRONG
uint32_t bodySize;
uint32_t reserved[2];
};
struct RpcWireTransaction {
RpcWireAddress address; // 8 bytes: target binder address
uint32_t code; // Transaction code (AIDL method index)
uint32_t flags; // FLAG_ONEWAY, etc.
uint64_t asyncNumber; // Ordering for oneway calls
uint32_t parcelDataSize; // Parcel payload size
uint32_t reserved[3];
uint8_t data[]; // Parcel data follows
};
The asyncNumber field ensures oneway transactions are delivered in order,
since socket transport doesn't guarantee in-order delivery across multiple
connections.
Protocol Versions¶
| Version | Feature |
|---|---|
| 0 | Initial protocol |
| 1 | Explicit parcel size in replies |
| 2 | Binder positions in transaction headers (current stable) |
| 3 | Next version (in development) |
| 0xF0000000 | Experimental (development only) |
Version negotiation happens during the connection handshake — client sends its maximum supported version, server responds with the highest version it supports that is ≤ the client's maximum.
9.10.5 Transport Layers¶
Unix Domain Sockets¶
The most common transport for on-device RPC Binder. Used for communication between processes on the same machine when kernel binder is unavailable or undesirable:
// Server side
server->setupUnixDomainServer("/dev/socket/my_rpc_service");
// Client side
session->setupUnixDomainClient("/dev/socket/my_rpc_service");
The bootstrap variant passes an existing connected socket pair, useful for parent-child process communication:
// Source: frameworks/native/libs/binder/RpcServer.cpp:66
status_t RpcServer::setupUnixDomainSocketBootstrapServer(unique_fd bootstrapFd);
Vsock (Virtual Machine Sockets)¶
Vsock provides direct communication between a VM guest and its host without network configuration. This is the primary transport for pKVM protected VMs and Microdroid:
// Source: frameworks/native/libs/binder/RpcServer.cpp:74
status_t RpcServer::setupVsockServer(unsigned bindCid, unsigned port,
unsigned* assignedPort);
// Source: packages/modules/Virtualization/android/virtmgr/src/virtualmachine.rs:1503
let (vm_server, _) = RpcServer::new_vsock(service, cid, port)
.context(format!("Could not start RpcServer on port {port}"))?;
TCP/IP (Inet)¶
For network-accessible RPC services, primarily used in testing and remote debugging scenarios:
// Source: frameworks/native/libs/binder/RpcServer.cpp
status_t RpcServer::setupInetServer(const char* address, unsigned int port,
unsigned int* assignedPort);
Trusty TIPC¶
A specialized transport for communication with the Trusty TEE (Trusted Execution Environment). Uses Trusty's IPC mechanism instead of sockets:
// Source: frameworks/native/libs/binder/trusty/RpcServerTrusty.cpp
// Separate RpcServerTrusty class with TIPC-specific transport
// Source: frameworks/native/libs/binder/trusty/RpcTransportTipcTrusty.cpp
// TIPC transport implementation for the Trusty-side binder
The Trusty transport enables Android services to call into secure-world services (like Keymaster or Gatekeeper) using the same AIDL interface definitions they use for regular binder calls.
9.10.6 Security: TLS and Authentication¶
RPC Binder supports TLS encryption for transports that cross trust boundaries:
// Create server with TLS
auto tlsFactory = RpcTransportCtxFactoryTls::make(authInfo);
sp<RpcServer> server = RpcServer::make(std::move(tlsFactory));
The TLS implementation uses OpenSSL and supports:
RpcAuth— configures SSL context with certificates and private keysRpcCertificateVerifier— custom peer certificate verification callback- Certificate formats — PEM and DER (
RpcCertificateFormat.h) - Key formats — PEM and DER (
RpcKeyFormat.h)
For transports within a single device (Unix domain sockets), TLS is typically unnecessary — the raw (unencrypted) transport is used instead:
// Source: frameworks/native/libs/binder/RpcServer.cpp:57
sp<RpcServer> RpcServer::make(
std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory) {
// Default is without TLS
if (rpcTransportCtxFactory == nullptr)
rpcTransportCtxFactory = binder::os::makeDefaultRpcTransportCtxFactory();
// ...
}
9.10.7 File Descriptor Transport¶
RPC Binder can pass file descriptors across process boundaries using socket ancillary data (SCM_RIGHTS), similar to kernel binder's flat_binder_object:
// Source: frameworks/native/libs/binder/include/binder/RpcSession.h:107-113
enum class FileDescriptorTransportMode : uint8_t {
NONE = 0, // No FD passing (default)
UNIX = 1, // Unix domain socket ancillary data
TRUSTY = 2, // Trusty IPC handles
};
This is essential for sharing memory-mapped buffers, hardware device handles, or other kernel resources across RPC boundaries.
9.10.8 Threading Model¶
RPC Binder manages two pools of connections per session:
graph TB
subgraph Session["RpcSession"]
direction TB
OUT["Outgoing Pool<br/>Max: setMaxOutgoingConnections()"]
IN["Incoming Pool<br/>Max: setMaxIncomingThreads()"]
end
OUT -->|"Client → Server calls"| SERVER["RpcServer"]
SERVER -->|"Server → Client callbacks"| IN
- Outgoing connections carry client-to-server transactions. The pool is
limited by
setMaxOutgoingConnections()(default 10). - Incoming connections handle server-to-client callbacks (reverse calls).
Limited by
setMaxIncomingThreads(). - Server threads are managed via
RpcServer::setMaxThreads().
For embedded environments (Trusty), a single-threaded mode is available
via the BINDER_RPC_SINGLE_THREADED compile flag, which replaces mutexes
and threads with no-op implementations.
9.10.9 Rust and NDK Bindings¶
Rust API¶
The rpcbinder crate provides Rust bindings for RPC Binder:
// Source: packages/modules/Virtualization/android/virtmgr/src/main.rs:35
use rpcbinder::{FileDescriptorTransportMode, RpcServer};
// Source: packages/modules/Virtualization/android/virtmgr/src/virtualmachine.rs:1503
let (vm_server, _) = RpcServer::new_vsock(service, cid, port)?;
The Rust API supports:
RpcServer::new_vsock()— vsock serverRpcServer::new_unix_domain_bootstrap()— bootstrap serverRpcSession— client connectionsFileDescriptorTransportMode— FD passing configuration
NDK API (Unstable)¶
The NDK provides a C API for RPC Binder, currently marked as unstable (platform-only):
// Source: frameworks/native/libs/binder/ndk/include_platform/android/binder_rpc.h
ARpcSession* ARpcSession_new();
void ARpcSession_free(ARpcSession* session);
AIBinder* ARpcSession_setupUnixDomainBootstrapClient(
ARpcSession* session, int bootstrapFd);
void ARpcSession_setMaxIncomingThreads(ARpcSession* session, size_t threads);
void ARpcSession_setMaxOutgoingConnections(ARpcSession* session, size_t connections);
void ARpcSession_setFileDescriptorTransportMode(
ARpcSession* session, ARpcSession_FileDescriptorTransportMode mode);
9.10.10 Use Cases in AOSP¶
Microdroid and Protected VMs¶
The primary production use case for RPC Binder is Microdroid — a
lightweight Android VM used for isolated computation. The Virtual Machine
Manager (virtmgr) uses RPC Binder over vsock to expose services to guest VMs:
graph LR
subgraph Host["Android Host"]
VM_MGR["virtmgr<br/>RpcServer (vsock)"]
SVC["System Services<br/>via ServiceManager"]
end
subgraph Guest["Microdroid VM"]
APP["Isolated App<br/>RpcSession (vsock)"]
end
APP <-->|"vsock"| VM_MGR
VM_MGR --> SVC
The guest VM has no /dev/binder device. All binder communication with the
host goes through RPC Binder over vsock. The virtmgr daemon creates an
RpcServer that accepts vsock connections from the guest, providing access to
a curated set of host services.
// Source: packages/modules/Virtualization/android/virtmgr/src/virtualmachine.rs:1503
let (vm_server, _) = RpcServer::new_vsock(service, cid, port)
.context(format!("Could not start RpcServer on port {port}"))?;
The NDK demo (vm_demo_native) shows the client side in the guest VM:
// Source: packages/modules/Virtualization/android/vm_demo_native/main.cpp:126-132
std::unique_ptr<ARpcSession, decltype(&ARpcSession_free)>
session(ARpcSession_new(), &ARpcSession_free);
ARpcSession_setFileDescriptorTransportMode(session.get(),
ARpcSession_FileDescriptorTransportMode::Unix);
ARpcSession_setMaxIncomingThreads(session.get(), VIRTMGR_THREADS);
AIBinder* binder = ARpcSession_setupUnixDomainBootstrapClient(
session.get(), fd);
CompOS (Compilation OS)¶
CompOS runs dex2oat (DEX-to-native compilation) inside an isolated VM for
verified boot integrity. It uses RPC Binder to receive compilation requests
from the host and return compiled artifacts.
Trusty TEE Communication¶
RPC Binder over TIPC provides a standard AIDL interface to Trusty secure-world services. Instead of custom IPC protocols, services like Keymaster and Gatekeeper can use the same AIDL definitions on both Android and Trusty sides:
graph LR
subgraph Android["Android (Normal World)"]
CLIENT["KeystoreService<br/>RpcSession (TIPC)"]
end
subgraph Trusty["Trusty (Secure World)"]
KM["Keymaster TA<br/>RpcServerTrusty"]
end
CLIENT <-->|"TIPC Transport"| KM
Service Access in VMs via AccessorProvider¶
The NDK ABinderRpc_AccessorProvider API enables automatic service discovery
across VM boundaries. When a service is not available locally (because the
process is in a VM without kernel binder), the AccessorProvider callback
transparently sets up an RPC Binder connection to the host:
// Source: frameworks/native/libs/binder/ndk/include_platform/android/binder_rpc.h:75
// AccessorProvider bridges service discovery in VMs
// When kernel binder is unavailable, the provider creates
// RPC connections to host services transparently
9.10.11 Kernel Binder vs. RPC Binder¶
| Aspect | Kernel Binder | RPC Binder |
|---|---|---|
| Transport | /dev/binder driver |
Sockets (Unix/vsock/TCP/TIPC) |
| Data copy | One-copy via mmap |
Standard socket send/recv |
| Scope | Same kernel only | Cross-kernel, cross-machine |
| FD passing | flat_binder_object |
SCM_RIGHTS ancillary data |
| Thread management | Kernel-managed pool | Userspace thread pool |
| Reference counting | Kernel-tracked | Wire protocol (DEC_STRONG) |
| Death notifications | Kernel obituaries | Connection close detection |
| Performance | Lower latency (mmap) | Higher latency (socket copies) |
| Security | UID/PID from kernel | TLS certificates / socket perms |
| AIDL compatibility | Native | Fully compatible (same interfaces) |
9.11 Debugging and Diagnostics¶
9.11.1 debugfs Interface¶
The binder driver exposes debug information via debugfs:
/sys/kernel/debug/binder/
├── failed_transaction_log # Log of failed transactions
├── state # Current driver state
├── stats # Global statistics
├── transaction_log # Recent transaction log
└── proc/ # Per-process information
├── <pid>/
│ ├── state
│ └── stats
└── ...
Example: view all binder processes:
Example: view transactions for a specific process:
9.11.2 Perfetto Tracing¶
servicemanager integrates with Perfetto for tracing:
// frameworks/native/cmds/servicemanager/ServiceManager.cpp
#define SM_PERFETTO_TRACE_FUNC(...) \
PERFETTO_TE_SCOPED(servicemanager, \
PERFETTO_TE_SLICE_BEGIN(__func__) __VA_OPT__(,) __VA_ARGS__)
Every addService, getService, and checkService call is traced.
9.11.3 service command¶
The service shell command directly interacts with services:
# List all services
adb shell service list
# Check if a service exists
adb shell service check SurfaceFlinger
# Call a service method (raw)
adb shell service call SurfaceFlinger 1
# 1 = FIRST_CALL_TRANSACTION (first method in ISurfaceComposer)
9.11.4 Common Error Codes¶
| Error | Meaning |
|---|---|
DEAD_OBJECT |
The server process died |
FAILED_TRANSACTION |
Transaction failed (buffer overflow, frozen process, etc.) |
PERMISSION_DENIED |
SELinux denied the access |
BAD_TYPE |
Interface descriptor mismatch |
UNKNOWN_TRANSACTION |
The server does not recognize the transaction code |
FDS_NOT_ALLOWED |
File descriptors not allowed in this transaction |
9.11.5 Diagnosing Binder Buffer Exhaustion¶
When a process's binder buffer fills up, you see errors like:
To diagnose:
# Check buffer allocation for a specific process
adb shell cat /sys/kernel/debug/binder/proc/<pid>/state
# Look for "allocated" and "free" buffer sizes
# A process with many pending incoming transactions will show high allocation
Common causes:
- Slow onTransact handler: The server takes too long to process transactions, filling the buffer with queued requests
- Binder thread starvation: All threads are busy, and new transactions queue
- Large transactions: Sending bitmaps or large data through Binder instead of using shared memory
9.11.6 Tracing Binder Transactions with atrace¶
# Enable binder tracing
adb shell atrace --async_start -c binder_driver binder_lock
# Collect the trace
adb shell atrace --async_stop > trace.txt
# View in Perfetto UI
9.11.7 Monitoring Binder Proxy Counts¶
# Check per-UID proxy counts
adb shell dumpsys activity binder-proxies
# Check total proxy count for a process
adb shell cat /proc/<pid>/fd | wc -l # rough approximation
The proxy throttle watermarks (2000 low / 2250 warning / 2500 high) are configurable via system properties on debug builds.
9.11.8 Using binder_exception_to_string¶
When debugging AIDL binder exceptions, the status code can be decoded:
| Exception Code | Name | Meaning |
|---|---|---|
| -1 | EX_SECURITY |
Security violation |
| -2 | EX_BAD_PARCELABLE |
Bad parcelable data |
| -3 | EX_ILLEGAL_ARGUMENT |
Invalid argument |
| -4 | EX_NULL_POINTER |
Null pointer |
| -5 | EX_ILLEGAL_STATE |
Invalid state |
| -6 | EX_NETWORK_MAIN_THREAD |
Network on main thread |
| -7 | EX_UNSUPPORTED_OPERATION |
Unsupported operation |
| -8 | EX_SERVICE_SPECIFIC |
Service-specific error (with detail code) |
| -9 | EX_PARCELABLE |
Custom parcelable exception |
| -128 | EX_TRANSACTION_FAILED |
Transaction failure |
These are the AIDL binder::Status exception codes, distinct from the kernel-
level status_t return codes.
9.12 Summary¶
Key Source Files¶
| Component | Path |
|---|---|
| ProcessState | frameworks/native/libs/binder/ProcessState.cpp |
| IPCThreadState | frameworks/native/libs/binder/IPCThreadState.cpp |
| IBinder header | frameworks/native/libs/binder/include/binder/IBinder.h |
| BBinder | frameworks/native/libs/binder/Binder.cpp |
| BpBinder | frameworks/native/libs/binder/BpBinder.cpp |
| IInterface | frameworks/native/libs/binder/include/binder/IInterface.h |
| Parcel | frameworks/native/libs/binder/include/binder/Parcel.h |
| IServiceManager | frameworks/native/libs/binder/include/binder/IServiceManager.h |
| servicemanager main | frameworks/native/cmds/servicemanager/main.cpp |
| ServiceManager | frameworks/native/cmds/servicemanager/ServiceManager.cpp |
| Access control | frameworks/native/cmds/servicemanager/Access.cpp |
| servicemanager.rc | frameworks/native/cmds/servicemanager/servicemanager.rc |
| vndservicemanager.rc | frameworks/native/cmds/servicemanager/vndservicemanager.rc |
| AIDL compiler | system/tools/aidl/aidl.cpp |
| AIDL to C++ | system/tools/aidl/aidl_to_cpp.cpp |
| AIDL to Java | system/tools/aidl/aidl_to_java.cpp |
| AIDL to Rust | system/tools/aidl/aidl_to_rust.cpp |
| Rust binder | frameworks/native/libs/binder/rust/src/lib.rs |
| Rust binder traits | frameworks/native/libs/binder/rust/src/binder.rs |
| Rust proxy | frameworks/native/libs/binder/rust/src/proxy.rs |
| Rust native | frameworks/native/libs/binder/rust/src/native.rs |
| hwservicemanager | system/hwservicemanager/ServiceManager.h |
| hwservicemanager.rc | system/hwservicemanager/hwservicemanager.rc |
| LazyServiceRegistrar | frameworks/native/libs/binder/include/binder/LazyServiceRegistrar.h |
| Kernel header bridge | frameworks/native/libs/binder/binder_module.h |
Architecture Summary¶
graph TB
subgraph "Application Layer"
APP["App (Java/Kotlin)"]
SYS["system_server"]
end
subgraph "AIDL / HIDL Layer"
AIDL["AIDL Compiler"]
JAVA_STUB["Java Stubs"]
CPP_STUB["C++ Stubs"]
RUST_STUB["Rust Stubs"]
end
subgraph "libbinder Layer"
BB["BBinder"]
BP["BpBinder"]
IPC["IPCThreadState"]
PS["ProcessState"]
end
subgraph "Kernel Layer"
BD["/dev/binder"]
HBD["/dev/hwbinder"]
VBD["/dev/vndbinder"]
end
subgraph "Service Managers"
SM["servicemanager"]
HSM["hwservicemanager"]
VSM["vndservicemanager"]
end
APP --> JAVA_STUB
SYS --> CPP_STUB
AIDL --> JAVA_STUB
AIDL --> CPP_STUB
AIDL --> RUST_STUB
JAVA_STUB --> BP
CPP_STUB --> BB
CPP_STUB --> BP
RUST_STUB --> BP
BB --> IPC
BP --> IPC
IPC --> PS
PS --> BD
PS --> HBD
PS --> VBD
BD --> SM
HBD --> HSM
VBD --> VSM
Key Takeaways¶
-
Binder is a one-copy IPC mechanism that achieves high performance through memory mapping. The kernel copies data directly into the receiver's mapped buffer.
-
Every transaction carries kernel-verified identity (UID, PID, SELinux context), making it the foundation of Android's security model.
-
Object reference semantics with reference counting and death notifications enable robust distributed object lifecycle management.
-
The architecture is layered: kernel driver -> libbinder (C++/Rust) -> AIDL-generated stubs -> service implementations.
-
servicemanager is the name server for the entire system, protected by SELinux and VINTF manifest validation.
-
Three binder domains (binder, hwbinder, vndbinder) enforce the Treble architecture boundary between framework and vendor.
-
AIDL is the standard interface definition language for all new Binder interfaces, generating code for Java, C++, NDK C++, and Rust.
-
HIDL and hwbinder are deprecated in favor of AIDL for HAL interfaces starting with Android 13.
Next chapter: Chapter 8 will explore the Hardware Abstraction Layer (HAL) architecture, building on the AIDL and binder concepts covered here.