Binder translates as adhesive, and as it should, entire Android components communicate with each other.

It is recommended to learn about AIDL before reading this article. AIDL introduction

Background knowledge

Binder is a large module that runs through the Entire Android operating system, which is based on the Linux kernel, so you need to know the basics.

Process isolation

Process isolation is a set of different hardware and software techniques designed to protect processes in an operating system from interfering with each other. This technique is used to prevent process A from writing to process B. Process isolation implementation, using virtual address space. The virtual address of process A is different from that of process B. This prevents process A from writing data to process B.

In Android, each application is an independent process. Data between processes is not shared and each process is unaware of the existence of the other.

User space/kernel space

Normal processes belong to user space, while kernel space refers to the space where the Linux kernel resides. The kernel space is protected and user space has no access to the kernel space because the Linux kernel is the core of the operating system and is highly secure. User space can only access some licensed resources through an open port in the kernel space.

System call/kernel/user mode

The system call is the same as in the previous section. The kernel space opens up some openings for the user space to access resources, such as files. The only way user space can access kernel space is through system calls, some of which appear in Binder, such as open, IOCtl, mmap, etc.

When user process execution code is caught in kernel code through a system call, it is called kernel-state.

When a user process executes application code, it is called user mode.

An overview of the Binder

Binder is the Cross-process communication mechanism for Android.

Why Binder?

The Linux kernel supports a variety of cross-process communication mechanisms, such as sockets, pipes, message queues, shared memory, etc. Binder was chosen mainly for performance, security and stability.

For cross-process communication, data copying is definitely involved. Sockets/pipes/message queues require two data copies, while binders require only one data copy, greatly improving performance. But shared memory is actually 0 copies, so why not use shared memory? As you can see from your experience with multithreading, handling shared variables requires careful locking and writing complex logic, as with shared memory, Binder mechanisms do not need to be considered for ease of use.

Android assigns a UNIQUE identifier (UID) to each installed application. A UID is an important identifier that can be added by kernel modules to verify process UUIds during communication with binders to ensure security. However, other methods, such as socket, cannot guarantee that the whole data will not be tampered, because IP is open, even if UID/PID is written in the packet, it cannot prevent malicious programs from intercepting tampered data.

Binder communication model

The Binder communication model is a typical C/S model, which appears to be the direct IPC between Client and Server, but is supported by very complex logic inside. Here’s a good one:

There are four important roles in the whole communication process: Client, Server, ServiceManager and Binder driver. The general communication steps are as follows:

  1. When the system starts, the ServiceManager daemon first applies to the Binder driver as an address book. In fact, it is also a Binder Server side, with an internal table to manage all the remaining Server handles, and its own handle is 0, Binder drivers can directly obtain it.
  2. There are a lot of Server ends that are registered with the ServiceManager through system calls, driven by Binder, and the table key is the fully qualified class name of Binder, and the value is the handle in Binder.
  3. To call the server-side method, the Client first obtains the ServiceManager proxy class, finds the ServiceManager entity through the Binder driver to obtain the handle to the Server, and uses this handle to transfer data in the Binder driver. Finally, it goes to the Server-side Java layer Binder entities to perform the real logic.

As you can see, each step is driven by Binder, which is the core of the whole communication. There may be some points in the text description that you don’t understand. You can just understand the flow in the picture.

Handwriting Binder communicates across processes

This article focuses on understanding the overall process and application layer of Binder. Although AIDL has helped us simplify the Binder template code, we still need to be familiar with how to implement a Binder application layer communication code, and then it makes more sense to go deeper.

public interface IUserBinder extends IInterface { String getUserInfo(User user) throws RemoteException; abstract class Stub extends Binder implements IUserBinder { private static final String DESCRIPTOR = "com.example.service.binder.IUserBinder"; private static final int TRANSACTION_getUserInfo = FIRST_CALL_TRANSACTION + 0; Public Stub() {// Store Binder instances and descriptors of this process. attachInterface(this, DESCRIPTOR); } public static IUserBinder asInterface(IBinder obj) { if(obj == null) { return null; } // Use the Binder descriptor to retrieve a local Binder instance. // Otherwise, OBJ is a Binder proxy that needs to call the corresponding methods remotely, thus encapsulating the proxy pattern into a proxy class that looks like it calls interface methods directly. IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if(iin ! = null && iin instanceof IUserBinder) { return (IUserBinder) obj; } return new Proxy(obj); } @Override public IBinder asBinder() { return this; } /** * This method is a server entry that receives IPC from the client to call the real method. * @param code * @param data * @param reply * @param flags * @return * @throws RemoteException */ @Override protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException { switch (code) { case INTERFACE_TRANSACTION: {// Write interface descriptors for client validation reply.writeString(DESCRIPTOR); return true; } // Matches the code written by the client, indicating that the getUserInfo method is called. // Code defaults from FIRST_CALL_TRANSACTION in ascending order. case TRANSACTION_getUserInfo: { data.enforceInterface(DESCRIPTOR); User _user; // Data is the data from the client, if there is any deserialization into the object if(0! = data.readInt()) { _user = User.CREATOR.createFromParcel(data); }else { _user = null; } // Execute the real method and return the result. String _result = this.getUserInfo(_user); reply.writeNoException(); // The result is written to the response data for the client to receive. reply.writeString(_result); return true; } default: { return super.onTransact(code, data, reply, flags); */ private static class Proxy implements IUserBinder {private IBinder mRemote; public static IUserBinder sDefaultImpl; Public Proxy(IBinder remote) {// mRemote is a Binder remote Proxy class this.mRemote = remote; } @override public String getUserInfo(User User) throws RemoteException {// Obtains an empty serializable object from the object pool. // _data is the object that the client sends to the server. _reply is the object that the server responds to the client. Parcel _reply = Parcel.obtain(); String _result; Try {// Write descriptors to Binder for authentication _data.writeInterfaceToken(DESCRIPTOR); if(user ! = null) {// Write 1 in the header, indicating that the client has sent data to the server. _data.writeint (1); WriteToParcel (_data, 0); // Write user to _data user.writetoparcel (_data, 0); }else { _data.writeInt(0); } // The proxy class will go to the native layer and then to the driver and then to the server, Boolean _status = mremote. transact(stub.transaction_getuserinfo, _data, _reply, 0); if(! _status && getsDefaultImpl() ! = null) {// If the call is unsuccessful, the bottom-pocket method is entered. return getsDefaultImpl().getUserInfo(user); } _reply.readException(); _result = _reply.readString(); }finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public IBinder asBinder() { return mRemote; } public String getInterfaceDescriptor() { return DESCRIPTOR; } public static boolean setDefaultImpl(IUserBinder impl) { if(Proxy.sDefaultImpl ! = null) { throw new IllegalStateException("setDefaultImpl() called twice"); } if(impl ! = null) { sDefaultImpl = impl; return true; } return false; } public static IUserBinder getsDefaultImpl() { return sDefaultImpl; }}}}Copy the code

Go straight to the complete code, comments are already in the code. Both the server and client need to have an IUserBinder interface and a User class. The only difference is that the server needs to implement the Stub abstract class and implement the getUserInfo method, and declare a Service class that returns binder in the onBind method. All clients need to do is bind to the Service, get the binder’s proxy class, and call the interface methods freely. The implementation code is not posted, you can see the full implementation in the AIDL introduction.

IInterface

Binder Base class of an interface. When you define a new interface, you must derive it from IInterface.

Generally speaking, we must implement the IInterface interface when we customize the interface methods for cross-process communication. The only interface method of IInterface, asBinder, is associated with Binder objects (local Binder instances or Binder proxies).

Parcel

A container that can send messages (data and object references) via IBinder. A Parcel can contain either data that will be flattened on the other side of the IPC (basic type, generic Parcelable interface) or a proxy reference to an IBinder entity object, which associates ibInders on both ends.

IBinder

It represents the ability to transport across processes; As long as this interface is implemented, this object can be passed across processes; This is what drives the underlying support; As cross-process data flows through the driver, the driver recognizes IBinder type data and automatically converts Binder native objects and Binder proxy objects between different processes.

The basic interface to remotable objects, a core part of the lightweight remote procedure call mechanism, is designed to achieve high performance when performing in-process and cross-process calls. This interface describes an abstract protocol for interacting with remote objects. Do not implement this interface directly; instead, inherit the Binder abstract classes.

The two most critical methods in the IBinder interface are Transact and onTransact. The former is sending the calling transaction, and the latter is receiving the call sent by the former. The two methods are synchronized. After A process transact remotely calls the onTransact method of process B, the subsequent code of process A can be executed only after the onTransact method is executed. You can also use Oneway to make asynchronous calls.

The data sent via Transact is a Parcel object, which is a generic buffer for the data and also maintains some metadata about its contents. Metadata is used to manage IBinder object references in the buffer so that they can be maintained as the buffer is moved across processes. This mechanism ensures that when an IBinder is written to a Parcel object and sent to another process, if that other process sends back a reference to the same IBinder, the original process will return the same IBinder object. These semantics allow IBinder/Binder objects to be used as unique identifiers that can be managed across processes (as tokens or for other purposes).

The system maintains a pool of transaction threads in each running process. These threads are used to dispatch all IPC from other processes. For example, when an IPC is created from process A to process B, the calling thread in PROCESS A blocks Transact while sending A transaction to process B. The next available pool thread in B receives the incoming transaction, calls the onTransact method and returns the Parcel result. Upon receiving its results, the thread in process A returns to allow its execution to continue. So process B runs in an asynchronous thread that you don’t need to create yourself.

There are several ways to listen when a Binder is no longer available:

  • The Transact method throws a RemoteException.
  • Call the pingBinder method and return false to indicate that Binder has been destroyed.
  • Call linkToDeath to register a DeathRecipient object that listens for when Binder is destroyed.

Binder

The IBinder interface is implemented to represent Binder native objects.

BinderProxy

The IBinder interface is implemented to represent Binder proxy objects.

Binder classes are easy to write, each step is clear, and all are template code, which is where AIDL comes in. But with a Binder, it’s very complicated, so let’s look at how it communicates.

A Binder communication flow

Most binders are anonymous (which means they are not registered with a ServiceManager) and do not involve a ServiceManager. For example, this section uses anonymous binders to bind services to binders, so we’ll just analyze this case and leave the ServiceManager principle out for the moment. Binder communication model chapter has illustrated the four roles Client, Server, ServiceManager, Binder drive the communication process. Now take a closer look at the role transitions in the process.

The transition from the application layer to the Java layer has been fairly clear. BinderProxy is the Proxy class of the Server side Binder entity. BinderProxy implements the functional interface and holds BinderProxy remote Proxy, enabling clients to use BinderProxy as if they were directly invoking remote Binder. Binder and BinderProxy are IBinder implementation classes that can be transmitted across processes. Stub abstract classes inherit Binder and implement the functional interface, requiring the Server to implement the functional interface.

When A initiates A bindService, we retrieve the Binder’s connected object from ServiceConnection’s onServiceConnected method. As the focus of this section is our customized Binder communication process, the following words are simply used to summarize:

When process A initiates bindService, it actually goes to ContextImpl’s bindService method, The ServiceManager then finds a proxy object for ActivityManagerService(AMS) and calls its bindService method, at which point the code is already in the system process. The system process will find the process corresponding to Service according to the package name and class name passed in. If the process is not started, the process will be started. Then AMS will remotely call the scheduleCreateService method of process B through ApplicationThread proxy. At this time, the code is in process B. After creation, the system process will call scheduleBindService and go to process B to bind the service. At this time, our customized Binder is obtained. We then passed our Binder to the system process and finally went back to process A via A remote call and got Binder.

It’s just a few lines of code for our application, but the whole process goes through several times with Binder communication across processes. Do you think Binder is important? Again, the focus here is not the system ServiceManager, AMS and other components, we first go through the Binder process in daily application scenarios. Before you do that, it’s important to remember the commands in a diagram to get an idea of where the process is going.

Following the previous section on handwritten Binder cross-process communication (# Handwritten Binder cross-process Communication), the client process took the BinderProxy proxy object and called the Transact method, And what we’ll see in a second is that it calls native methods directly and transactNative goes directly to the c++ layer. The above Java layer source code analysis can be viewed in any project using AS. But to the native layer is not good, in addition to their own compilation of Android source code, I recommend using a direct look at the online source code AndroidXref, you can search files, search content, etc., very convenient.

The Client communicates with the Binder driver

TransactNative corresponds to the android_os_BinderProxy_transact function at the native layer.

// android_util_Binder.app static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj, jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException { // ...... Parcel* data = parcelForJavaObject(env, dataObj); parcelForJavaObject(env, dataObj); Parcel* reply = parcelForJavaObject(env, replyObj); IBinder* target = getBPNativeData(env, obj)-> mobject.get (); // Call bpBinder. transact status_t err = target->transact(code, *data, reply, flags); / /... }Copy the code

The system source code will omit a lot, we only look at the key code. The transact function of IPCThreadState is actually called immediately after bpBinder.transact is called.

// IPCThreadState.cpp status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { status_t err; Err = writeTransactionData(BC_TRANSACTION, flags, handle, etc.); err = writeTransactionData(BC_TRANSACTION, flags, handle) code, data, NULL); If ((flags & TF_ONE_WAY) == 0) {if (reply) {err = waitForResponse(reply); } else { Parcel fakeReply; err = waitForResponse(&fakeReply); } } else { err = waitForResponse(NULL, NULL); } return err; }Copy the code

The “command” is important here. The BC prefix means that the user process sends messages to the Binder driver, and the BR prefix means that the Binder driver sends messages to the user process. Remember the BC_TRANSATION command is written here. The binder_transaction_data structure, which we call packets, drives the data flow through Binder, Finally, writeTransactionData writes the command and binder_transaction_data to the mOut object, waiting for it to be written together to the binder_write_read structure. And then we go to waitForResponse.

// IPCThreadState.cpp status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult) { uint32_t cmd; int32_t err; while (1) { if ((err=talkWithDriver()) < NO_ERROR) break; // This part of the code is also very critical, come back to analyze later //...... }}Copy the code

Continue with the talkWithDriver function:

// IPCThreadState.cpp status_t IPCThreadState::talkWithDriver(bool doReceive) { if (mProcess->mDriverFD <= 0) { return -EBADF; } binder_write_read bwr; const bool needRead = mIn.dataPosition() >= mIn.dataSize(); const size_t outAvail = (! doReceive || needRead) ? mOut.dataSize() : 0; Bwr. write_size = outAvail; bwr.write_size = outAvail; bwr.write_buffer = (uintptr_t)mOut.data(); if (doReceive && needRead) { bwr.read_size = mIn.dataCapacity(); bwr.read_buffer = (uintptr_t)mIn.data(); } else { bwr.read_size = 0; bwr.read_buffer = 0; } status_t err; Do {// ioctl is a system call, If (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, & BWR) >= 0) err = NO_ERROR; } while (err == -EINTR); return err; }Copy the code

The binder_write_read data structure, which holds both write and read data, finally takes the crucial step of making the IOCTL system call. This is when we enter kernel mode. The BINDER_WRITE_READ command is commonly used. The command’s parameters include two parts of data: the data written to the Binder and the data to be read from the Binder. The driver processes the write part first and then the read part. The advantage of this arrangement is that the application is flexible in handling synchronization or asynchrony of commands. For example, if you want to send asynchronous commands, you can just fill in the write part and set read_size to 0. To get data only from Binder, null the write part (write_size) to 0; To send the request and synchronously wait for the data to return, place both parts.

This part we have to go to the Linux Source Code, still recommend an online Source Code website Linux Source Code.

Note that the function name is binder_ioctl when you go to the Linux source:

// drivers/android/binder.c
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret;
	switch (cmd) {
	case BINDER_WRITE_READ:
		ret = binder_ioctl_write_read(filp, cmd, arg, thread);
		if (ret)
			goto err;
		break;
	return ret;
}
Copy the code

Continue with the binder_ioctl_write_read function:

// drivers/android/binder.c static int binder_ioctl_write_read(struct file *filp, unsigned int cmd, unsigned long arg, struct binder_thread *thread) { int ret = 0; void __user *ubuf = (void __user *)arg; struct binder_write_read bwr; If (copy_from_user(& BWR, ubuf, sizeof(BWR))) {ret = -efault; goto out; } if (bwr.write_size > 0) { ret = binder_thread_write(proc, thread, bwr.write_buffer, bwr.write_size, &bwr.write_consumed); } if (bwr.read_size > 0) { ret = binder_thread_read(proc, thread, bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK); } out: return ret; }Copy the code

Here again comes the system call copy_from_user function, which corresponds to copy_to_user(copy from kernel space to user space). Copying from the user-space cache of process A to the kernel-space cache and then from the kernel-space cache to process B is not A copy Binder is known for, so mmap is used to map the user-space cache to the user-space cache. This makes a copy of copy_from_USER work just once. Instead of expanding it here, this article will mainly go through the process. Ok, now the BWR object is the kernel space memory, and the binder_write_read object mentioned earlier is actually in the user process memory. Instead of looking at the binder_thread_read function, which will be covered in a later section, let’s look at the write part of the binder_thread_write function:

// drivers/android/binder.c static int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread, binder_uintptr_t binder_buffer, size_t size, binder_size_t *consumed) { uint32_t cmd; // buffer can be interpreted as the out. data data written to bwr.write_buffer. Binder_transaction_data void __user *buffer = (void __user *)(uintptr_t)binder_buffer; // Unused buffer memory offset void __user * PTR = buffer + *consumed; Void __user *end = buffer + size; while (ptr < end && thread->return_error.cmd == BR_OK) { int ret; If (get_user(CMD, (uint32_t __user *) PTR)) return -efault; // PTR += sizeof(uint32_t); Switch (CMD) {case BC_TRANSACTION: case BC_REPLY: {struct binder_transaction_data tr; // copy a segment of binder_transaction_data from the write cache. If (copy_from_user(&tr, PTR, sizeof(tr))) return -EFAULT; PTR += sizeof(tr); binder_transaction(proc, thread, &tr, cmd == BC_REPLY, 0); break; } *consumed = ptr - buffer; } return 0; }Copy the code

Continue with the binder_transaction function:

// drivers/android/binder.c static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply, binder_size_t extra_buffers_size) { int ret; struct binder_transaction *t; struct binder_work *tcomplete; binder_size_t buffer_offset = 0; binder_size_t off_start_offset, off_end_offset; struct binder_proc *target_proc = NULL; struct binder_thread *target_thread = NULL; struct binder_node *target_node = NULL; If (reply) {//...... If (tr->target.handle) {struct binder_ref *ref; // There must already be a strong reference on this node. If so, the node is strongly incremented to ensure that it survives until the transaction completes. binder_proc_lock(proc); // Get the bind_ref object saved in the process ref = binder_get_ref_olocked(proc, tr->target.handle, true); Target_node = binder_get_node_refs_for_txn(ref->node, &target_proc, &return_error); } else { binder_user_error("%d:%d got transaction to invalid handle\n", proc->pid, thread->pid); return_error = BR_FAILED_REPLY; } binder_proc_unlock(proc); } else { // ... } if (! (tr->flags & TF_ONE_WAY) && thread->transaction_stack) { struct binder_transaction *tmp; tmp = thread->transaction_stack; / / is to optimize the logic, in the case of A synchronous invocation assumption T thread calls the B of A process, / / the result has not yet returned again call A B process, is actually A process of T thread is blocked waiting for B process back, / / A process at this time will not randomly selected in the thread pool thread B process request, I'm just using T threads. while (tmp) { struct binder_thread *from; spin_lock(&tmp->lock); from = tmp->from; if (from && from->proc == target_proc) { atomic_inc(&from->tmp_ref); target_thread = from; spin_unlock(&tmp->lock); break; } spin_unlock(&tmp->lock); tmp = tmp->from_parent; } } binder_inner_proc_unlock(proc); T ->debug_id = t_debug_id; t->debug_id; if (! reply && ! (tr->flags & TF_ONE_WAY)) t->from = thread; else t->from = NULL; t->sender_euid = task_euid(proc->tsk); T ->to_proc = target_proc; t->to_thread = target_thread; t->code = tr->code; t->flags = tr->flags; t->priority = task_nice(current); t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size, tr->offsets_size, extra_buffers_size, ! reply && (t->flags & TF_ONE_WAY), current->tgid); t->buffer->debug_id = t->debug_id; t->buffer->transaction = t; t->buffer->target_node = target_node; t->buffer->clear_on_free = !! (t->flags & TF_CLEAR_BUF); If (binder_alloc_copy_user_to_buffer(&target_proc->alloc, t->buffer, 0, (const void __user *) (uintptr_t)tr->data.ptr.buffer, If (binder_alloc_copy_user_to_buffer(&target_proc->alloc, t->buffer, ALIGN(tr->data_size, sizeof(void *)), (const void __user *) (uintptr_t)tr->data.ptr.offsets, tr->offsets_size)) { } off_start_offset = ALIGN(tr->data_size, sizeof(void *)); buffer_offset = off_start_offset; off_end_offset = off_start_offset + tr->offsets_size; For (buffer_offset = off_start_offset; buffer_offset < off_end_offset; buffer_offset += sizeof(binder_size_t)) { struct binder_object_header *hdr; size_t object_size; struct binder_object object; binder_size_t object_offset; object_size = binder_get_object(target_proc, t->buffer, object_offset, &object); hdr = &object.hdr; // BINDER_TYPE_BINDER: case BINDER_TYPE_WEAK_BINDER: Case BINDER_TYPE_WEAK_BINDER: {// Both Binder entities and references are flat_binder_object struct flat_binder_object *fp; fp = to_flat_binder_object(hdr); Ret = binder_translate_binder(fp, t, thread); } break; // Binder reference case BINDER_TYPE_HANDLE: case BINDER_TYPE_WEAK_HANDLE: {// Both Binder entities and references are flat_binder_object struct flat_binder_object *fp; fp = to_flat_binder_object(hdr); Ret = binder_translate_handle(fp, t, thread); } break; } tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; t->work.type = BINDER_WORK_TRANSACTION; if (reply) { // ... } else if (! (t->flags & TF_ONE_WAY)) {// call binder_inner_proc_lock(proc); // The TRANSACTION_COMPLETE command is delayed and will not immediately return to user space; // Allow the target process to execute the transaction immediately, reducing latency. When the target responds we will return the TRANSACTION_COMPLETE command binder_enqueue_deferred_thread_work_ilocked(thread, tcomplete); // A synchronous call must return t->need_reply = 1; t->from_parent = thread->transaction_stack; thread->transaction_stack = t; binder_inner_proc_unlock(proc); if (! binder_proc_transaction(t, target_proc, target_thread)) { binder_inner_proc_lock(proc); binder_pop_transaction_ilocked(thread, t); binder_inner_proc_unlock(proc); goto err_dead_proc_or_thread; }} else {// async call // directly returns the TRANSACTION_COMPLETE command. User space does not need to wait until the transaction completes. binder_enqueue_thread_work(thread, tcomplete); if (! binder_proc_transaction(t, target_proc, NULL)) goto err_dead_proc_or_thread; }}Copy the code

There is a lot of code for this function. I have filtered out the key parts and commented them out. We are only looking at the write data part for now.

Binder_ref is the structure that holds Binder references and other information, and the corresponding structure for Binder entity information is binder_node.

Focusing on the binder_get_ref_olocked function, the process actually holds a red-black tree with user-space Pointers as keys and kerl-space bind_ref objects as values. In this example, after bindService is executed, references to both userspace and kernel space are already in place and saved. Pointers to userspace must be unique in the process, so there is no duplicate key.

When HDR ->type is BINDER_TYPE_BINDER, the data transferred is a Binder entity. Similarly, BINDER_TYPE_HANDLE indicates that the transferred data is a Binder agent object, in which case both Binder entities and Binder references are flat_binder_object structures. For Binder entities, the driver translates the binder_translate_binder function into a Binder reference and assigns it to the handle variable of flat_binder_object because the data stream is about to flow to the recipient (Client). The recipient needs a Binder reference. If it is a Binder reference, the driver translates it into a Binder entity using binder_translate_handle and assigns it to the Binder variable of flat_binder_object. This logic demonstrates that Binder cross-process mechanisms support Binder delivery. In this example, AMS and a process that registers a Service and returns a Binder entity in onBind follow this logic, but there is no Binder entity or proxy for data transferred when a Client calls a method with a Binder proxy object.

Ok, follow up on the binder_proc_transaction function, we are about to reach the Server side.

// drivers/android/binder.c static bool binder_proc_transaction(struct binder_transaction *t, struct binder_proc *proc, struct binder_thread *thread) { struct binder_node *node = t->buffer->target_node; bool oneway = !! (t->flags & TF_ONE_WAY); bool pending_async = false; If (oneway) {if (node->has_async_transaction) pending_async = true; else node->has_async_transaction = true; If (thread) binder_enqueue_thread_work_ilocked(thread, &t->work); // add the transaction to the toDO transaction queue pending_async) binder_enqueue_work_ilocked(&t->work, &proc->todo); Else binder_enqueue_work_ilocked(&t->work, &node->async_todo); if (! pending_async) binder_wakeup_thread_ilocked(proc, thread, ! oneway /* sync */); return true; }Copy the code

The reason for limiting the flow of asynchronous transactions is that if you’re synchronous, the client will block and wait for the server to return the result, and you have todo it first and you can’t wait too long. Asynchronous doesn’t have this problem, because the client isn’t blocking and waiting anyway, so if there’s an asynchronous transaction that’s already on the todo queue, Subsequent asynchronous transactions can only be added to the asynC_TOdo queue until the asynchronous transaction is executed. The binder_wakeup_thread_ilocked function does not need to be followed, so we can think of it as waking up a thread of the receiving process to execute the transaction. The process is broken here and it is necessary to understand how the Binder drivers manage threads.

Thread management

A thread pool is initialized at process startup and a number of threads are created and put to sleep. These threads are also stored as binder_threads in Binder drivers. However, thread pools cannot be infinite, so there is a threshold to control them. When a thread is woken up, the binder_thread_read function is eventually executed. It all starts with the Native layer’s ProcessState.cpp, which has only one ProcessState object per process. The open_driver function is called when ProcessState is initialized:

// ProcessState.cpp #define DEFAULT_MAX_BINDER_THREADS 15 static int open_driver(const char *driver) { size_t maxThreads  = DEFAULT_MAX_BINDER_THREADS; result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads); / /... }Copy the code

BINDER_SET_MAX_THREADS the native layer uses the BINDER_SET_MAX_THREADS command to notify Binder drivers of the thread pool size via the IOCtl system call. Binder drivers are told the maximum number of threads in the thread pool so that they do not order recipients to start new threads if the number of threads in the recipient process reaches this value.

The Binder driver tells the receiver process to create a thread using the BR_SPAWN_LOOPER command when it knows that the receiver threads are busy and have not reached the upper limit:

// ProcessState.cpp void ProcessState::spawnPooledThread(bool isMain) { if (mThreadPoolStarted) { sp<Thread> t = new PoolThread(isMain); t->run(name.string()); }}Copy the code

The thread is actually executing! The thread enters the threadLoop function:

// ProcessState.cpp
virtual bool threadLoop(){
    IPCThreadState::self()->joinThreadPool(mIsMain);
    return false;
}
Copy the code
// IPCThreadState.cpp void IPCThreadState::joinThreadPool(bool isMain){ mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER); status_t result; do { result = getAndExecuteCommand(); } while (result ! = -ECONNREFUSED && result ! = -EBADF); }Copy the code

As you can see, the mOut object writes the command again. BC_ENTER_LOOPER notifies the driver that the thread has entered the loop, and BC_REGISTER_LOOPER notifies the driver that the thread has been created. Then continue with the getAndExecuteCommand function:

// IPCThreadState.cpp
status_t IPCThreadState::getAndExecuteCommand()
{
    status_t result;
    int32_t cmd;
    result = talkWithDriver();
    if (result >= NO_ERROR) {
        size_t IN = mIn.dataAvail();
        cmd = mIn.readInt32();
        result = executeCommand(cmd);
    }
    return result;
}
Copy the code

The binder_ioctl_write_read function is then called. The binder_ioctl_write_read function supports read. So it goes into the binder_thread_read function, which is the read part that we left out in the previous analysis. The Server thread suspends the thread by calling binder_wait_for_work. Wake up is what we analyzed earlier. When a transaction is added to a process’s TODO queue, the process finds a suspended thread and wakes it up to work.

Remember that we examined a transaction that gave a BINDER_WORK_TRANSACTION_COMPLETE command to the client thread and a BINDER_WORK_TRANSACTION command to the server thread when the thread was awakened. The code is pasted again:

// drivers/android/binder.c binder_thread_write function // client transaction tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; T ->work.type = BINDER_WORK_TRANSACTION;Copy the code

The client receives the BINDER_WORK_TRANSACTION_COMPLETE command and the thread goes to sleep to be woken up, just as the server thread went to sleep.

The Binder driver communicates with the Server

Now let’s take a look at where the receiver thread starts to work and read data after being woken up. Looking directly at the code snippet, binder_thread_read has this code:

// drivers/android/binder.c static int binder_thread_read(struct binder_proc *proc, struct binder_thread *thread, Binder_uintptr_t binder_buffer, size_t size, binder_size_t *consumed, int non_block){ Ret = binder_wait_for_work(thread, wait_for_proc_work); Switch (w->type) {// This command is very simple: { binder_inner_proc_unlock(proc); t = container_of(w, struct binder_transaction, work); } break; If (t->buffer->target_node) {CMD = BR_TRANSACTION; If (put_user(CMD, (uint32_t __user *) PTR)) {// } return 0; }Copy the code

The target_node must be present when the receiver thread is woken up to handle the transaction, so CMD is converted to BR_TRANSATION and placed in buffer. Remember that this is the driver sending the message to the receiver, so it is the BR prefix. If 0 is returned, normal execution is complete. After that, we’ll go back to the framework Native layer’s talkWithDriver function, find NO_ERROR and exit the loop, Remember that the receiver calls talkWithDriver from getAndExecuteCommand after creating the thread:

// IPCThreadState.cpp
status_t IPCThreadState::getAndExecuteCommand(){
    status_t result;
    int32_t cmd;
    result = talkWithDriver();
    if (result >= NO_ERROR) {
        cmd = mIn.readInt32();
        result = executeCommand(cmd);
    }
    return result;
}
Copy the code

The CMD read from mIn is the BR_TRANSATION command assigned in kernel space. Follow up:

// IPCThreadState.cpp status_t IPCThreadState::executeCommand(int32_t cmd){ status_t result = NO_ERROR; switch ((uint32_t)cmd) { case BR_TRANSACTION: { Parcel reply; PTR (tr.target. PTR) {if (reinterpret_cast<RefBase:: Weakref_type *>( Tr.target.ptr)->attemptIncStrong(this)) {// Here is a transform of Binder entities to type BBinder error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer, &reply, tr.flags); reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this); } } if ((tr.flags & TF_ONE_WAY) == 0) { sendReply(reply, 0); } } break; } return result; }Copy the code

Call the Transact method of a Binder entity after converting it to BBinder:

// Binder.cpp status_t BBinder::transact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags){ data.setDataPosition(0); status_t err = NO_ERROR; Switch (code) {default: // the onTransact method goes to android_util_binder.cpp err = onTransact(code, data, reply, flags); break; } return err; } // android_util_Binder.cpp virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0){ JNIEnv* env = javavm_to_jnienv(mVM); / / really invokes the Java layer of Binder entity jboolean res = env - > CallBooleanMethod (mObject, gBinderOffsets mExecTransact, code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags); }Copy the code

May see real invokes the Java layer of Binder entities will not be so easy to understand, this involves the jni native Java layers of knowledge points, actually gBinderOffsets. MExecTransact is a function of has already been registered.

// android_util_Binder.cpp
static int int_register_android_os_Binder(JNIEnv* env){
    jclass clazz = FindClassOrDie(env, kBinderPathName);
    gBinderOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
    gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact", "(IJJI)Z");
    gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");
    return RegisterMethodsOrDie(
        env, kBinderPathName,
        gBinderMethods, NELEM(gBinderMethods));
}
Copy the code

You can see that the Class object, the entity object, and the method name are all saved in the gBinderOffsets object. The method name is execTransact. Then when you actually call the Java layer, you go back to the execTransact method of the Binder Class.

// Binder.java
private boolean execTransact(int code, long dataObj, long replyObj,
            int flags) {
      Parcel data = Parcel.obtain(dataObj);
      Parcel reply = Parcel.obtain(replyObj);
      res = onTransact(code, data, reply, flags);
      return res;
}
Copy the code

After converting the input and output memory addresses into serialized Parcel objects, calling onTransact calls our Stub method, and the Binder driver to the Server and Client. In this case, reply stores the data to be returned by the Server to the Client. Let’s speed up our analysis! The code returns to the Native executeCommand function and then calls the sendReply function to send the Reply data back.

// IPCThreadState.cpp
status_t IPCThreadState::sendReply(const Parcel& reply, uint32_t flags)
{
    status_t err;
    status_t statusBuffer;
    err = writeTransactionData(BC_REPLY, flags, -1, 0, reply, &statusBuffer);
    return waitForResponse(NULL, NULL);
}
Copy the code

Finally, we wrap the data in a binder_transaction_data structure, write BC_REPLY and call waitForResponse, which is exactly the same as the process we analyzed earlier, continue to call talkWithDriver, The BC_REPLY command has much the same logic as the BC_TRANSATION command. The target thread is the client thread, which is already stored in the transaction queue. We add the transaction to the dormant client thread and wake it up. A BINDER_WORK_TRANSACTION_COMPLETE transaction will also be added to the server thread, and as with the previous logic, the server will go to sleep after it completes its work. After waking up the Client thread, the binder_thread_read function is used to read the commands and data passed from the Server to the Client. The logic has been explained in the previous section. The kernel code is very similar and easy to get lost. Then because the Server is calling back to the Client, there is no target_node in buffer, so we give CMD BR_REPLY. The code then exits the IOCTL function and goes back to the talkWithDriver function in the native layer and then back to the waitForResponse function, freeing the cache for the entire process and closing the loop in the waitForResponse function. The rest of the analysis is done, and the function will layer by layer go back to the Proxy getUserInfo method in the Java layer and get the Reply data.

At this point, the Binder communication process is completed. Finally, the sequence diagram is used to summarize:

Ps: The solid arrow is the call from the Client process, and the dotted arrow is the call from the Server process.

conclusion

This paper learned about the concept of Binder, used handwritten Binder to get familiar with the use of Binder in application layer, and went through the process from application layer to Framework layer and then to Linux source code. There will be a lot of C/C ++ code involved, even if I am not so familiar with C ++, it is no problem to read the code of the main process with the materials. However, many of the details of the process are not yet covered, which will be covered in the next Binder article.

Finally, summarize the full text with a paragraph:

Binder is the Cross-process communication mechanism for Android. Binder provides high performance for copy memory, UID verification security, and stability of Binder driver modules. Binder is ubiquitous in systems. Important components such as AMS, PMS, AND WMS communicate with user apps through Binder. Application layers often use AIDL for cross-process communication. The Binder communication model has four important roles: Client, Server, ServiceManager, and Binder drivers. ServiceManager is an address book used to record Binder references. Clients that need to use the functionality of Server Binder entities need to get a reference from the ServiceManager and use this reference to transfer data between Binder drivers. This paper introduces a Binder cross-process communication, and I analyze it mainly from three perspectives: BinderProxy->BpBinder->binder_ref Binder->BBinder->binder_node Binder entity The BC prefix indicates that the user process sends a message to the driver, while the BR prefix indicates that the driver sends a message to the user process. The most important commands are BC(R)_TRASACTION and BC(R)_REPLY. The thread pool is limited to 15 threads per process by default. Binder drivers manage the sleep and wake up of user processes to process transactions.

reference

Binder|Android Developers

Binder Learning Guide

Binder design and implementation

Binder article by Shengyang Lo

Introduction to Binder Driver series 2 – Binder Driver