Binder Transaction

Baiqin Wang
28 min readFeb 4, 2021

--

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_transactions 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_refs by target binder_node's address; the refs_by_desc tree sorts all binder_refs 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 Parcels 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.

External links

[1] https://github.com/androidonekb/HamKing

--

--