The following key classes in the Binder mechanism are described in detail in the source code:

  1. ProcessState
  2. IPCThreadState
  3. BpBinder
  4. BinderProxy

Binder architecture

Before we begin, let’s briefly review binder’s overall architecture and get a sense of the roles of these classes.

For a typical IPC communication process between two applications:

A Proxy is used to encapsulate remote binder entities obtained by the Client through ServiceManager or AMS. For example, ServiceManagerProxy, The encapsulated remote Binder entity is a BinderProxy.

BpBinder and BinderProxy are actually the same thing: remote Binder entities, only a Native layer and a Java layer. BpBinder internally holds a binder handle value, handle.

ProcessState is a process singleton that opens Binder driver devices and MMAP. IPCThreadState is a thread singleton responsible for specific command communication with the Binder driver.

The transact() call is made by the Proxy, which packages the data to a Parcel, and works its way down to the BpBinder, calling the transact() method of IPCThreadState in the BpBinder and passing in the handle value. IPCThreadState is used to execute specific binder commands.

The basic process that the binder drives to the Server is that the Server receives a request from the Client via IPCThreadState, works its way up, and then calls back to the Stub onTransact() method.

The following combined with source code detailed analysis of these classes, thoroughly understand them! (This may require a lot of your attention and time. If not, please save this article.)

ProcessState

ProcessState specifically manages Binder operations for each application process. Only one instance of ProcessState exists in the same process, and Binder devices and memory mappings are opened only when ProcessState objects are created. The relevant code is as follows:

///frameworks/native/libs/binder/ProcessState.cpp
sp<ProcessState> ProcessState::self(){
    Mutex::Autolock _l(gProcessMutex);
    if(gProcess ! =NULL) { Return ProcessState if ProcessState was created
        return gProcess;
    }
    gProcess = new ProcessState;
    return gProcess;
}
Copy the code

External unity guarantees a process singleton of ProcessState by obtaining ProcessState from the ProcessState::self() method. The constructor of ProcessState is as follows:

#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))
#define DEFAULT_MAX_BINDER_THREADS 15

ProcessState::ProcessState()
    : mDriverFD(open_driver()) // Open binder devices
    , mVMStart(MAP_FAILED) // Set the initialization to MAP_FAILED. The mapping will change after success
    , mThreadCountLock(PTHREAD_MUTEX_INITIALIZER)
    , mThreadCountDecrement(PTHREAD_COND_INITIALIZER)
    , mExecutingThreadsCount(0)
    , mMaxThreads(DEFAULT_MAX_BINDER_THREADS) // Maximum number of binder threads
    , mStarvationStartTimeMs(0)
    , mManagesContexts(false)
    , mBinderContextCheckFunc(NULL)
    , mBinderContextUserData(NULL)
    , mThreadPoolStarted(false)
    , mThreadPoolSeq(1) {if (mDriverFD >= 0) { // The binder driver has been successfully opened
           // Map a virtual memory space of application processes to the Binder driver for data communication on this memory block
           mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
           if (mVMStart == MAP_FAILED) { // Mapping failure processing
               ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");
               close(mDriverFD);
               mDriverFD = - 1; }}}Copy the code

The constructor of ProcessState initializes several important variables, including opening binder devices with open_driver(), initializing the maximum number of Binder threads, Bind BINDER_VM_SIZE (close to 1M) memory with binder driver MMAP.

GetStrongProxyForHandle (), getWeakProxyForHandle(), getStrongProxyForHandle(), Etc. You can obtain the corresponding IBinder object through the handle value. GetWeakProxyForHandle () method is as follows:

wp<IBinder> ProcessState::getWeakProxyForHandle(int32_t handle){
    wp<IBinder> result;
    AutoMutex _l(mLock);
    // Check whether the IBinder has been created
    handle_entry* e = lookupHandleLocked(handle);
    if(e ! =NULL) {
        IBinder* b = e->binder;
        if (b == NULL| |! e->refs->attemptIncWeak(this)) {
            b = new BpBinder(handle); // Create a new BpBinder before creating one
            result = b;
            e->binder = b;
            if (b) e->refs = b->getWeakRefs();
        } else {
            result = b;
            e->refs->decWeak(this); }}return result;
}
Copy the code

The lookupHandleLocked() method is used to find out if the process has already created the IBinder. If it has not, it creates one. LookupHandleLocked () uses a Vector to hold the created IBinder:

Vector<handle_entry> mHandleToObject;

struct handle_entry{
    IBinder* binder;
    RefBase::weakref_type* refs;
}
Copy the code

As shown in the code above, each IBinder object is stored in a handLE_Entry structure, that is, there is a global list of all IBinder objects in ProcessState.

IPCThreadState

ProcessState corresponds to a process and is an in-process singleton, while IPCThreadState corresponds to a Thread and is a Thread Local singleton.

The Binder driver is turned on in ProcessState, mMap mapping is done, and the IOCtl () function is called, but mostly for initial configuration. IPCThreadState is responsible for executing specific commands such as BR_TRANSACTION. When a command is passed from the upper layer, its transact function is called. This function is simplified as follows:

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags){
    // Check whether the data is valid
    status_t err = data.errorCheck();
    if (err == NO_ERROR) {
        // Pack the data into mOut
        err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    }
    if ((flags & TF_ONE_WAY) == 0) { // Not one way call, need to wait for reply
        if (reply) {
            err = waitForResponse(reply);
        } else{ Parcel fakeReply; err = waitForResponse(&fakeReply); }}else { // Do not wait for a reply
        err = waitForResponse(NULL.NULL);
    }
    return err;
}
Copy the code

IPCThreadState has mIn and mOut parcels. MIn holds data that has been read from elsewhere, mOut holds data to be written to elsewhere, and writeTransactionData() stores data to mOut. Ready to write to the binder driver.

The waitForResponse() method does the actual writing to the Binder driver. The simplified waitForResponse() method looks like this:

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult){
    uint32_t cmd;
    int32_t err;
    while (1) {
        // Further call talkWithDriver to perform writing to the Binder driver
        if ((err=talkWithDriver()) < NO_ERROR) break;
        err = mIn.errorCheck(); // Check the data validity
        if (err < NO_ERROR) break;
        if (mIn.dataAvail() == 0) continue; // Check the data validity
        cmd = (uint32_t)mIn.readInt32(); // Get the orders from the binder driver
        switch (cmd) {
            // Processing command
            caseBR_TRANSACTION_COMPLETE:{... }caseBR_DEAD_REPLY:{... }caseBR_FAILED_REPLY:{... }caseBR_ACQUIRE_RESULT:{... }caseBR_REPLY:{... }default:
                // Other commands are handled in the executeCommand method
                err = executeCommand(cmd);
                if(err ! = NO_ERROR)goto finish;
                break; }}return err;
}
Copy the code

As you can see, waitForResponse() does not write data directly to the Binder. Instead, talkWithDriver is called to process the data. Then waitForResponse() processes the commands sent by the Binder driver. Such as BR_TRANSACTION_COMPLETE:

case BR_TRANSACTION_COMPLETE:
       if(! reply && ! acquireResult)goto finish;
       break;
Copy the code

In the transact() method, reply and acquireResult are both passed NULL if the call is one way, so the loop exits without waiting for a binder driven reply.

So far, from Transact () to waitForResponse(), the data is ready to be sent and the subsequent Binder driven responses are processed, but we haven’t seen the actual data written to the Binder driven code. But we already know that in the talkWithDriver() method, this method mainly does three jobs:

  1. Prepare the binder_write_read data
  2. Writing to the Binder driver
  3. Process driver response

To simplify talkWithDriver() code into three parts, the first is to prepare the binder_write_read data:

status_t IPCThreadState::talkWithDriver(bool doReceive){
    binder_write_read bwr; Binder drives the accepted data format
    const bool needRead = mIn.dataPosition() >= mIn.dataSize();
    constsize_t outAvail = (! doReceive || needRead) ? mOut.dataSize() :0;
    bwr.write_size = outAvail; // The amount of data to be written
    bwr.write_buffer = (uintptr_t)mOut.data(); // The data to be written
    // This is what we'll read.
    if (doReceive && needRead) {
        bwr.read_size = mIn.dataCapacity(); // The amount of data to be read
        bwr.read_buffer = (uintptr_t)mIn.data(); // The memory space to store the read data
    } else {
        bwr.read_size = 0;
        bwr.read_buffer = 0;
    }
    // If you don't need to read or write, return it directly
    if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;
Copy the code

The doReceive argument to the talkWithDriver() method is declared in ipcThreadstate.h and is true by default. No argument is passed in waitForResponse(), so doReceive is true here.

Binder_write_read is shared by the binder driver and user mode to store read/write data. Binder_write_read is used by the binder driver to determine whether to read or write data: The internal variable read_size>0 indicates that data is to be read, and the internal variable write_size>0 indicates that data is to be written. If both variables are greater than 0, data is written first and then read.

With binder_write_read ready, let’s look at how to write to the Binder driver. The actual write is a single line of code:

    ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)
Copy the code

The binder_ioctl() function is called to the binder driver, which is not extended here. Let’s take a look at the third job of talkWithDriver(), which handles the driver’s reply:

     if (bwr.write_consumed > 0) { // The data was successfully written
         if (bwr.write_consumed < mOut.dataSize())
             mOut.remove(0, bwr.write_consumed);
         else
             mOut.setDataSize(0);
     }
     if (bwr.read_consumed > 0) { // Read data successfully
         mIn.setDataSize(bwr.read_consumed);
         mIn.setDataPosition(0);
     }
     return NO_ERROR;
}
Copy the code

BWR. Write_consumed > 0 indicates that the binder driver consumed data in mOut. Therefore, the data that has been processed should be removed. Read_consumed > 0 indicates that the binder driver has successfully returned data to us and written it to the memory address specified above by bwr.read_buffer.

At this point the talkWithDriver is finished and the data read is put into mIn, which corresponds to the logic in waitForResponse() above to fetch data from mIn.

BpBinder

When ProcessState getWeakProxyForHandle() method is introduced above, a BpBinder object is constructed to return:

new BpBinder(handle)
Copy the code

IPCThreadState, the object that interacts primarily with the Binder driver, takes the handle value as the first argument to its Transact method:

status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
Copy the code

Note the two clues: what does it mean to give the handle to the BpBinder and pass the handle when calling the IPCThreadState Transact method? A BpBinder object is an operation encapsulation associated with a remote handle, which is implemented internally through IPCThreadState. But this is just conjecture. Let’s verify this with the BpBinder source code. First, the constructor:

BpBinder::BpBinder(int32_t handle)
   : mHandle(handle)
   , mAlive(1)
   , mObitsSent(0)
   , mObituaries(NULL)

   ALOGV("Creating BpBinder %p handle %d\n".this, mHandle);

   extendObjectLifetime(OBJECT_LIFETIME_WEAK);
   IPCThreadState::self()->incWeakHandle(handle);
}
Copy the code

The OBJECT_LIFETIME_WEAK configuration of the smart pointer means that the BpBinder object will be destroyed only if both the strong and weak counters of the BpBinder object are 0. In addition, you can see that holding the handle value through the internal variable mHandle is used in the BpBinder’s Transact method:

status_t BpBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags){
    if (mAlive) {
        status_t status = IPCThreadState::self()->transact(
            mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }
    return DEAD_OBJECT;
}
Copy the code

It does call the transact method of IPCThreadState internally, which validates the description of a BpBinder object as an operation wrapper associated with a remote handle that is implemented internally by IPCThreadState.

BinderProxy

Conclusion: BinderProxy is BpBinder, “P” in “BpBinder” is Proxy, but BpBinder is Native layer, BinderProxy is Java layer. BinderProxy and BpBinder inherit IBinder interfaces of Java and Native layer respectively, namely, ibinder. h and ibinder. Java. They can be regarded as the same interface, and both define transact and other methods.

To verify this conclusion, look at the source code of servicemanager. Java.

    private static IServiceManager getIServiceManager(a) {
        if(sServiceManager ! =null) {
            return sServiceManager;
        }
        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        return sServiceManager;
    }
Copy the code

The asInterface method takes an object of type IBinder, and the BinderInternal getContextObject() method returns a BinderProxy object:

    /** * Return the global "context object" of the system. This is usually * an implementation of IServiceManager, which you can use to find * other services. */
    public static final native IBinder getContextObject(a);
Copy the code

There’s a comment here. Why is the Service Manager named Context Object? Because every process requires AN IPC operation, IPC exists as the basic configuration for the process. The comments in the above code describe this method more directly and help us understand it.

Next, the corresponding native implementation of this method is in android_util_Binder. CPP:

static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz){
    sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
    return javaObjectForIBinder(env, b);
}
Copy the code

The getContextObject() method for ProcessState is as follows:

sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/) {return getStrongProxyForHandle(0);
}
Copy the code

The getWeakProxyForHandle() method of ProcessState is described above, which internally constructs a BpBinder object to return, The getStrongProxyForHandle() method, like getWeakProxyForHandle(), returns a BpBinder object, but with a strong reference type.

The javaObjectForIBinder() method is no longer expanded, it constructs a BinderProxy object based on the BpBinder object and records the BpBinder’s memory address so that later from Java->Native, The BpBinder object can be obtained according to the BinderProxy.

Corresponding to javaObjectForIBinder(), the ibinderForJavaObject() method of Android_util_binder.cpp is invoked by BinderProxy -> BpBinder.

The last

Don’t be afraid of difficulties, as long as we patient and unremitting breakthrough to its single point, repeatedly mulling, no matter how complex the mechanism is just a paper tiger!