Android isolates every application into its own process, assigning it a unique UID and an isolated virtual memory space. While this is great for security, it creates a massive problem: how does the Camera app talk to the hardware Camera daemon if their memory spaces are completely isolated?
They must use Inter-Process Communication (IPC). While standard Linux offers several IPC mechanisms (pipes, sockets, shared memory), Google found them either too slow or too insecure for a modern smartphone, leading to the creation of the Binder Driver.
Binder as a Kernel Driver
Binder is a custom Linux kernel driver located at drivers/android/binder.c in the kernel source tree.
It exposes a character device file in the filesystem at /dev/binder.
Any user-space process (like an app or a system service) that wants to communicate with another process simply opens the /dev/binder file and uses the ioctl() system call to send and receive binary transaction data.
// A simplified view of how user-space talks to the Binder driver
int fd = open("/dev/binder", O_RDWR);
struct binder_write_read bwr;
bwr.write_size = sizeof(write_buffer);
bwr.write_buffer = (uintptr_t)write_buffer;
// ... populate read buffer ...
// Send the transaction to the kernel
ioctl(fd, BINDER_WRITE_READ, &bwr);
The One-Copy Mechanism
The defining feature of Binder, and the reason it is vastly superior to traditional Linux pipes or sockets, is its incredible performance due to the One-Copy Mechanism.
In a traditional Linux socket IPC:
- The sender copies data from its user-space RAM into kernel-space RAM.
- The receiver copies that same data from kernel-space RAM into its own user-space RAM. This results in two heavy memory copies, draining the battery and lagging the UI when transferring large payloads.
In Binder:
- When a process starts, it uses the
mmap()system call to map a chunk of its virtual user-space memory directly to the Binder kernel driver's memory. - When the sender sends a message, the kernel copies the data once from the sender's memory directly into that pre-mapped shared memory block.
- The receiver instantly has access to the data because it is already mapped into its user-space.
// Mapping 1MB of memory for the Binder driver (One-Copy mechanism)
void *mapped_memory = mmap(NULL, 1024 * 1024, PROT_READ, MAP_PRIVATE, fd, 0);
By eliminating the second copy, Binder drastically reduces CPU overhead when transferring massive amounts of data.
Thread Pool Management
Binder requests are fundamentally synchronous by default. When App A calls a function in Service B via Binder, App A is blocked (put to sleep by the kernel) until Service B finishes the calculation and returns the result.
To handle hundreds of simultaneous requests without freezing, Binder utilizes kernel-managed thread pools.
When a process opens /dev/binder, it registers a maximum number of Binder threads (typically 15).
The kernel tracks these threads in the task_struct. If 5 apps call Service B simultaneously, the Binder driver automatically wakes up 5 sleeping threads in Service B's pool and dispatches the work concurrently, ensuring the system remains highly responsive.
Inspecting Binder Transactions
You can view real-time Binder transaction statistics directly from the ADB shell using the debug filesystem:
adb shell cat /sys/kernel/debug/binder/state
This command outputs the exact number of active threads, pending transactions, and memory allocations inside the Binder kernel driver!