undefined reference
undefined reference

Android Binder IPC

Written by bcopos on July 12, 2014.

What?

Inter-Process Communication (IPC) is a framework for exchange of signals (or data) across processes. Some options include shared memory, semaphores, files, pipes, etc. Another options is Binder. Binder is based on OpenBinder (Fun fact: Dianne Hackborn, a key dev from OpenBinder was recruited by Google's Android team to develop Binder). Android uses Binder for various reasons and is the underlying mechanism for some of the Android IPC methods (i.e. Intents, Messenger).

Binder takes an Object Oriented approach to operating system design and implements a server-client IPC model (synchronous).

Why?

Several reasons why Android uses Binder over other IPC methods:

  • every Android application runs in its own process (unique UID) and in its own Dalvik VM, therefore two applications do not share any memory (however, Android has implement ashmem, a shared memory allocator)
  • Android applications must follow a strict life-cycle. Because of limited resources (memory in this case)

Components

  • Service - object implements operations
  • Client - uses Binder service
  • Token - unique identifier for Binder object
  • Transaction - exchange of signals/data using Binder interface
  • IBinder - interface that Binder objects must implement
  • AIDL - Android Interface Definition Language used to define the interface that both client and service must agree upon in order to communicate
  • Proxy1 - an implementation of AIDL that maps function calls to transactions submitted
  • Stub1 - an implementation of AIDL that maps transactions to method calls
  • 1 both proxies and stubs take care of marshaling and unmarshaling data

How?

Binder driver is exposed `/dev/binder` and extends API functions `open, release, poll, mmap, flush, ioctl`. Most communications happen using `ioctl`. The `ioctl` operation takes as arguments a Binder driver command and data (i.e. a buffer). The commands are:

  • `BINDER_WRITE_READ` - submits transmissions
  • `BINDER_SET_MAX_THREADS` - sets the max number of threads per process to handle requests
  • `BINDER_SET_CONTEXT_MGR` - sets the *context manager*
  • `BINDER_THREAD_EXIT` - sent when thread exits
  • `BINDER_VERSION`

A transaction is made up of a token, code (of the method to execute), raw data buffer, and sender PID/UID. The sender PID/UID is added by the driver. However, clients and services don't know anything about Binder or libbinder (which abstracts most low-level operations) so instead they rely on proxies and stubs. Sometimes even the proxies and stubs are abstracted through the use of *managers* (e.g. ServiceManager, WindowManager, ActivityManager, etc).

ServiceManager is a manager that plays a very important role in IPC. An application will use the ServiceManager (aka Binder's `CONTEXT_MGR`) to obtain a handle to the service it wishes to communicate with. If the client would know the remote Binder address, there would be no need for the token. This also has some security implications since the Binder token can act as a capability token. The ServiceManager has a special predefined handle (handle 0) so every application knows how to reach it. More detail? Ok. ServiceManager is one of the first services to start when Android boots up. A service will then register with ServiceManager. Once it has registered, it will create the service, wait for the Binder driver to respond via blocking ioctl, at which point the service will start a pool of threads each ready to process binder requests. Let's say a client now wants to use that service. The client will ask ServiceManager for a handle to the service (as previously mentioned above). Then it will invoke some function defined by the service's AIDL, which then passes through the proxy where the marshaling process takes place and the transaction is setup. The proxy then uses libbinder to execute the transaction via a blocking ioctl call. On the Service's side, libbinder will detect the blocking ioctl and process the transaction data. Once processed, the transaction and data is forwarded to the service's stub which unmarshals the data and presents the service with an object representation of the data. At this point, the service will respond and the response goes through the stub and then to libbinder which submits a *replyParcel* via ioctl.

That's pretty much how the exchange of data happens between processes/applications. But what is this data? Due to the Object Oriented design, the data exchanged is represented by an object, Binder object. Binder driver keeps track of these objects by using either references which can be either actual virtual memory address (same process) or a 32-bit handle to a binder object (different processes). For every transaction, the driver is responsible for mapping the local addresses to binder handles and vice versa. MORE???

In `/sys/kernel/debug/binder/`:

  • transaction_log
  • failed_transaction_log
  • transactions
  • proc
  • state
  • stat
However, these can only be accessed on a rooted device (womp womp)