Binder Transaction
The ultimate reason of using Binder is to transfer data between processes. The action of sending data to a remote Binder for processing and getting back a reply is called a Binder transaction. There are typically four main steps involved in a Binder transaction:
(1) The source process prepares the data to be transferred and make a BINDER_WRITE_READ
with BC_TRANSACTION
command code. Binder driver will create a binder_transaction
data structure for this transaction, finds a Binder thread in target process and enqueue this binder_transaction
structure to the todo list of target thread then wake the thread up. The data to be transferred is copied to target thread's memory space.
(2) Target thread wakes up inside binder_thread_read
function. It will process the binder_transaction
structure by writing a BR_TRANSACTION
return code together with transaction data into the thread's read buffer. After the control returns to user space, user space will process this BR_TRANSACTION
code by invoking the transact
method on the target BBinder
. After that, the target thread will send back a reply to driver using BC_REPLY
command.
(3) Binder driver processes this BC_REPLY
command by locating the source thread and enqueuing this binder_transaction
structure with reply data into the source thread's todo list then wakes up the source thread.
(4) When source thread uses binder_thread_read
to read incoming data, it will handle this transaction work by writing a BR_REPLY
return code with reply data into the read buffer. After the control returns to user space, source thread will see this BR_REPLY
command and process the reply data. And a Binder transaction is complete.
Android Oreo introduced two new command codes to facilitate scatter-gather data transfer. The corresponding command codes are BC_TRANSACTION_SG
and BC_REPLY_SG
. Since it is not used by Android framework, we won't talk about it in this article. The way they are handled in Binder driver are almost the same as BC_TRANSACTION
and BC_REPLY
.
In this article, I will use the HamKing sample project [1] as a context to trace through a Binder transaction. Specifically, I will the use the use case when client app calls requestOrder
to start an order.
binder_transaction data structure
First let’s take a look at how Binder driver models a transaction. The kernel data structure used is binder_transaction
:
We talked about binder_work
in the article "Binder threading model". A binder_work
structure is embedded in binder_transaction
so that binder_transaction
structure can be enqueued into a target work list. Or in another word, this work
field is needed so that a binder_transaction
can be scheduled to a target thread. The from
points to the binder_thread
structure of the source thread. The to_proc
points to the target process's binder_proc
structure and to_thread
points to a potential target thread's binder_thread
structure. When the driver handles BC_TRANSACTION
it will try to find a high priority thread in target process and enqueue this transaction onto the thread's todo list. If the driver cannot find such a thread then it will enqueue this transaction onto the target process's todo list. That basically means whichever thread processes this transaction doesn't make a difference. In this case target_thread
will be null. need_reply
being true means the transaction is synchronous. The buffer
manages a range of memory to copy the data to be transferred. The code
field is the command code for this transaction. In our example, it will be the command code corresponding to requestOrder
. Binder wants the target thread to process a transaction with at least the priority of the source process. If a the target thread has lower Linux scheduling priority than source thread, Binder driver will temporary raise the priority of target thread to process the transaction. After target thread finishes processing, the driver will restore the thread to it's original scheduling priority. The priority
field is the minimum scheduling priority this transaction should run with. It is basically the scheduling priority of the source thread. The saved_priority
is used to temporarily store the target thread's original scheduling priority before running the transaction so that its original scheduling priority can be restored afterwards. The sender_euid
is the sender process's effective user id. Target thread can use this value to do authentication checks.
The flags
field is a bitmap of the following flags:
The two important flags are TF_ONE_WAY
which indicates that the transaction is asynchronous so that target process doesn't need to reply, and the TF_ACCEPT_FDS
flag which indicates whether the source process accepts file descriptors in the reply. Allowing file descriptors has security implications so this flag allows source process to turn it off. The target process also has the option to disallow file descriptors in transactions by setting a FLAG_BINDER_FLAG_ACCEPTS_FDS
flag on a serialized BBinder
.
File descriptor is a live object that needs special handling when transferred between process boundaries. The file descriptor number lives in the source process’s file descriptor space so it has no meaning in the target process. So a new file descriptor needs to be opened in the target process and point to the same underlying file
structure when it goes across process boundary. This is called a file descriptor fix-up and fd_fixups
is a list representing all pending fix-ups. By convention, a device driver in Linux shouldn't modify a process's file descriptor table while running in another process's context. While the kernel is handing BC_TRANSACTION
it is running in the source process's context. In theory the file descriptors in the target process can be opened while handling BC_TRANSACTION
but it will make Binder driver a bad Linux citizen. So such a pending list is needed. When the target process reads data in binder_thread_read
the file descriptor fix-ups will be handled in the target process's context. All kinds of live object need special handling but only file descriptor fix-ups has such a pending list inside binder_transaction
.
from_parent
and to_parent
points to another binder_transaction
data structure. The binder_thread
structure has a transaction_stack
field that points to a stack of binder_transaction
s which indicates the list of transactions that this thread is processing. This stack is needed because transactions can be recursive which means while processing an incoming transaction, a thread can start another transaction and so on. This kind of recursive transactions are common and it actually happen in our requestOrder
example:
While the server is in the middle of processing the incoming requestOrder
transaction, it needs to charge the "credit card" so it starts a recursive transaction by calling the remote charge
method. The native Binder that implements the ICreditCard
interface is created in the client app process. Let's assume right now the charge
method inside PrepareOrderActivity
is being executed, then there are two instances of binder_transaction
exist. The first binder_transaction
corresponds to the requestOrder
call and let's refer to it as TA
. The second binder_transaction
corresponds to the charge
call let's refer to it as TB
. In the binder_thread
data structure that represents the UI thread of client app, the transaction_stack
field points to TB
which means TB
is the transaction this thread is currently processing (top of transaction stack).
The to_parent
field of a binder_transaction
structure points to the top of the target thread's transaction stack before this binder_transaction
was created. TA
was at the top of the transaction stack of the client UI thread before TB
is created and scheduled onto it, so the to_parent
field of TB
points to TA
. Similarly, the from_parent
field of a binder_transaction
structure points to the top of the source thread's transaction stack before this binder_transaction
was created. The source thread of TB
is a Binder thread in server app that handles the incoming requestOrder
call. Before TB
is created TA
is at the top of this Binder thread's transaction stack because the Binder thread is handling the requestOrder
call. So the from_parent
field of TB
also points to TA
. TA
is the outermost transaction in the recursion to both of it's from_parent
and to_parent
points to null.
In this scenario both the to_parent
and from_parent
points to TA
because there are only two processes involved in the recursion. Let's consider a more complex recursion where three processes are involved. Let's refer to the three processes as P1
, P2
and P3
and each process has one thread involved in the recursion. Let's refer to the thread in P1
as TR1
, the thread in P2
as TR2
and the thread in P3
as TR3
. Let's assume TR1
sends a transaction T1
to TR2
and TR2
sends another transaction T2
to TR3
while in the middle of handling T1
; TR3
then sends a third transaction T3
to TR1
while in the middle of handling T2
. When T1
is created, there is nothing in the transaction stack of TR1
and TR2
so the to_parent
and from_parent
of T1
are both null. When T2
is created, T1
is already in the source thread TR2
's stack so the from_parent
field of T2
is T1
but the to_parent
field of T2
is null because the target thread TR3
's stack is empty. When T3
is created, T2
is already in the source thread TR3
's stack so the from_parent
field of T3
is T2
and the to_parent
field of T3
is T1
because T1
is already in the target thread TR1
's stack.
If we look at it from a thread’s perspective, at the time T3
is being processed in TR1
, each of the three threads has two transactions in its stack. For TR1
they are T1
and T3
and T3
is the stack top; for TR2
they are T1
and T2
and T2
is the stack top; for TR3
they are T2
and T3
and T3
is the stack top.
Let’s go back to the requestOrder
example, the requestOrder
call is initiated on the UI thread of client app and Binder driver will enqueue this transaction onto the server process's todo list instead of choosing a specific Binder thread. This is because all Binder threads in the server app can process this transaction right away so there is no high priority process. However when the transaction of charge
call is scheduled, the driver will definitely choose the client UI thread as the target thread. This is because the client UI thread is waiting on the reply of requestOrder
so it can do nothing else before that. Since charge
is an inner transaction inside requestOrder
the driver will let the client UI thread handle the transaction instead of just waiting for requestOrder
to complete. In most cases a transaction can only be scheduled on a Binder thread but in this case charge
is scheduled on a UI thread. This works because the driver knows the client UI thread is waiting on the reply of requestOrder
by continuously reading data from driver so the BR_TRANSACTION
command will be immediately read and processed by the UI thread. This explains when the driver will pin a target thread to process a transaction and when the to_thread
field in binder_transaction
structure won't be null.
That’s all for an introduction of binder_transaction
data structure. Next we will trace through the four steps of a requestOrder
call.
BC_TRANSACTION trace through
We know from previous articles that the source thread initiates a transaction by calling the transact
method on IPCThreadState
:
The requestOrder
call is synchronous which means the TF_ONE_WAY
flag is not set and the client UI thread needs to wait for the reply of requestOrder
. The waitForResponse
method will not return until the reply of requestOrder
is received. We know that there are several ioctl
calls involved in a transaction so this kind of atomicity cannot be enforced by Binder driver; in fact it is implemented by waitForResponse
in user space. Let's see how this method implements this reply waiting.
The key is the infinite while loop inside waitForResponse
. This loop will continue until certain kinds of return codes are received. BR_REPLY
return code indicates the transaction has been processed by target process and the reply data is received. In this case, after the reply data is processed the waitForResponse
function can return. The BR_TRANSACTION_COMPLETE
return code indicates the transaction has been sent to target process. If this transaction is asynchronous then waitForResponse
will return, otherwise the current thread needs to continue looping until a later BR_REPLY
is received. In the case of requestOrder
a BR_REPLY
is needed to end the loop waiting. During the process of waiting, other kinds of commands may come so waitForResponse
will process them with executeCommand
then continue waiting for reply. For example, while the UI thread is loop waiting for the BR_REPLY
, a BR_TRANSACTION
command corresponding to the charge
call will be received. The UI thread will process this BR_TRANSACTION
, send the reply then continue waiting for the BR_REPLY
of requestOrder
call.
We have seen in the article “Binder data model” that writeTransactionData
writes a BC_TRANSACTION
command together with a binder_transaction_data
structure to the current thread's output buffer. The binder_transaction_data
contains pointers to the payload data to be transferred. The command will be sent to the driver the next time talkToDriver
interacts with Binder driver.
binder_thread_write
processes BC_*
commands by reading out the command code and corresponding data structure. Both of BC_TRANSACTION
and BC_REPLY
use binder_transaction_data
as the protocol data structure so they are both handled by binder_transaction
function. This function is incredible long so I have split the function into four parts. Roughly speaking, the first part locates a target process or thread for the transaction; the second part sets up an instance of binder_transaction
structure and a data buffer for the transaction; the third part does live object handling and the last part schedules the transaction onto a todo list.
The proc
argument in binder_transaction
is the source process and thread
is the source thread. In the case of requestOrder
the proc
represents the client app process and thread
represents the UI thread in it. Since the command code is BC_TRANSACTION
, the reply
argument will be false. Line 17 checks whether the handle value is 0. The Service Manager has a hard coded handle value of 0 in all processes. Since the client app is referencing the RemoteService
in HamKingService
not the Service Manager, so the value won't be 0. Binder driver already generated this handle for client app process when it uses bindService
to request a reference to the RemoteService
. If you remember, before the client app sends BC_TRANSACTION
it fills in this handle in the binder_transaction_data
structure. Line 19 calls binder_get_ref_olocked
to retrieve the binder_ref
data structure that references the binder_node
structure of RemoteService
. The client app has already got a reference to the service via bindService
so the binder_ref
is definitely already in the red black tree. Line 20 uses binder_get_node_refs_for_txn
to get the binder_node
structure that this binder_ref
maps to and increments reference counting on the binder_node
. Line 26 to 38 tries to find a best thread by unwrapping transaction recursion and find a best thread to schedule the transaction onto. I already talked about this earlier in this article so I won't elaborate here. If line 32 is true then it means the thread from
in the target process is waiting current transaction to finish. So it's better to schedule the transaction onto it instead of letting it just wait. In the case of requestOrder
the current transaction is empty so tmp
will be null. No target thread will be chosen so any Binder thread in server app process can run this transaction.
Next section is the second part in binder_transaction
function :
Line 16 allocate a binder_transaction
data structure for this transaction. The current code will allocate another binder_transaction
data structure during BC_REPLY
and replace the current one. But the two allocations represent the same logical Binder transaction instance. So it doesn't hurt to say only one instance of binder_transaction
structure is created during a transaction. tcomplete
is a bare binder_work
to send a BR_TRANSACTION_COMPLETE
return code to source thread. Since the requestOrder
transaction is synchronous, the client UI thread keeps waiting for target thread reply after seeing this return code. If this transaction is synchronous, line 20 sets the from
field to the source thread which in this case is the client UI thread. Binder driver can then locate the thread to send reply to later during BC_REPLY
. Line 24 to 29 sets several fields in the binder_transaction
structure. We already talked about them in this article. Line 31 is a key step in binder_transaction
function. It allocate a transaction buffer in target process's address space and line 38 to 43 copies the transaction to the target process's memory space. In our case, it is where the serialized ClientInfo
and OrderInfo
are copied to server app's memory space.
I have talked about the kernel buffer management structures in the article “Binder architecture and core components”. In our case, the alloc
points to the binder_alloc
structure of server app process so this function allocates the transaction buffer in server app's memory space. The data_size
is the size of total serialized data to be copied. In our case the data mainly contains the serialized OrderInfo
and ClientInfo
. The offsets_size
is the size of the offsets buffer. In our case there is only one offset value in the offsets buffer which is the offset of the serialized ICreditCard
native Binder. The extra_buffers_size
will be 0 since we are not using scatter-gather buffer. is_async
will be false since requestOrder
is a synchronous transaction. Line 33 to 36 computes the size of the buffer to be allocated, which is basically the sum of data buffer and offsets buffer. Line 38 to 60 tries to find a buffer that satisfies the size requirement in the process's free buffer tree. After line 60, best_fit
will be a free buffer that's at least the size we need or null if no such buffer is found. n
will be null at line 57 if a free buffer is found but its size is larger than what we need. buffer
will point to the target buffer and buffer_size
is the size of it. Line 71 to 79 will trim the target buffer and insert it back to the free buffer tree; of course, if the target buffer size is exactly what we ask, no trimming is needed. We already know that after a process calls mmap
on /dev/binder
, the virtual memory range is reserved and an array of binder_lru_page
structures are allocated to manage the physical page frames. But the actual physical pages are not requested from kernel at the time mmap
is called. This is how Binder driver does demand paging. But now the physical memory is really needed, so binder_update_page_range
is called to allocate physical pages and fill the page tables with address mapping information. The start
and end
in binder_update_page_range
indicate a range of user virtual memory that needs to be mapped to physical memory. Eventually the input Parcel
named mIn
in target thread's IPCThreadState
will use this range of memory as data buffer. Line 101 to 110 goes through this range and allocates physical memory page by page. The alloc_page
function at line 106 requests a physical page from Linux kernel. Line 109 creates mapping between virtual and physical address for the allocated page. Line 81 removes the target buffer from process's free buffer tree and line 84 insert it to the process's allocated buffer tree.
Now let’s go back to the binder_transaction
function:
Now that a buffer is allocated in target process’s memory address space, we can copy over the data to target process’s memory space. The tr->data.ptr.buffer
points to the source thread's data buffer and tr->data.ptr.offsets
points to the source thread's offsets buffer. t->buffer
is the binder_buffer
we just allocated in target process's memory space. So the first binder_alloc_copy_user_to_buffer
copies the main transaction data to the start of target buffer and the second call copies the offsets buffer right next to the main transaction data. Now the payload data and live object offsets are already copied to target process, let's check how Binder driver does special handling of live objects. First of all, let's first look at the data structures representing serialized live objects:
The binder_object_header
structure is a common header for all live object data types. There is only one field type
indicating the type of the live object. This header must be the first element because the code needs to first read out the common header, check the type, then read out whole data structure. There are seven types of live objects. BINDER_TYPE_BINDER
and BINDER_TYPE_WEAK_BINDER
are serialized BBinder
, BINDER_TYPE_HANDLE
and BINDER_TYPE
_WEAK_HANDLE
are serialized BpBinder
. They are all serialized into flat_binder_object
type. BINDER_TYPE_FD
represents file descriptor and the corresponding type is binder_fd_object
. binder_buffer_object
and binder_fd_array_object
are not used by Android framework so we will not discuss them. Let's go back to binder_transaction
function:
off_start_offset
is the offset of live object offsets array in the transaction buffer. Remember the payload data is copied to the start of the buffer and the live object offsets array is copied to the address right after that. So the offset of live object offsets array is basically just the size of payload data tr->data_size
. off_end_offset
is the offset of the end of live object offsets array. In our case, let's assume the input data which contains a serialized OrderInfo
and ClientInfo
takes up 401 bytes in total and the pointer size sizeof(void *)
is 4 bytes, then off_start_offset
is 404. Like we said, there is only one offset value in the offsets array which is the offset of the serialized ICreditCard
in ClientInfo
. We know that the underlying BBinder
of the ICreditCard
is serialized into a flat_binder_object
and copied to the transaction buffer. Let's assume it's located at 130 bytes from the start address of the transaction buffer, then the integer value of the only offset is 130.
The for loop inside binder_transaction
just reads out the offset value one by one, then reads out a binder_object_header
from the buffer based on the offset value, check the live object type and reads out the whole live object and do special handling on them. After doing special handling the objects needs to be copied back to the buffer with binder_alloc_copy_to_buffer
because after special handling the fields in the live object data structure probably have changed.
Let’s start with file descriptors special handling. In the requestOrder
transaction there is no file descriptor involved. Serializing a Bitmap
usually involves file descriptors but mBurgerImage
field in OrderInfo
is null when client app calls requestOrder
.
Line 15 creates a binder_txn_fd_fixup
structure and sets the underlying file
structure and the offset of the fd
field of binder_fd_object
in the data buffer. This offset is needed because when target thread apply the fix-up a new file descriptor value will be written to the field. Line 18 adds this fix-up entry to the fd_fixups
list in binder_transaction
structure. At the time the target thread handles this transaction, it will call binder_apply_fd_fixups
function to apply the pending fix-ups.
Let’s assume in the source thread a file is opened with fd
value 23. The value 23 was copied over to target thread's data buffer so target thread needs to open a file descriptor pointing to the same underlying file and replace the value 23. The value 23 is a file descriptor in source process space, it has no meaning in another process. This binder_apply_fd_fixups
does just that. Remember that this function runs under target process's context. Line 10 locates an unused file descriptor in current process and line 11 links it to the underlying file
structure. Then line 13 replaces the file descriptor value with the new one just allocated.
Now let’s look at how Binder driver does special handling for a flat_binder_object
which is basically a serialize BBinder
or BpBinder
.
binder_translate_binder
will be called when a serialized BBinder
is in the transaction buffer. First of all, if this is the first time this serialized BBinder
goes through driver, a new binder_node
data structure needs to be created as a book keeping structure for the BBinder
. When a native Binder goes across process boundary, it becomes a proxy Binder. So a binder_ref
structure needs to be created inside target process's binder_proc
. For the same reason the live object type needs to be changed from BINDER_TYPE_BINDER
to BINDER_TYPE_HANDLE
or from BINDER_TYP_WEAK_BINDER
to BINDER_TYPE_WEAK_HANDLE
. Line 28 and 30 removes the BBinder
addresses in the flat_binder_object
since the virtual addresses have no meaning in another process. Instead, the handle
value will be set to a handle allocated for the new proxy Binder.
When requestOrder
is called, the ICreditCard
instance inside ClientInfo
is just created in the client app process. It is of course a native Binder so binder_translate_binder
will be called to handle this native Binder. Line 13 tries to find a binder_node
inside the binder_proc
structure of client app process. Let's assume this is the first time this ICreditCard
goes through Binder then line 13 will return null and binder_new_node
will be called to create a binder_node
for this ICreditCard
. (This assumption is not true since the ClientInfo
object is retrieved in onActivityResult
, so the ICreditCard
has been sent to system_server
then sent back to client app before calling requestOrder
. But you can easily rewrite the code to avoid using onActivityResult
to retrieve the ICreditCard
.)
When a BBinder
is serialized to a flat_binder_object
, its memory address is stored in the cookie
field. Later when user space gets back a flat_binder_object
with type BINDER_TYPE_BINDER
, it can just cast the address cookie
to a BBinder
to deserialize the object. The binder
field in flat_binder_object
stores the address of a weak pointer type in the BBinder
. You can think this weak pointer type as part of the BBinder
but one caveat is that this weak pointer may outlive the BBinder
. This happens when the BBinder
is no longer strongly referenced but some weak references to it still remains. In this case this BBinder
should still be managed by a binder_node
even though in fact it is already gone in user space. That's why the address of the weak pointer type is serialized and used as the sorting key in the process's binder_node
tree. This binder_init_node_ilocked
shouldn't be difficult to understand. It just creates a new binder_node
, insert it into the tree and sets several fields in the structure.
When the flat_binder_object
of a native Binder is sent to target process for the first time, a binder_ref
data structure needs to be created to manage the reference. The binder_ref
has a per-process scope. Let's say process "A" creates a BBinder
and serializes it then send to driver, there will only be one instance of binder_node
for that BBinder
in the driver at all times. If process "B" and "C" both reference this BBinder
, then each process will have it's own binder_ref
to record this reference. The node
field in the binder_ref
structure points this binder_node
so the driver can get the underlying binder_node
from a binder_ref
. If a process references a BBinder
, there will be one and only one binder_ref
in the driver for that binder_node
. No matter how many BpBinder
instances are created in user space to reference that BBinder
there will only be one binder_ref
instance in the driver in current process. The only thing gets changed is the reference counting in binder_ref
.
The binder_inc_ref_for_node
first tries to find a binder_ref
pointing to node
in target process at line 13. If there is no such binder_ref
, a new instance of binder_ref
will be created in target process at line 16. In the case of requestOrder
, the ICreditCard
instance is first time passed to server process, so a binder_ref
referencing this ICreditCard
will be created inside server app's binder_proc
. A binder_proc
has two trees to manage all binder_ref
structures in the process. The refs_by_node
tree sorts all binder_ref
s by target binder_node
's address; the refs_by_desc
tree sorts all binder_ref
s by the user space handle value. Line 33 to 43 tries to find the a binder_ref
pointing to the node
. In the requestOrder
case, the binder_ref
won't be found so a new binder_ref
needs to be created. Line 47 to 50 adds the new binder_ref
to the refs_by_node
tree.
Line 52 to 58 is key to understanding how the handle value for the binder_ref
is created. The binder_ref
data structure can use a pointer to binder_node
to store the mapping because it's in kernel space. But the BpBinder
in user space needs another layer of mapping to target a BBinder
. Basically the handle value is an integer id of binder_ref
, this value is given to BpBinder
in user space to target a binder_ref
. The binder_ref
uniquely maps to a binder_node
and the corresponding BBinder
so actually the handle value gives a BpBinder
the capability to reference a remote BBinder
. Since binder_ref
has a per-process scope so the handle value also has per-process scope. Two binder_ref
structures in different processes can have the same handle value. Line 52 checks whether the target binder_node
is the Service Manager. Service Manager has a hard coded handle value 0 in every process. Otherwise, line 53 to 58 will find the smallest unused integer starting from 1 as the handle value of the binder_ref
. Line 60 to 71 links the binder_ref
to the refs_by_desc
tree. Line 72 adds the new binder_ref
to a hash table in target binder_node
.
That’s how a BINDER_TYPE_BINDER
or BINDER_TYPE_WEAK_BINDER
is specially handled during Binder transaction. Next let's check how a BINDER_TYPE_HANDLE
or BINDER_TYPE_WEAK_HANDLE
is specially handled in a transaction.
When a BINDER_TYPE_BINDER
is sent to target process it becomes a BINDER_TYPE_HANDLE
for sure, but when a BINDER_TYPE_HANDLE
is sent to another process it may remain BINDER_TYPE_HANDLE
. For example, if a BBinder
is created in process "A" and both process "B" and "C" references this BBinder
with a BpBinder
. If during a transaction process "B" sends the reference to process "A" then this BINDER_TYPE_HANDLE
needs to be converted to a BINDER_TYPE_BINDER
because the underlying BBinder
is local to "A". However if "B" sends the reference to "C" then this reference remains BINDER_TYPE_HANDLE
, but the handle value needs to be changed to the handle value in process "C". Line 17 to 23 handles the case when a serialized BpBinder
is sent to a process where the underlying BBinder
is created and line 25 to 30 handles the other case. In the case of requestOrder
, there is no BpBinder
serialized and passed to server app. However, in requestOrder
the server app creates a IOrderSession
instance and sends it back to client app. Later when client app calls pickupOrder
this IOrderSession
will be sent back to server app as a BINDER_TYPE_HANDLE
and it will be converted to a BINDER_TYPE_BINDER
since this IOrderSession
is local to server app.
We have seen how binder_transaction
function does live object special handling. Let's look at the last part of this function during which the transaction is scheduled to a target thread.
Line 14 create a binder_work
with type BINDER_WORK_TRANSACTION_COMPLETE
and enqueues it to source thread's todo list. Source thread handles this work by sending back a BR_TRANSACTION_COMPLETE
return code to user space to notify user space that driver has finished handling the BC_TRANSACTION
. If this is an asynchronous transaction the source thread will treat this transaction as finished. Otherwise the source thread will just continue waiting for a BR_REPLY
. Line 15 sets the work type to BINDER_WORK_TRANSACTION
and enqueues it to a todo list in binder_proc_transaction
. If this transaction is synchronous, line 21 and 22 will push this transaction to the top of the stack. For asynchronous transactions the remaining job left is just let target thread process it in user space since no reply is needed, so no book keeping is needed in the driver. Keep in mind that most transactions are synchronous and the requestOrder
is no exception.
binder_proc_transaction
function enqueues the transaction to a todo list and wakes up the corresponding thread. pending_async
indicates whether there is already an asynchronous transaction running or enqueued in target process. Even though there can be multiple Binder threads in target process, at most one asynchronous transaction can be running at any time. This basically gives asynchronous transactions a lower priority since source thread is not waiting for reply. If pending_async
is true, the asynchronous transaction will be stored on the async_todo
list of the target binder_node
, instead of being scheduled to run on a thread immediately. If the thread
parameter is null, it means there is no high priority thread selected so binder_select_thread_ilocked
just gets the first idle Binder thread in target process to run the transaction. If there is no idle Binder thread then this transaction will be enqueued onto the todo list of the binder_proc
of the target process. Otherwise the transaction will be enqueued onto the todo list of the binder_thread
of the target thread. Line 57 calls binder_wakeup_thread_ilocked
to wake up the target thread. We mentioned in the "Binder threading model" article that an idle Binder thread is sleeping inside the binder_thread_read
function on the wait
wait queue in the binder_thread
data structure. So line 66 to 72 wakes up the target thread who is sleeping on this wait queue. We also mentioned in the "Binder threading model" article that a thread can also use the polling to interact with Binder driver. These threads won't be in the idle Binder thread list because they are not block reading incoming data from kernel in a loop. Line 73 calls binder_wakeup_poll_threads_ilocked
to wake up those threads to poll the Binder device again.
Up to now, the BC_TRANSACTION
command is fully handled by binder_transaction
function. The transaction work is scheduled on the target thread's todo list and the driver has waken up the target Binder thread who is sleeping inside binder_thread_read
right now.
BR_TRANSACTION trace through
Welcome to second part of our code tracing through of requestOrder
call. The BC_TRANSACTION
part runs in the client app's UI thread. The second part runs in a Binder thread inside server app's thread pool.
Before the target thread is waken up, it is sleep waiting for incoming data on line 20 binder_wait_for_work
. Line 33 to 42 tries to find a todo list to read incoming data. First it will check the todo list in current binder_thread
, if it empty then it will check the todo list of current binder_proc
. If both are empty then there is nothing for current thread to read. The thread will jump back to retry
tag to sleep again. The looper_need_return
indicates whether the thread should return to use space immediately. It is set to true only during BC_ENTER_LOOPER
or BC_REGISTER_LOOPER
, at which time the thread just wants to register itself, not to block waiting on incoming data.
After a todo list is found, line 43 dequeues a work item from the todo list. In our case it is a BINDER_WORK_TRANSACTION
work for requestOrder
. Line 49 gets the binder_transaction
structure from the work_item
. The trd
is a binder_transaction_data
that's going to be sent to target thread's user space. The ptr
and cookie
are set so that user space can locate the target BBinder
to handle the transaction. We talked about the saved_priority
and priority
field in this article, line 57 to 62 temporarily changes the target thread's scheduling priority to run the transaction. Line 63 sets the return code to BR_TRANSACTION
for user space to handle the transaction. Line 65 sets the transaction code in trd
and in our case it is the transaction code of requestOrder
. We talked about the function binder_apply_fd_fixups
already. Line 68 to 72 sets the pointers in trd
to the data buffer and offsets buffer that were copied to target process's address space during the BC_TRANSACTION
step. Line 73 to 76 copies the command code and binder_transaction_data
to the Binder thread's read buffer. Line 79 to 81 pushes the binder_transaction
to the target thread's transaction stack. In our case, requestOrder
is not a recursive transaction so this binder_transaction
will be the first item in the stack. Note that for asynchronous transactions binder_free_transaction
will be called to free up the binder_transaction
structure instead of pushing it to transaction stack because the only thing left for an asynchronous transaction is letting user space handle the transaction. So from driver's perspective, an asynchronous transaction is already done.
After the binder_thread_read
the ioctl
system call will return to user space:
We have seen from “Binder threading model” that what a Binder thread in target process does is basically calling getAndExecuteCommand
indefinitely. This function calls talkWithDriver
to write outgoing data to Binder driver and read incoming data from driver then process incoming data in executeCommand
. After the Binder thread reads data from kernel at line 25, line 30 clears the data in the outgoing Parcel
and line 33 sets the read data size and line 34 sets the current data pointer.
The executeCommand
handles various return code but we are only looking at BR_TRANSACTION
right now. We have seen that a binder_transaction_data
structure was read from kernel in binder_thread_read
. Line 11 reads out the structure from the Binder thread's read buffer. Line 12 and 18 create two temporary Parcel
s for down stream code to work on. The buffer
is a Parcel
that contains incoming data from the calling process. In the case of requestOrder
it mainly contains a serialized OrderInfo
and ClientInfo
. The reply
is a Parcel
that the transaction handling code should write reply data onto and line 27 will send back the populated reply data back to caller. We have seen in the "Binder: Android Interface Definition Language" article how aidl
auto-generates code to write to the reply Parcel
.
The ipcSetDataReference
method sets the data pointer and offsets pointer in the Parcel
to point to the memory that the driver allocated during BC_TRANSACTION
step. Line 22 casts the cookie
value back to the BBinder
and calls transact
on it to do actual work. Then line 27 sends back the reply to caller. Let's look at the three functions one by one.
The main job of this function is just setting data pointers to the addresses in the binder_transaction_data
structure read from Binder driver. In the "Binder architecture and core components" article, we have seen that a Java Binder
class will create a peer native JavaBBinder
class. Since the HamKing project is written in Java so the RemoteService
class has such a peer JavaBBinder
class. So the cookie
field is actually cast to a JavaBBinder
. The transact
method in JavaBBinder
will call the Java method execTransact
method in Binder
class through JNI. The execTransact
method will then call the requestOrder
method in RemoteService
. After requestOrder
method is called, the reply Parcel
will be filled in with data to be returned to client app. Eventually the sendReply
method in IPCThreadState
will be called to send the reply back to client app:
The structure of the sendReply
method is similar to the transact
method. But this time the command code is BC_REPLY
and the handle
parameter is set to -1 because there is no need to specify the target of the reply. The reply should always be sent to the source thread. The reply
and acquireResult
parameters are both set to null in waitForResponse
so that this waitForResponse
function will finish and return when a BR_TRANSACTION_COMPLETE
is sent back from kernel when BC_REPLY
processing is done. Other than that, the structure is very similar to transact
method in IPCThreadState
. Both methods use the binder_transaction_data
structure to pass the payload data to Binder driver.
BC_REPLY trace through
The BC_REPLY
is also handled by binder_transaction
function. Luckily, most of the code flow is the same as BC_TRANSACTION
handling so we are only look at the different parts.
The majority of the code are shared with BC_TRANSACTION
so it is not shown here. The first different code logic is target thread selection. This should be easy to understand because the target thread of a reply is always the sender thread of the transaction. When the kernel handles BC_TRANSACTION
, the corresponding binder_transaction
structure is already pushed onto the target thread's transaction stack. (During BC_TRANSACTION
it is the target thread, now during BC_REPLY
it is the source thread.) Line 17 gets this old binder_transaction
structure and line 18 pops it out of source thread's transaction stack. In our requestOrder
case, the source thread is a Binder thread inside server app's process. This transaction is not a recursive one so the to_parent
field is null thus the transaction stack of the source thread will be empty after line 18. The Binder thread's priority may have changed before running the transaction, now it is time to restore the priority and line 19 does just that. Line 20 just sets the target thread to the original sender of the transaction which is the client UI thread.
There is also some difference when scheduling the transaction. Line 28 pops the old binder_transaction
structure out of the target thread's transaction stack. In this case it is the client UI thread's stack. I use the word "old" because this structure was created to handle the BC_TRANSACTION
command. During BC_REPLY
a new binder_transaction
structure is created. Now that this old binder_transaction
structure is popped out from both source and target threads' stack, line 31 can call binder_free_transaction
to free the structure. Line 29 enqueues the reply transaction to the target thread's todo list and wakes the thread up on line 30. Note that the new binder_transaction
structure is never pushed to a transaction stack. This is because after BC_REPLY
the transaction is finished so the driver no longer needs a book keeping for this transaction.
When line 30 is called, the client UI thread is waiting for the reply of requestOrder
. Let's see how the UI thread handles this reply.
BR_REPLY trace through
This is the last step in a full Binder transaction. This step runs in the source thread’s context which is the UI thread of client app in the case of requestOrder
. The UI thread is waiting inside binder_thread_read
when it is waken up. The code flow inside binder_thread_read
is almost identical to the BR_TRANSACTION
step. The only difference is that a BR_REPLY
return code is written to the read buffer. We will only look at how user space handles the BR_REPLY
return code.
Line 25 calls ipcSetDataReference
to set the data and offsets pointers in reply
to the addresses in the binder_transaction_data
structure. This piece of code logic is similar to that of BR_TRANSACTION
handling. But after BR_REPLY
the code flow jumps to finish
tag so eventually the IPCThreadState.transact
method returns and the UI thread finishes block waiting on the transaction result.
Finally we finish the code walk through of a full Binder transaction in the context of requestOrder
call. The code flow of a Binder transaction is complex but I hope by now you have a good understanding of how Binder system makes a transaction possible.