Author: reading the little snail

Blog: https://www.jianshu.com/u/3b1099674c2c

The introduction

Binder is responsible for most of the communication between Android processes. The Binder is Android’s vascular system, responsible for communication between processes of different service modules.

The understanding of Binder can vary from large to small. The communication knowledge of Binder is not involved in daily APP development. At most, some knowledge of Binder is involved in the use of Service and AIDL.

Binder, at a smaller scale, sums it up in one sentence:

An IPC interprocess communication mode in which data from process A is sent to process B.

Generally speaking, there is a lot of knowledge involved, such as Android extension for original Binder driver, Zygote process incubation for Binder communication support, Binder encapsulation of Java layer, Native layer for Binder communication encapsulation, Binder obituary mechanism and so on. A lot of Binder framework analysis is from ServiceManager, Binder driver, addService, getService to analyze, in fact, these are mainly for the system to provide services, but bindService startup services are still very different.

This article will briefly outline some points that Binder has difficulty understanding, but will not be too detailed tracking analysis.

The outline

The following are the points outlined in this paper, which have been cut down for length. Interested students can read the original text.

  • Directional guidance for Binder, how to find target binders, invoke processes or threads

  • Binder_ref = binder_ref = binder_ref = binder_ref = binder_ref = binder_ref = binder_ref

  • Binder one copy principle (copy directly to the target thread’s kernel space, kernel space corresponds to user space)

  • The difference between a system service and a started service such as bindService

  • Binder thread, Binder main thread, Client request thread

1. How does Binder guide accurately

Binder entity services are registered with ServiceManager through addService, for example: ActivityManagerService, PackageManagerService, and PowerManagerService are generally system services.

There are also services pulled up through bindService, usually implemented by the developers themselves.

This section starts with the services managed by the ServiceManager added by addService. There are many articles that analyze ServiceManager. This article does not analyze ServiceManager, but only briefly.

The ServiceManager service is special and can be directly used by all applications. The Handle of the ServiceManager is fixed to the Client and is 0. Therefore, the ServiceManager service does not need to be queried.

The key to understanding Binder’s directional guidance is to understand the Binder’s four red-black trees. First look at the binder_proc structure, which has four red-black trees inside: Threads, Nodes, refs_BY_DESC, and refs_by_node.

Binder_nodes is the kernel data structure for the Binder entity. Binder_node contains information about the binder_proc process and the address of the Binder entity. Nodes red-black tree is located in binder_proc. You can see that Binder entities are actually in-process, not in-thread.

(P.S., if you don’t know much about red-black trees, imagine what a binary search tree looks like, and write a special article about it.)

struct binder_proc { struct hlist_node proc_node; struct rb_root threads; struct rb_root nodes; struct rb_root refs_by_desc; struct rb_root refs_by_node; . struct list_head todo; wait_queue_head_t wait; . };Copy the code

Now suppose there are a bunch of clients and services. How can a Client access a Service?

The Service registers binder entities with the ServiceManager using addService. To use Servcie, clients need to use getService to request the Service from the ServiceManager.

When a Service registers with a ServiceManager through addService, the ServiceManager stores information about the Service in the Service list of its own process. Add a binder_ref node to the Service in the Binder_ref red-black tree of the ServiceManager process so that the ServiceManager can obtain information about the Service’s Binder entities.

When the Client requests the Service from the ServiceManager using getService, the ServiceManager searches for the Service in the registered Service list and returns the Service to the Client.

In this process, the ServiceManager adds a binder_ref node to the binder_ref red-black tree of the Client process. Either the Service process inserts binder_ref into the ServiceManager, or the ServiceManager process inserts binder_ref into the Client. The Client can then access the Service by retrieving the binder_ref from the Handle.

Binder_ref adds logic

After getService, the binder_ref reference is obtained, and binder_PROC and binder_node information are obtained. The Client can then purposefully insert the binder_transaction transaction into the binder_proc backlog. Also, if the process is sleeping, invoke the process.

When a Client sends a request to a Service, the thread on the binder_proc is usually awakened:

struct binder_ref { int debug_id; struct rb_node rb_node_desc; struct rb_node rb_node_node; struct hlist_node node_entry; struct binder_proc *proc; struct binder_node *node; uint32_t desc; int strong; int weak; struct binder_ref_death *death; };Copy the code

2. Why does binder_proc have two binder_ref red-black trees

There are two binder_ref red-black trees in binder_proc. The nodes in the binder_ref red-black trees are actually reused, but the query method is different. One is through the handle and the other is through the node.

Personal Understanding:

Refs_by_node red-black tree is used by binder drivers to write data to user space. Refs_by_desc is used by binder drivers to write data to user space.

For example, with addService, binder drivers look for the binder_ref structure in ServiceManager’s binder_proc and create a new binder_ref structure if none is present.

Binder_get_ref_for_node creates a binder_ref structure for the Client process and allocates a handle to getService. It is also inserted into the refS_BY_DESC red-black tree.

The refS_BY_node red-black tree is used by binder drivers to write data to user space. Refs_by_desc is used by user Spaces to write data to binder drivers. When user Spaces have obtained binder_ref handles created by binder drivers, We can find the response binder_ref from refs_by_DESC by binder_get_ref, and then find the target binder_node.

There are two red-black trees, which distinguish between objects and the direction of data flow.

// Uint32_t desc Static struct binder_ref *binder_get_ref(struct binder_proc *proc, uint32_t desc){ struct rb_node *n = proc->refs_by_desc.rb_node; struct binder_ref *ref; while (n) { ref = rb_entry(n, struct binder_ref, rb_node_desc); if (desc < ref->desc) n = n->rb_left; else if (desc > ref->desc) n = n->rb_right; else return ref; } return NULL; }Copy the code

Binder_get_ref_for_node: The binder_get_ref_for_node red-black tree is searched mainly by binder_node. If it cannot be found, Create a new binder_ref and insert it into both red-black trees

static struct binder_ref *binder_get_ref_for_node(struct binder_proc *proc, struct binder_node *node){ struct rb_node *n; struct rb_node **p = &proc->refs_by_node.rb_node; struct rb_node *parent = NULL; struct binder_ref *ref, *new_ref; while (*p) { parent = *p; ref = rb_entry(parent, struct binder_ref, rb_node_node); if (node < ref->node) p = &(*p)->rb_left; else if (node > ref->node) p = &(*p)->rb_right; else return ref; } new_ref = kzalloc(sizeof(*ref), GFP_KERNEL); // Binder_ref = kzalloc(sizeof(*ref), GFP_KERNEL); if (new_ref == NULL) return NULL; binder_stats_created(BINDER_STAT_REF); new_ref->debug_id = ++binder_last_id; new_ref->proc = proc; new_ref->node = node; rb_link_node(&new_ref->rb_node_node, parent, p); // insert rb_insert_color(&new_ref->rb_node_node, &proc->refs_by_node) into proc->refs_by_node; // Is the ServiceManager new_ref->desc = (node == binder_context_mgr_node)? 0:1; // Allocate a Handle to insert into refs_by_desc for (n = rb_first(&proc->refs_by_desc); n ! = NULL; n = rb_next(n)) { ref = rb_entry(n, struct binder_ref, rb_node_desc); if (ref->desc > new_ref->desc) break; new_ref->desc = ref->desc + 1; P = &proc->refs_by_desc.rb_node; while (*p) { parent = *p; ref = rb_entry(parent, struct binder_ref, rb_node_desc); if (new_ref->desc < ref->desc) p = &(*p)->rb_left; else if (new_ref->desc > ref->desc) p = &(*p)->rb_right; else BUG(); } rb_link_node(&new_ref->rb_node_desc, parent, p); // Insert rb_insert_color(&new_ref->rb_node_desc, &proc->refs_by_desc); if (node) { hlist_add_head(&new_ref->node_entry, &node->refs); binder_debug(BINDER_DEBUG_INTERNAL_REFS, "binder: %d new ref %d desc %d for " "node %d\n", proc->pid, new_ref->debug_id, new_ref->desc, node->debug_id); } else { binder_debug(BINDER_DEBUG_INTERNAL_REFS, "binder: %d new ref %d desc %d for " "dead node\n", proc->pid, new_ref->debug_id, new_ref->desc); } return new_ref; }Copy the code

The binder_transaction function is called when the Binder driver is accessing target_PROC. It is also easy to understand that the Handle has no cross-process meaning. The Handle in process A is invalid in process B.

Two binder_ref red black trees

3. Binder one copy principle

Binder’s choice of primary process communication is also due to its high performance. Binder can use user space data from process A to process Bin A single copy. There are two main points involved here:

  • Binder’s map function maps kernel space directly to user space, which has direct access to kernel space data

  • Process A’s data is copied directly to process B’s kernel space (one copy).

#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2)) ProcessState::ProcessState() : mDriverFD(open_driver()) , mVMStart(MAP_FAILED) , mManagesContexts(false) , mBinderContextCheckFunc(NULL) , mBinderContextUserData(NULL) , mThreadPoolStarted(false) , mThreadPoolSeq(1){ if (mDriverFD >= 0) { .... // mmap the binder, providing a chunk of virtual address space to receive transactions. mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0); . }}Copy the code

Mmap function is a system call. Mmap will get the virtual address space (VM_areA_struct * VMA) from the current process and obtain the VMA from mmAP_region. Then call file->f_op-> Mmap (file, VMA). After entering drive processing, will be in the memory is allocated in a continuous virtual address space, and good pre-allocated page table, have been used and unused logo, initial address, and user space migration and so on, through this step, can make the Binder in kernel space data directly through the pointer address mapping to the user space, for the use of process in user space, This is the basis for a copy, which is identified in the kernel as follows:

struct binder_proc { struct hlist_node proc_node; Struct rb_root threads; struct rb_root nodes; struct rb_root refs_by_desc; struct rb_root refs_by_node; int pid; struct vm_area_struct *vma; Struct mm_struct *vma_vm_mm; struct task_struct *tsk; struct files_struct *files; struct hlist_node deferred_work_node; int deferred_work; void *buffer; Ptrdiff_t user_buffer_offset; Struct list_head buffers; Struct rb_root free_buffers; struct rb_root free_buffers; Struct rb_root allocated_buffers struct rb_root allocated_buffers; // Join all allocated virtual memory blocks under the red-black tree with the starting address of the memory block as index}Copy the code

The above address mapping is only enabled when the APP is started, but it does not involve data copy. The following is the data copy operation.

When data is copied from user space to kernel space, it is directly copied from the user space of the current process to the kernel space of the target process. This process is handled in the requester thread and the operation object is the kernel space of the target process. Look at the following code:

static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply){ ... If a Binder transaction (struct binder_transaction) requires memory when passing through a binder transaction, the binder_alloc_buf function is called to allocate the required memory for the binder transaction. Note that: // The user space and the kernel space have only one offset address. // The user space and the kernel space have only one offset address. Buffer = binder_alloc_buf(target_proc, tr->data_size, tr->offsets_size,! reply && (t->flags & TF_ONE_WAY)); t->buffer->allow_user_free = 0; t->buffer->debug_id = t->debug_id; T ->buffer->transaction = t; T ->buffer->target_node = target_node; t->buffer->target_node; trace_binder_transaction_alloc_buf(t->buffer); if (target_node) binder_inc_node(target_node, 1, 0, NULL); // Calculate the starting address of the offset array of flat_binder_object, aligned with 4 bytes. offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *))); // Struct flat_binder_object is a copy of the user process and the kernel buffer // // It's actually copied to the target process, because t itself is allocated in the target process's kernel space,  if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) { binder_user_error("binder: %d:%d got transaction with invalid " "data ptr\n", proc->pid, thread->pid); return_error = BR_FAILED_REPLY; goto err_copy_data_failed; }Copy the code

Binder_alloc_buf (target_proc, tr->data_size,tr->offsets_size,! The reply && (t->flags & TF_ONE_WAY) function requests memory from the target_proc process space. Buffer (t->buffer->data, tr->data.ptr.buffer, tr->data_size)); Since data in Binder kernel space can be mapped directly to user space, there is no need to copy it to user space.

That’s how one copy works.

Data is mapped to a kernel space is to add a user space offset, and the data of the first address, the size of the data is copied to a Parcel of user space structure, specific can consult Parcel. CPP Parcel: : ipcSetDataReference function.

Binder one copy principle

4. Differences between system services and services such as bindService

Services can be classified into system services and common services. System services are created and registered with the ServiceManager process when the system is started. A normal Service is typically a Service started through ActivityManagerService, or a Service component of one of the four components. There are differences in the implementation and use of these two services, mainly from the following aspects:

  • Startup mode of the service

  • Registration and management of services

  • How the service is requested

Binder interfaces are incorporated into ServiceManager services as Binder entities. These services are generally implemented by SystemServer processes, such as AMS, WMS, PKMS, power management, etc. Binder threads are used to listen for Client requests and distribute them to the service entity classes that respond to them. Media services). BindService services are usually started by the Activity’s startService or by the startService of another context. Binder Service entities are used to encapsulate Service components. The startup process is not managed by the ServcieManager, but by ActivityManagerService, similar to Activity management.

Let’s look at service registration and management: System services are registered with the ServiceManager addService. These services require specific permissions to be registered with the ServiceManager. BindService can register services with ActivityManagerService. ActivityManagerService manages services in a different way than ServiceManager. Detailed analysis can be done.

Finally, see how to use the system service. Generally, the service handle is obtained through the getService of the ServiceManager. This process is actually to query the registered system service in the ServiceManager. BindService starts a Service by searching for the corresponding Service component in ActivityManagerService. Binder handles are sent to the Client.

Difference between system service and bindService startup service

5. Concept and difference of Binder thread, Binder main thread and Client request thread

Binder threads are the carrier of Binder services. Binder threads are only meaningful for the server, and are not necessary for the requestor. However, the processing mechanism of Android system is mostly C/S for each other. For example, when APP and AMS interact, they are each other’s C and S. Binder threads are a concept that will not be discussed here.

A Binder thread is a thread that performs business with a Binder entity. How can a normal thread be a Binder thread? This is as simple as starting a Loop thread that listens to the Binder character devices. There are several ways to do this on Android, but all of them are actually listening to the Binder.

In the case of the ServerManager process, the main thread is the Binder thread, which implements deathless threads through binder_loop:

void binder_loop(struct binder_state *bs, binder_handler func){ ... for (;;) {<! Res = ioctl(bs->fd, BINDER_WRITE_READ, & BWR); <! Res = binder_parse(BS, 0, readBUf, bWR. read_consumed, func); . }}Copy the code

The key code above is 1 to block listening for client requests, 2 to process requests, and this is an infinite loop, do not exit. Binder main thread (Binder main thread) Binder main thread (Binder main thread)

Binder threads differ from Binder main threads: All Binder threads are considered as the main thread of the Binder. When the SystemServer main thread runs to the end, the Loop listens to the Binder device and becomes an infinite Loop thread. Key code is as follows:

extern "C" status_t system_init(){ ... ALOGI("System server: entering thread pool.\n"); ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool(); ALOGI("System server: exiting thread pool.\n"); return NO_ERROR; }Copy the code

ProcessState::self()->startThreadPool() creates a new Binder main thread, and PCThreadState::self()->joinThreadPool() converts the current thread into a Binder main thread. StartThreadPool will eventually call joinThreadPool as well.

void IPCThreadState::joinThreadPool(bool isMain){ ... status_t result; do { int32_t cmd; . Result = talkWithDriver(); if (result >= NO_ERROR) { ... Result = executeCommand(CMD); If (result == TIMED_OUT &&! isMain) { break; } while (result! = -ECONNREFUSED && result ! = -EBADF); }status_t IPCThreadState::talkWithDriver(bool doReceive){ do { ... If (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, & BWR) >= 0)}Copy the code

> >mDriverFD, BINDER_WRITE_READ, & BWR) >= 0) JoinThreadPool is the most common way for a common thread to become a Binder thread, using executeCommand to execute the request. If you don’t believe me, look at the MediaService function main_mediaserver:

Int main(int argc, char** argv){... sp<ProcessState> proc(ProcessState::self()); sp<IServiceManager> sm = defaultServiceManager(); ALOGI("ServiceManager: %p", sm.get()); AudioFlinger::instantiate(); MediaPlayerService::instantiate(); CameraService::instantiate(); AudioPolicyService::instantiate(); registerExtensions(); ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool(); }Copy the code

JoinThreadPool is a joinThreadPool thread that is a Binder thread. If it is not the main thread, look at the following function:

void IPCThreadState::joinThreadPool(bool isMain)void ProcessState::spawnPooledThread(bool isMain){    if (mThreadPoolStarted) {        String8 name = makeBinderThreadName();        ALOGV("Spawning new pooled thread, name=%s\n", name.string());        sp<Thread> t = new PoolThread(isMain);        t->run(name.string());    }}Copy the code

The key is whether the isMain passed to joinThreadPool is true. Binder main is useless because there is no entry in the source code for handling the two. Check TIMED_OUT with Binder.

Finally, the binder request thread of common Client, such as the main thread of our APP, when startActivity requests AMS, the main thread of APP is actually the binder request thread. During the process of communication with the binder, The Client’s Binder request thread blocks until the Service finishes processing and returns the result.

This article first here, more content, see more understanding.

Recent Articles:

  • How to disable Android 9.0 private apis

  • Bitmap and OOM’s love-hate relationship

  • Android Handler ThreadLocal

Question of the day:

What’s hard to understand about Binder mechanics?

Punch card format:

Punch X days, answer: XXX.