Implementation principle of Binder

9.0 Binder related source code is divided into three parts:

  • Java:frameworks/base/core/java/android/os/Binder.java
  • Native:frameworks/native/libs/binder/
  • Driver:common/drivers/android/binder.c

One more thing to be clear:

  • The user process: in view of theThe kernelSpace orBinder driveIn this case, toBinder driveThe process that sends messages
  • The client process: in view of theBinder communicationtheService processIn this case, I mean initiateBinder callThe process of

The book does not elaborate on these two terms, a little dizzy at first

Source code in hand, do everything

Binder design related issues

Remote invocation by Binder is an object-oriented remote invocation. How does it differ from procedural remote invocation?

  • Process orientedThe remote call is relatively easy to implement:
    • You just have to somehow get what needs to be doneFunction,andparameterPassed to theService process
    • thenService processAccording to theFunction,andparameterExecute the corresponding function and you’re done
  • object-orientedThe call to is more complicated:
    • Pairs of objects can be created from the same service class
      • Call not only throughFunction,andparameterTo identify the function to execute
      • Specify the object as well
    • Objects have a life cycle and need to be managed by listening
      • In the serviceEntity objectsAfter the death of the client processReference objectI also need to delete
      • This process needs to be done automatically and cannot be assisted by upper-level clients
      • Therefore, for administrative convenience, the client process needs (ProcessStateThe role of class) :
        • Centrally manage all of the processesBinder reference object
        • And responsible for their creation and release

Because Binder is object-oriented, multiple Binder entity objects can be created to serve different customers, each with its own data. They don’t interfere with each other.

In order for all reference objects and entity objects in the system to relate to each other:

  • BinderCreates a map of all processes in the driverReference objectandEntity objectstheAssociative table.
  • With the connection,BinderYou must also ensure that the user processEntity objectsandReference objectConsistent with the data in the driver
    • And in order to do that,BinderDefines its own reference counting rules, which are cross-process.

Parameter transfer problem:

  • Normal objects passed as parameters are not too much of a problem, just needserializationanddeserializationYou can do it.
  • butBinder objectWhen passed as a parameter, it will faceEntity objectsandReference objectThe problem of conversion.
    • This conversion is done automatically in the driver for the convenience of the upper application
  • ordinaryIPCWhen passing parameter data, two data copies are performed:
    • Once is copied from the caller’s data buffer to the kernel’s buffer
    • Copy once from the kernel’s buffer to the receiving process’s read buffer
  • BinderTo improve efficiency:
    • A cache is created for each process, which is shared between the kernel and user processes
    • Transferring data to the driver requires:
      • Copy from the sending process’s user-space cache to the destination process’s driver cache
      • The target process reading data from the driver does not need to copy it from kernel space to user space, but reads it directly from the kernel shared cache
    • This reduces one data replication process

For execution of Binder calls in the service process:

  • Each execution must be done in one thread
  • If threads are constantly created and released, there is significant system overhead.
  • useThe thread poolTo manage theBinder callThe implementation of the
    • inBinderDesign, except that the first thread is actively created by the application layer
    • Other threads in the thread pool are created at the request of the driver
    • This minimizes the number of threads and ensures that they arrive from the driverBinder callThreads are available.

Binder’s threading model

Binder thread pools

When the Zygote process starts, the onZygoteInit function of AppRuntime is called (chapter 8 of the book, not yet seen) with the following code:

    virtual void onZygoteInit(a)
    {
        sp<ProcessState> proc = ProcessState::self(a);ALOGV("App process: starting thread pool.\n");
        proc->startThreadPool(a); }Copy the code

All Android apps are fork from the Zygote process. Therefore, this code is valid for all application processes.

  • onZygoteInitThe function is called firstselfFunction to getProcessStateClass, and there is only one instance per process.ProcessStateConstructors are as follows:
    ProcessState::ProcessState(const char *driver) : mDriverName(String8(driver)) , mDriverFD(open_driver(driver)) //...... { 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); if (mVMStart == MAP_FAILED) { // *sigh* ALOGE("Using %s failed: unable to mmap transaction memory.\n", mDriverName.c_str()); close(mDriverFD); mDriverFD = -1; mDriverName.clear(); }}}Copy the code
  • ProcessStateClass does two things:
    • callopen_driver()Function to open theBinder equipment
    • callmmap()The function allocates a chunk of memory in the driver
      • The size of this block is slightly less than 1MB and is defined as follows:
      #define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
      Copy the code
      • This chunk of memory is not intended for use by upper-layer applications
      • This memory is used in Binder drivers to receive Binder data passed to the process
      • This is shared between the kernel and the application
  • getProcessStateClass after calling itsstartThreadPool()Function to start theThe thread pool, look at the code:
    void ProcessState::startThreadPool(a){
       AutoMutex _l(mLock);
       if(! mThreadPoolStarted) { mThreadPoolStarted =true;
            spawnPooledThread(true); }}Copy the code
    • To determinemThreadPoolStartedWhether it istrue, not fortrueBefore proceeding
    • Then put themThreadPoolStartedSet totrue, which suggests thatstartThreadPool()It only runs once in the process
    • Then callspawnPooledThreadFunction, with arguments oftrue
      void ProcessState::spawnPooledThread(bool isMain)
      {
        if (mThreadPoolStarted) {
            String8 name = makeBinderThreadName(a);ALOGV("Spawning new pooled thread, name=%s\n", name.string());
            sp<Thread> t = new PoolThread(isMain);
            t->run(name.string()); }}Copy the code
    • spawnPooledThreadThe threadpool () function creates a thread to be added to a thread pool
    • The function creates onePoolThreadClass, classrunThe function creates a thread
    • The argument passed in istrue, indicating that the thread isThe thread poolThe first thread of
    • Subsequent threads are created after being notified by the driver, passing in the parameterfalseLike this,
      status_t IPCThreadState::executeCommand(int32_t cmd){
        / /...
         case BR_SPAWN_LOOPER:
            mProcess->spawnPooledThread(false);
            break;
        / /...
      }
      Copy the code
    • Let’s seePoolThreadBusiness implementation of classthreadLoopfunction
      protected:
      virtual bool threadLoop(a)
      {
        IPCThreadState::self() - >joinThreadPool(mIsMain);
        return false;
      }
      Copy the code
      • returnfalseIs executed once. Why is it executed atthreadLoop()To execute the business logic, you can take a lookIn the AndroidthreadLoop
      • Call details for you to readframeworksSource code, the path should be in:frameworks/av/services/audioflinger/Threads.h
      • threadLoopThe function is just calledIPCThreadStatethejoinThreadPoolFunction, this function is just going to be practiced

Ok, let’s first comb through the thread pool:

  • First, it’s executed when the application startsonZygoteInitThe function, this is going to beOpening Binder equipmentandApply for shared memory space
  • Then, executeProcessStatethestartThreadPoolcreateThe thread pool
  • Then, by creatingPoolThreadTo create an instance ofThe thread poolThe first thread in
  • In the end,PoolThreadIt’s just a simple callIPCThreadStatethejoinThreadPoolfunction

More on IPCThreadState later

The thread that invokes Binder services

The invocation of the client Binder service is done through IBinder’s Transact function. The IBInder here is actually a BpBinder object with the following code:

status_t BpBinder::transact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    // Once a binder has died, it will never come back to life.
    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

This part of the code does:

  • throughIPCThreadStatetheselfStatic function acquisitionIPCThreadStateA pointer to the
    • IPCThreadStateObject is associated with each thread
    • selfThe function determines whether the thread is associatedIPCThreadStateobject
    • If there is no associated object, create a new oneIPCThreadStateObject and save to the current thread
  • getIPCThreadStateObject, then calledIPCThreadStatethetransact, let’s look at the code:
status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
    status_t err;
    flags |= TF_ACCEPT_FDS;
    / /...
    // Put the data to be sent into the class member variable mOut
    err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    if(err ! = NO_ERROR) {if (reply) reply->setError(err);
        return (mLastError = err);
    }
    if ((flags & TF_ONE_WAY) == 0) {// Synchronous invocation
        / /...
        if (reply) {
            // The caller asks for a result, sends data to the underlying layer and waits for the return value
            err = waitForResponse(reply);
        } else {
            // The caller does not need to return a value, but waits for the remote execution to complete
            // Use fakeRely to receive the returned Parcel object
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
       / /...
    } else {// asynchronously, the function returns immediately
        err = waitForResponse(NULL.NULL);
    }
    return err;
}
Copy the code
  • IPCThreadStatethetransactExecuted firstwriteTransactionDataPackets are saved to transmit datamOutvariable
  • Then throughwaitForResponsethemOutThe data inioctlSend to the underlying driver

There is only one Binder driven file descriptor for a process, and all IOCTL calls use this descriptor. If the client has several threads making remote calls at the same time, they will all wait on the ioctl function of the same descriptor. So which thread gets the reply when the data arrives?

  • The usualIO model, multiple threads waiting on the same descriptor will be woken up at random
  • But the AndroidBinder driveWhich records information about each Binder call, includingThread ID, soBinder driveKnow which thread to pass the return value to
    • It is simpler and more efficient to handle thread awakening by driver than to do the same thing at the application layer
    • However, from an architectural point of view, this design is poor, with coupling between the application layer and the driver.

Ok, let’s make a summary of this part:

  • The clientMakes a call from a thread, packs the parameters, and passesioctlFunction is passed to the driver
  • The clientHang and waitioctlReturns the result of the function
  • Binder driveLogs information about the calling thread, and then based on the calledBinder objectLooking forBinder serviceWhich is the process in whichThe service side
  • Binder drivefindThe service sideFirst check whether there are idle threads, no notificationThe service sidecreate
  • The service sideAfter getting the idle thread, according toBinder driveStored in theBBinder objectTo call the corresponding function
  • The service sidePass after the function returnsioctlPackage the results and pass them toBinder drive
  • Binder driveFind the caller thread based on the returned information
  • Binder driveFind the calling thread and wake it up, and passioctlThe function passes the result back
  • The clientThe thread returns the result and continues running

Passing of Binder objects

When a Binder object is passed as a parameter, two situations occur:

  • Binder entity objectPassed as a parameter:The Binder objectIs accomplished by copying objects at the receiving end, butBinder entity objectIs not replicable, so you need to create one in the client processBinder reference objectInstead of a solid object.
  • Binder referenceObject passed as a parameter:
    • If the destination of the pass is the process where the reference object corresponds to the entity object, then:
      • Binder frameworkYou have to put thisReference objectConverted toBinder entity object.
      • You cannot create a new oneEntity objects
      • The original must be found and usedEntity objects
    • If the destination of the delivery is anotherThe clientProcess, then:
      • You can’t just copy itReference object
      • Need to establish destination processReference objectandEntity objectsThe relationship between
      • The relationship was established inBinder driveDone in

Introduction to Binder object transfer process

Parameter passing through Binder calls is done through the Parcel class. A brief look at how a Binder entity object is converted into a Binder reference object:

  • inService processLt.IBinder (BBinder) objectTo join theParcel objectLater,Parcel objectWill:
    • Package the data and mark the data type asBINDER_TYPE_BINDER
    • theBpBinderPut the pointer intocookiefield
    • throughioctltheParcel objectThe data is passed toBinder driveIn the
  • Binder driveThe data passed in is checked if it is marked asBINDER_TYPE_BINDERData:
    • The server process is searched firstBinder Entity object table:
      • If there is no record of the entity object in the table, a new node is created and the information is saved.
    • The driver then looks at the client processBinder Object reference table:
      • If there is no record that refers to the object, new nodes are also created
      • And have a field in this node point to the server process’sBinder Entity object tableThe nodes in the
    • And then drive pairsParcel objectChange the data in:
      • The data fromBINDER_TYPE_BINDERInstead ofBINDER_TYPE_HANEL
      • At the same timehandleThe value of the setBinder Object reference tableThe nodes in the
    • Finally, the changed data is transmittedThe client process
  • The clientData is received and found in the dataBinder typeforBINDER_TYPE_HANELafter
    • usehandleValue as an argument, calledProcessStateFunctions in a classgetStrongProxyFoHandleTo get theBpBinder object
      • If the object does not exist, create a new object
      • thisBpBinder objectIt’s going to stay thereProcessStatethemHandleToObjectIn the table
    • In this way, the client getsBinder reference object

The process of writing Binder objects

With the overall flow above, let’s look at the writing details of Binder objects:

  • Parcel class:
    • writeBinder objectThe function of is:
      • writeStrongBinder: Writes a strong referenceBinder object
      • writeWeakBinder: Writes a weak referenceBinder object
    • readBinder objectThe function of is:
      • readStrongBinder: Gets a strong referenceBinder object
        • Strongly referenced Binder objects can be classified asEntity objectsandReference object
      • readWeakBinder: Gets a weak referenceBinder object
        • Weak references make no distinctionEntity objectsandReference object

Take a look at writeStrongBinder’s code:

status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
    return flatten_binder(ProcessState::self(), val, this);
}
status_t flatten_binder(const sp<ProcessState>& /*proc*/.const sp<IBinder>& binder, Parcel* out)
{
    The whole method is simply storing data into the obJ structure
    flat_binder_object obj;

    if (IPCThreadState::self() - >backgroundSchedulingDisabled()) {
        /* minimum priority for all nodes is nice 0 */
        obj.flags = FLAT_BINDER_FLAG_ACCEPTS_FDS;
    } else {
        /* minimum priority for all nodes is MAX_NICE(19) */
        obj.flags = 0x13 | FLAT_BINDER_FLAG_ACCEPTS_FDS;
    }
    
    if(binder ! =NULL) {
        // Call localBinder to distinguish between entity objects and reference objects
        IBinder *local = binder->localBinder(a);if(! local) {//binder references objects
            BpBinder *proxy = binder->remoteBinder(a);if (proxy == NULL) {
                ALOGE("null proxy");
            }
            const int32_t handle = proxy ? proxy->handle() : 0;
            obj.hdr.type = BINDER_TYPE_HANDLE;
            obj.binder = 0; /* Don't pass uninitialized stack data to a remote process */
            obj.handle = handle;
            obj.cookie = 0;
        } else { // binder entity object
            obj.hdr.type = BINDER_TYPE_BINDER;
            obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
            obj.cookie = reinterpret_cast<uintptr_t>(local); }}else {
        obj.hdr.type = BINDER_TYPE_BINDER;
        obj.binder = 0;
        obj.cookie = 0;
    }

    return finish_flatten_binder(binder, obj, out);
}
Copy the code

The flatten_binder whole method actually stores data into a flat_binder_object structure. Let’s look at the structure of flat_binder_object:

struct flat_binder_object {
	/* 8 bytes for large_flat_header. */
	__u32		type;
	__u32		flags;
	/* 8 bytes of data. */
	union {
		binder_uintptr_t	binder;	/* local object */
		__u32			handle;	/* remote object */
	};
	/* extra data associated with local object */
	binder_uintptr_t	cookie;
};
Copy the code

Let’s look at the properties in flat_binder_object:

  • typeThe type of:
    • BINDER_TYPE_BINDER: represents Binder entity objects
    • BINDER_TYPE_WEAK_BINDER: used to represent a weak reference to a Bindr entity object
    • BINDER_TYPE_HANDLE: used to represent Binder reference objects
    • BINDER_TYPE_WEAK_HANDLE: used to represent weak references to Binder reference objects
    • BINDER_TYPE_FD: represents a file descriptor
  • flagThe field is used to hold flags passed to the driver
  • union.binderIn the packagingEntity objectsIs stored in the objectWeak reference pointer
  • union.handleIn the packagingReference objectIs stored in an objectThe handle value
  • cookieFields are only used for packagingEntity objectsIs stored inBBinder pointer

parsingStrong referenceBinder object data process

The function in the Parcel class that parses the data is unflatten_binder, code like this:

status_t unflatten_binder(const sp<ProcessState>& proc,
    const Parcel& in, sp<IBinder>* out)
{
    const flat_binder_object* flat = in.readObject(false);

    if (flat) {
        switch (flat->hdr.type) {
            case BINDER_TYPE_BINDER:
                *out = reinterpret_cast<IBinder*>(flat->cookie);
                return finish_unflatten_binder(NULL, *flat, in);
            case BINDER_TYPE_HANDLE:
                *out = proc->getStrongProxyForHandle(flat->handle);
                return finish_unflatten_binder(
                    static_cast<BpBinder*>(out->get()), *flat, in); }}return BAD_TYPE;
}
Copy the code

Unflatten_binder’s logic is:

  • If it isBINDER_TYPE_BINDERType indicates that the received data type isBinder entity objectAt this time,Cookie fieldThe store is for this processBinder entity objectPointer to, can be directly converted toIBinderA pointer to the
  • If it isBINDER_TYPE_HANDLEType of data, is calledProcessState classthegetStrongProxyForHandleFunction to getBpBinderObject, the function code is as follows:
    sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
    {
       sp<IBinder> result;
       AutoMutex _l(mLock);
       // Check whether a reference object has been created in the process according to the handle
       // If no reference object for handle exists in the process, insert a new element into the table and return it
       handle_entry* e = lookupHandleLocked(handle);
       if(e ! =NULL) {
           IBinder* b = e->binder; 
           // Determine if there is a reference object based on the binder value in the returned element
           if (b == NULL| |! e->refs->attemptIncWeak(this)) {
               if (handle == 0) { // Handle 0 is the reference object of the ServiceManager
                   Parcel data;
                   // Send PING_TRANSACTION to check whether the ServiceManager exists
                   status_t status = IPCThreadState::self() - >transact(
                           0, IBinder::PING_TRANSACTION, data, NULL.0);
                   if (status == DEAD_OBJECT)
                      return NULL;
               }
               b = BpBinder::create(handle); Create a new reference object
               e->binder = b;// Add the binder attribute to the element
               if (b) e->refs = b->getWeakRefs(a); result = b; }else {
               result.force_set(b);// If the reference object already exists, it is put into the returned object result
               e->refs->decWeak(this); }}return result;
    }
    Copy the code

The getStrongProxyForHandle() function calls lookupHandleLocked() to find the corresponding reference object of handle in the process. All process reference objects are stored in the mHandleToObject variable of ProcessState. The mHandleToObject variable is defined as follows:

Vector<handle_entry> mHandleToObject;
Copy the code
  • mHandleToObjectIs aVectorCollection class, element typehandle_entry
    • handle_entryThe structure is simple:
    struct handle_entry {
      IBinder* binder;
      RefBase::weakref_type* refs;
    };
    Copy the code
  • lookupHandleLocked()The function is just usinghandleAs aKey itemsTo find the correspondinghandle_entryIf no, create a new onehandle_entryAnd add to the collection
  • When gethandle_entryIf, afterhandleA value of 0 indicates that what is being created isServiceManagertheReference object
    • And send thePING_TRANSACTIONMessage to checkServiceManagerHas been created?

IPCThreadState class

Each Binder thread has an associated object of the IPCThreadState class. The IPCThreadState class interacts with Binder drivers, sending and receiving Binder data, and processing messages to and from Binder drivers.

We already know from the Binder thread model:

  • Binder serviceAt startup, the service thread is calledjoinThreadPool()function
  • The remote invocationBinder serviceThe client thread is calledwaitForResponse()function

Both of these functions are defined in the IPCThreadState class, so let’s look at them separately.

waitForResponse()function

The function is defined as follows:

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    uint32_t cmd;
    int32_t err;

    while (1) {
        if ((err=talkWithDriver()) < NO_ERROR) break;// Communicate with the driver
        err = mIn.errorCheck(a);if (err < NO_ERROR) break;
        if (mIn.dataAvail() = =0) continue;// No data, start the loop again

        cmd = (uint32_t)mIn.readInt32(a);// Read data
        / /...
        switch (cmd) {
        case BR_TRANSACTION_COMPLETE:
            if(! reply && ! acquireResult)goto finish;
            break;
        / /... Omit some case statements
        case BR_REPLY:
        // Message returned by Binder calls
        default:
            err = executeCommand(cmd);
            if(err ! = NO_ERROR)goto finish;
            break;
        }
    }

finish:
    / /... Error handling
    return err;
}
Copy the code

The waitForResponse() function is an infinite while loop in which the following is repeated:

  • calltalkWithDriverfunctionSend/receivedata
  • If a message is returned from the driver, it will passA switch statementProcess messages.
    • If an error message is received or a message returned by the call is passedgotoStatement outThe while loop
    • If there are any unprocessed messages, theexecuteCommandFunction to deal with

Binder calls code that returns type BR_REPLY:

        case BR_REPLY:
            {
                binder_transaction_data tr;
                // Read data according to the size of the binder_transaction_data structure
                err = mIn.read(&tr, sizeof(tr));
                ALOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY");
                if(err ! = NO_ERROR)goto finish;

                if (reply) {
                    // Reply is not null, indicating that the caller needs to return the result
                    if ((tr.flags & TF_STATUS_CODE) == 0) {
                        // Binder calls successfully, setting the data from the driver into the Reply object
                        reply->ipcSetDataReference(
                            reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                            tr.data_size,
                            reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
                            tr.offsets_size/sizeof(binder_size_t),
                            freeBuffer, this);
                    } else {
                        //bind failed to call binder, use freeBuffer to free the buffer allocated in the driver
                        err = *reinterpret_cast<const status_t*>(tr.data.ptr.buffer);
                        freeBuffer(NULL.reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                            tr.data_size,
                            reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
                            tr.offsets_size/sizeof(binder_size_t), this); }}else {
                    // The caller does not need to return the result, using the freeBuffer function to free the buffer allocated in the driver
                    freeBuffer(NULL.reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                        tr.data_size,
                        reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
                        tr.offsets_size/sizeof(binder_size_t), this);
                    continue; }}Copy the code

The process for the BR_REPLY type is as follows:

  • ifBinder callReturns successfully, andThe callerReturn values are also required
    • Put the received data inParcelobjectreplyIn return
  • ifBinder callNot successful, orThe callerThere is no need to return data
    • throughfreeBufferFrees the buffer allocated in the driver

Because Binder provides a shared memory space between the driver and the application layer, Binder data is received without the need for additional buffers to be created and copied, but the shared space can quickly be used up if the driver is not notified to release unused memory in the buffer.

The reply->ipcSetDataReference method sets a Parcel object and also passes a pointer to the freeBuffer. If the reply object is deleted, the freeBuffer will be called to release the buffer in the driver.

The waitForResponse() function sends data called by Binder and waits for the return value. Why do you need to repeatedly interact with the driver in a loop? There are two reasons:

  • One is that the application layer is required to pass the message protocolBC_TRANSACTIONsendBinder call dataAfter:
    • The driver replies to the application layer firstBC_TRANSACTION_COMPLETEMessage, indicating that it has been said and approvedBinder call data.
    • Then the upper application calls againtalkWithDriverTo wait for the driver to return the call result
    • If the result of the call is returned, it will be receivedBR_REPLYThe message
  • While waiting for the call to return, the driver might send a message to the thread, using the thread to do some work…

joinThreadPoolfunction

As you already know in the Binder thread pool section, the Binder service is started when the application is started, and the final method executed is the joinThreadPool function.

Let’s look at the function definition:

void IPCThreadState::joinThreadPool(bool isMain)
{
    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);

    status_t result;
    do {
        processPendingDerefs(a);//now get the next command to be processed, waiting if necessary
        result = getAndExecuteCommand(a);// Reads and processes messages sent by the driver
        if(result < NO_ERROR && result ! = TIMED_OUT && result ! = -ECONNREFUSED && result ! = -EBADF) {abort(a); }// Let this thread exit the thread pool if it is no longer
        // needed and it is not the main process thread.
        if(result == TIMED_OUT && ! isMain) {break; }}while(result ! = -ECONNREFUSED && result ! = -EBADF); mOut.writeInt32(BC_EXIT_LOOPER); Send a thread exit message to the driver before exit
    talkWithDriver(false);
}
Copy the code

The joinThreadPool function is structured as a while loop.

  • Parameter passed inisMain
    • If it istrue(usually made by the first thread in the thread pool), is sent to the driverBC_ENTER_LOOPERThe message
      • sendBC_ENTER_LOOPERIs marked by the driver as the “master” thread
      • Will not be driven to quit at idle time
    • Otherwise, sendBC_REGISTER_LOOPER.
    • Both messages tell the driver that the thread is ready to receive Binder calls from the driver
  • Enter the loop and callprocessPendingDerefs()function
    • To deal withIPCThreadStateIn the objectmPendingWeakDerefsandmPendingStrongDerefstheBinder objectReference count of
      • mPendingWeakDerefsandmPendingStrongDerefsAre allVectorA collection of
    • When received from the driverBR_RELEASEThe message will be put into itBinder objectIn themPendingStrongDerefsIn the
    • And in theprocessPendingDerefs()The object reference count function describes the object reference count
  • callgetAndExecuteCommandfunction
    • Call from a functiontalkWithDriverRead the data passed by the driver
    • And then callexecuteCommandTo perform the

Now let’s look at the talkWithDriver and executeCommand functions

talkWithDriverfunction

The talkWithDriver function sends the data stored in the mOut variable of the IPCThreadState class to the driver via the ioctl function, and puts the data returned by the driver into the mIn variable of the class.

The talkWithDriver function is coded as follows:

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    if (mProcess->mDriverFD <= 0) {
        return -EBADF;
    }
    // IocTL data structure used for transmission
    binder_write_read bwr;

    // Is the read buffer empty?
    // Check whether the data in mIn has been read. If not, continue reading
    const bool needRead = mIn.dataPosition() >= mIn.dataSize(a);// We don't want to write anything if we are still reading
    // from data left in the input buffer and the caller
    // has requested to read the next data.
    // There is a lot of detail in English
    // If you do not need to read data (doReceive=false, needRead=true), then you are ready to write data
    const size_toutAvail = (! doReceive || needRead) ? mOut.dataSize() : 0;
    bwr.write_size = outAvail;// Indicates the length to be written
    bwr.write_buffer = (uintptr_t)mOut.data(a);// A pointer to the data to be written

    // This is what we'll read.
    if (doReceive && needRead) {
        bwr.read_size = mIn.dataCapacity(a); bwr.read_buffer = (uintptr_t)mIn.data(a); }else {
        bwr.read_size = 0;
        bwr.read_buffer = 0;
    }
    // Return immediately if there is nothing to do.
    if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;

    bwr.write_consumed = 0;// This field is followed by the length of data read by the driver (the length of data written to the driver)
    bwr.read_consumed = 0;// This field is followed by the length of the data returned from the driver
    status_t err;
    do {
// 9.0 has added some platform decisions, it is possible to support multiple platforms in the future
#if defined(__ANDROID__)
        // Use ioctl to exchange data with the driver
        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
            err = NO_ERROR;
        else
            err = -errno;
#else
        err = INVALID_OPERATION;
#endif
        if (mProcess->mDriverFD <= 0) {
            // The device node is not availableerr = -EBADF; }}while (err == -EINTR);

    if (err >= NO_ERROR) {
        if (bwr.write_consumed > 0) {
            if (bwr.write_consumed < mOut.dataSize())
                // If the length of data already written to the driver is smaller than the length of data in mOut
                // Delete the data that has been written to the driver
                // The rest of the data will be sent next time
                mOut.remove(0, bwr.write_consumed);
            else {
                // All data has been written to the driver, reset mOut
                mOut.setDataSize(0);
                // Do some pointer cleanup
                processPostWriteDerefs();
            }
        }
        if (bwr.read_consumed > 0) {
            // Read data from driver, set mInt object
            mIn.setDataSize(bwr.read_consumed);
            mIn.setDataPosition(0);
        }
        return NO_ERROR;
    }

    return err;
}
Copy the code
  • Data ready to be sent to the driver is stored in member variablesmOutIn the
  • Data read from the driver is stored in member variablesmIntIn the
  • calltalkWithDriverIf themIntThere are data
    • Indicates that the message sent by the driver is not processed
    • This function call will not read data from the driver
  • ioctlfunction
    • The command used isBINDER_WRITE_READ
    • Need to bebinder_write_readStructure as a parameter
    • Let’s look at the drivers

executeCommandfunction

The executeCommand function is a large switch statement that processes messages from the driver.

We came across some news earlier, which probably included:

  • BR_SPAWN_LOOP: driver notification to start a new thread
  • BR_DEAD_BINDER: Driver notificationBinder serviceNews of death.
  • BR_FINISHED: driver notification thread exit message
  • BR_ERROR.BR_OK.BR_NOOP: Drives simple reply messages
  • BR_RELEASE.BR_INCREFS.BR_DECREFS: Driver notifications increase and decreaseBinder objectReference counts across processes
  • BR_TRANSACTION, : Drives notification to proceedBinder callThe news of the

The focus is on BR_TRANSACTION, which is defined as follows:

case BR_TRANSACTION:
        {
            binder_transaction_data tr;
            result = mIn.read(&tr, sizeof(tr));
            if(result ! = NO_ERROR)break; // Data is abnormal

            Parcel buffer;
            // Set the Parcel object Buffer with the data received from the driver
            buffer.ipcSetDataReference(
                reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                tr.data_size,
                reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
                tr.offsets_size/sizeof(binder_size_t), freeBuffer, this);

            const pid_t origPid = mCallingPid;
            const uid_t origUid = mCallingUid;
            const int32_t origStrictModePolicy = mStrictModePolicy;
            const int32_t origTransactionBinderFlags = mLastTransactionBinderFlags;
            // Retrieves the caller's process ID and EUID from the message
            mCallingPid = tr.sender_pid;
            mCallingUid = tr.sender_euid;
            mLastTransactionBinderFlags = tr.flags;

            Parcel reply;
            status_t error;

            if (tr.target.ptr) {
                // We only have a weak reference on the target object, so we must first try to
                // safely acquire a strong reference before doing anything else with it.
                if (reinterpret_cast<RefBase::weakref_type*>(
                        tr.target.ptr)->attemptIncStrong(this)) {
                    // If the PTR pointer is not null, the cookie holds a pointer to BBinder
                    // Call the cookie transact function
                    error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer,
                            &reply, tr.flags);
                    // Clear the pointer in time
                    reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this);
                } else{ error = UNKNOWN_TRANSACTION; }}else {
                // If tr.target. PTR is 0, it is a ServiceManager
                error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);
            }
            // If the call is synchronous, send the reply object back, otherwise do nothing
            if ((tr.flags & TF_ONE_WAY) == 0) {
                LOG_ONEWAY("Sending reply to %d!", mCallingPid);
                if (error < NO_ERROR) reply.setError(error);
                sendReply(reply, 0);
            } else {
                LOG_ONEWAY("NOT sending reply to %d!", mCallingPid);
            }

            mCallingPid = origPid;
            mCallingUid = origUid;
            mStrictModePolicy = origStrictModePolicy;
            mLastTransactionBinderFlags = origTransactionBinderFlags;
        }
        break;
Copy the code

BR_TRANSACTION messages are processed as follows:

  • After parsing the message, place it inParcelThe type ofbufferIn the object
  • Use the values in the message parametersBBinder pointerTo invoke thetransactfunction
    • transactThe function is passed inFunction,,buffer,reply
    • transactFunction according toFunction,To call the correspondingThe service function
    • The service functionAfter the execution, the result is saved inreplyIn the object
  • If the call is synchronous, usesendReplyThe function returnsreplyobject
  • If it’s an asynchronous call, do nothing and end the call