A Binder transaction is the act of passing data from a client process to a server process and, optionally, waiting for a reply. This exchange is heavily orchestrated by the Binder kernel driver using a specific command protocol.
BC_TRANSACTION and BR_TRANSACTION
Communication with the Binder driver (/dev/binder) happens via the ioctl system call using the BINDER_WRITE_READ command. The payload is a mix of commands starting with BC_ (Binder Command, sent to the driver) and BR_ (Binder Return, received from the driver).
- Client: The client constructs a payload and sends a
BC_TRANSACTIONcommand to the driver viaioctl. The client thread then blocks. - Driver: The kernel driver finds the target process, wakes up a sleeping thread in the target's Binder thread pool, and queues a
BR_TRANSACTION. - Server: The server thread wakes up, reads the
BR_TRANSACTIONfromioctl, unparcels the data, and executes the requested method. - Reply: The server thread packs the result, sends a
BC_REPLYto the driver, and goes back to sleep. - Return: The driver wakes the client thread and delivers a
BR_REPLY. The client reads the result and continues execution.
Synchronous vs One-way Transactions
By default, Binder calls are synchronous. The client thread halts execution until the server finishes processing and sends a BC_REPLY. This can lead to ANRs (Application Not Responding) if an app calls a system service from its main thread and the service is slow.
To prevent blocking, developers can use one-way transactions. In AIDL, this is denoted by the oneway keyword. When a transaction is marked FLAG_ONEWAY, the kernel driver simply queues the BR_TRANSACTION on the server and immediately returns a BR_TRANSACTION_COMPLETE to the client. The client thread resumes execution without waiting for the server to actually process the method.
Transaction Data: Code, Flags, Data, Objects
A raw binder_transaction_data struct contains several key fields:
target: The handle pointing to the target Binder object.code: A 32-bit integer indicating which method to call (e.g.,TRANSACTION_startActivity).flags: Bitmask indicating behavior (e.g.,TF_ONE_WAY).data: The serialized payload (theParcel).offsets: A pointer to an array of offsets. This is crucial: if thedatapayload contains nestedIBinderobjects or file descriptors, the offsets array tells the kernel exactly where they are, so the kernel can translate handles and duplicate file descriptors across the process boundary.
Nested Binder Calls
Binder supports recursive/nested calls across processes.
If Process A calls Process B, and Process B needs to call Process A to fulfill the request, the driver recognizes that Process A's thread is currently blocked waiting for a reply. Instead of spawning a new thread in Process A, the driver pushes the new BR_TRANSACTION onto Process A's blocked thread, essentially treating it as a standard function call stack spanning multiple processes.