This is the second part, which mainly involves the awakening of Binder threads and processes, encapsulation and parsing of transmitted data and other knowledge points.

  • Binder thread sleep and wake up (which wait queue the requester thread sleeps on and which queue the target thread wakes up on)
  • Differences between BC and BR in Binder protocols
  • How binders encapsulate layers of data as they transmit data – data structures used at different levels (command encapsulation)
  • Release of transfer data driven by Binder (release timing)
  • A simple Binder communication C/S model

Which queue the Client thread sleeps on and which queue the Server thread wakes up on

Let’s start with the first part: Which queue does the sending thread sleep on?

The sending thread must sleep on its own binder_thread wait queue, and the queue has only one sleeping thread

Part 2: Which wait queue does the Binder driver wake up when it wakes up threads?

To understand this, you need to understand the struct binder_transaction transaction_stack in binder_thread, which specifies the order in which transactions are executed: * The transaction at the top of the stack must be executed first.

If the local operation is BC_REPLY, it must wake up the thread that sent the wait, which is 100%, but if the local operation is BC_TRANSACTION, this is not necessarily the case, especially if the two ends are serving each other and requesting each other, as follows:

  • AT1, the common thread of process A, requests service B1 of process B, wakes up Binder thread of process B, and AT1 sleeps to wait for service completion
  • When the B1 service of process B needs to request the A1 service of process A, the Binder thread of process B sleeps and wakes up the Binder thread of process A to wait for the service to end
  • When A1 of process A needs B2 of process B, AT2, the binder thread of process A, wakes up THE binder thread of process B and waits for the service to end

At this point, the question arises: Which thread is appropriate to wake up? Is it the thread sleeping on the process queue, or the thread BT1 that was sleeping before? The answer is: the previously sleeping thread BT1, as shown in the diagram below

Binder_transaction1: binder_transaction1: binder_transaction1: binder_work Binder_transaction1 will also be added to its stack, as shown below:

Binder_transaction stack and wake up that queue 1

When the Binder thread of B wakes up and executes the service in the Binder entity, it finds that the service function needs to request the A1 service at the end of A, which needs to send A request to process A through Binder. Binder_transaction2 is added to the binder_transaction stack by the Binder thread of process A. Binder_transaction2 is added to the binder_transaction stack by the Binder thread of process A.

Binder_transaction stack and wake up that queue 2.jpg

At this point, there is no problem, but at the same time, A1 needs to request B2. At this point, A1 repeats the above process, creating a new binder_transaction3 and pushing it onto its stack. However, when writing to destination B, it faces the choice of writing to that queue. Is it the queue on binder_proc or the queue of BT1 threads waiting for A to return?

Binder_transaction stack and wake up that queue 3.jpg

It turns out, as I said, it’s a queue for BT1. Why? Because the previous binder_transaction2 on the BT1 queue is waiting for A to complete, but binder_transaction3 on the A queue is also waiting for binder_transaction3 to complete in the B queue, that is, Binder_transaction3 must be executed before binder_transaction2 on the B end, so wake up the BT1 thread and push binder_transaction3 onto the BT2 stack. Binder_transaction2 can be executed, so that the binder_transaction2 can be executed without hindering the execution of binder_transaction2, and the sleeping BT1 process can also be used more efficiently, because the final stack effect is:

Binder_transaction stack and wake up that queue 4.jpg

And when binder_transaction3 is done, it’s actually easy to unstack,

  • BT1 executes binder_transaction3, wakes up the A-side AT2 Binder thread, and BT1 continues to sleep (due to pending transactions)
  • AT2 executes binder_transaction2 to wake up BT1
  • BT1 executes binder_transaction1 to wake up AT1
  • Perform the end

The binder_transaction stack is used to query a binder_transaction stack. The binder_transaction stack is used to query a binder_transaction stack. The binder_transaction stack is used to query a binder_transaction stack. static void binder_transaction(struct binder_proc proc, struct binder_thread thread, struct binder_transaction_data *tr, int reply) {.. While (TMP) {// Find the thread that is waiting for its own process. If the thread is not waiting for its own process, do not look for it

                    // Check whether there is a thread waiting for the current thread in target_proc
                    // thread->transaction_stack
                    // Bind thread B to bind thread A
                    // The service of A must wait for the processing of B before returning to B. You can rest assured to use B
                        if (tmp->from && tmp->from->proc == target_proc)
                            target_thread = tmp->from; tmp = tmp->from_parent; . }}}Copy the code

Differences between BC and BR in Binder protocols

BC and BR are used to mark data and Transaction flows. BC flows from user space to kernel, while BR flows from kernel to user space. For example, when a Client sends a request to a Server, BC_TRANSACTION is used. The target_proc process wakes up, converts the BC to BR, and passes data and operations to the user space in kernel space.

BR is distinguished from BC

How binders encapsulate layers of data as they transmit data – data structures used at different levels (command encapsulation)

In the kernel, the structure object corresponding to the user space needs to be created, but the data transmitted is copied only once, that is, once.

Start from the Client request analysis, not considering the Java layer, only consider Native, take ServiceManager addService as an example, look at the specific

MediaPlayerService::instantiate();Copy the code

MediaPlayerService creates new Binder entities and registers them with ServiceManager:

void MediaPlayerService::instantiate() {
    defaultServiceManager()->addService(
            String16("media.player"), new MediaPlayerService());
}    Copy the code

DefaultServiceManager is the remote proxy that gets the ServiceManager:

sp<IServiceManager> defaultServiceManager()
{
    if(gDefaultServiceManager ! = NULL)return gDefaultServiceManager;

    {
        AutoMutex _l(gDefaultServiceManagerLock);
        if(gDefaultServiceManager == NULL) { gDefaultServiceManager = interface_cast<IServiceManager>( ProcessState::self()->getContextObject(NULL)); }}return gDefaultServiceManager;
}Copy the code

If I simplify the code, this is actually

return gDefaultServiceManager = BpServiceManager (new BpBinder(0));Copy the code

AddService is a call to BpServiceManager addService,

virtual status_t addService(const String16& name, const sp<IBinder>& service,
        bool allowIsolated)
{
    Parcel data, reply;
    data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
    data.writeString16(name);
    data.writeStrongBinder(service);
    data.writeInt32(allowIsolated ? 1 : 0);
    status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
    return err == NO_ERROR ? reply.readExceptionCode() : err;
}Copy the code

The first step of encapsulation, data encapsulation, is to write the transfer data to a Parcel object. A Parcel corresponds to ADD_SERVICE_TRANSACTION. WriteStrongBinder: writeStrongBinder:

status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
    return flatten_binder(ProcessState::self(), val, this);
}Copy the code

Convert to flat_binder_object to pass information about Binder types, Pointers, and so on:

status_t flatten_binder(const sp<ProcessState>& proc,
    const sp<IBinder>& binder, Parcel* out)
{
    flat_binder_object obj;

    obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
    if(binder ! = NULL) { IBinder *local = binder->localBinder();if(! local) { BpBinder *proxy = binder->remoteBinder();if (proxy == NULL) {
                ALOGE("null proxy");
            }
            const int32_t handle = proxy ? proxy->handle() : 0;
            obj.type = BINDER_TYPE_HANDLE;
            obj.handle = handle;
            obj.cookie = NULL;
        } else{ obj.type = BINDER_TYPE_BINDER; obj.binder = local->getWeakRefs(); obj.cookie = local; }}else {
        obj.type = BINDER_TYPE_BINDER;
        obj.binder = NULL;
        obj.cookie = NULL;
    }

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

Remote ()->transact(ADD_SERVICE_TRANSACTION, data, &reply); In the above environment, the remote() function returns BpBinder(0),

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

IPCThreadState::self()->transact(mHandle, code, data, reply, flags)

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

writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL); In this function, Parcel& data, handle, and code are wrapped as binder_transaction_data objects and copied to mOut’s data. BC_TRANSACTION is also written to mOut. Where the CMD corresponding to binder_transaction_data is BC_TRANSACTION, binder_transaction_data also stores the guidance new information for the data:

status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
    int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
    binder_transaction_data tr;
    tr.target.handle = handle;
    tr.code = code;
    tr.flags = binderFlags;
    tr.cookie = 0;
    tr.sender_pid = 0;
    tr.sender_euid = 0;
    const status_t err = data.errorCheck();
    if(err == NO_ERROR) { tr.data_size = data.ipcDataSize(); tr.data.ptr.buffer = data.ipcData(); tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t); tr.data.ptr.offsets = data.ipcObjects(); }.. mOut.writeInt32(cmd); mOut.write(&tr, sizeof(tr));return NO_ERROR;
}Copy the code

After mOut encapsulation is complete, the encapsulation continues with a call to talkWithDriver via waitForResponse:

status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    binder_write_read bwr;
    // Is the read buffer empty? There will be cases where both commands are returned BR_NOOP, BR_COMPLETE
    const bool needRead = mIn.dataPosition() >= mIn.dataSize();
    // 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.
    constsize_t outAvail = (! doReceive || needRead) ? mOut.dataSize() :0;
    bwr.write_size = outAvail;
    bwr.write_buffer = (long unsigned int)mOut.data();        // This is what we'll read.
    if (doReceive && needRead) {
        bwr.read_size = mIn.dataCapacity();
        bwr.read_buffer = (long unsigned int)mIn.data();
    } 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;
    bwr.read_consumed = 0;
    status_t err;
    do{.if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0) err = NO_ERROR;if (mProcess->mDriverFD <= 0) { err = -EBADF; }}while (err == -EINTR);

    if (err >= NO_ERROR) {
        if (bwr.write_consumed > 0) {
            if (bwr.write_consumed < (ssize_t)mOut.dataSize())
                mOut.remove(0, bwr.write_consumed);
            else
                mOut.setDataSize(0);
        }
        if (bwr.read_consumed > 0) {
            mIn.setDataSize(bwr.read_consumed);
            mIn.setDataPosition(0);
        }
        return NO_ERROR;
    }
    return err;
}Copy the code

TalkWithDriver continues to encapsulate data and commands in mOut as binder_write_read objects, Bwr. write_buffer is mOut’s data (binder_transaction_data+BC_TRRANSACTION), which then interacts with binder drivers via IOCtl to enter the kernel. CMD: binder_write_read; CMD: binder_write_read; The command codes corresponding to the BINDER_WRITE_READ level are generally related to threads, processes, and data transmission operations, and do not involve specific service processing. For example, BINDER_SET_CONTEXT_MGR is the thread programming ServiceManager thread. Create Handle 0 corresponding to binder_node, BINDER_SET_MAX_THREADS to set the maximum number of non-binder threads, and BINDER_WRITE_READ to indicate a read/write operation:

#define BINDER_CURRENT_PROTOCOL_VERSION 7
#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read)
#define BINDER_SET_IDLE_TIMEOUT _IOW('b', 3, int64_t)
#define BINDER_SET_MAX_THREADS _IOW('b', 5, size_t)
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#define BINDER_SET_IDLE_PRIORITY _IOW('b', 6, int)
#define BINDER_SET_CONTEXT_MGR _IOW('b', 7, int)
#define BINDER_THREAD_EXIT _IOW('b', 8, int)
#define BINDER_VERSION _IOWR('b', 9, struct binder_version)Copy the code

Take a closer look at binder_ioctl handling for BINDER_WRITE_READ,

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    switch (cmd) {
    caseBINDER_WRITE_READ: { struct binder_write_read bwr; . <! Copy binder_write_read object to kernel space -->if(copy_from_user(&bwr, ubuf, sizeof(bwr))) { ret = -EFAULT; goto err; } <! Whether to write data to the target process according to whether to write data to the target processif (bwr.write_size > 0) {
            ret = binder_thread_write(proc, thread, (void__user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed); } <! Read data into your own process according to whether you need to write data.if (bwr.read_size > 0) {
            ret = binder_thread_read(proc, thread, (void __user *)bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK);
            <! Wake up the blocking queue at the same timeif (! list_empty(&proc->todo)) wake_up_interruptible(&proc->wait); } break; } case BINDER_SET_MAX_THREADS: if (copy_from_user(&proc->max_threads, ubuf, sizeof(proc->max_threads))) { } break; case BINDER_SET_CONTEXT_MGR: .. break; case BINDER_THREAD_EXIT: binder_free_thread(proc, thread); thread = NULL; break; case BINDER_VERSION: .. }Copy the code

binder_thread_write(proc, thread, (void __user )bwr.write_buffer, bwr.write_size, * Bwr. write_buffer (BC_TRANSACTION+ binder_transaction_data)

int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread,
            void __user *buffer, int size, signed long *consumed)
{
    uint32_t cmd;
    void __user *ptr = buffer + *consumed;
    void __user *end = buffer + size;
    while (ptr < end && thread->return_error == BR_OK) {

        // binder_transaction_data BC_XXX+binder_transaction_data
        if (get_user(cmd, (uint32_t __user *)ptr))  (BC_TRANSACTION)
            return -EFAULT;
        ptr += sizeof(uint32_t);
        switch (cmd) {
        ..
        case BC_FREE_BUFFER: {
            ...
        }
        case BC_TRANSACTION:
        case BC_REPLY: {
            struct binder_transaction_data tr;
            if (copy_from_user(&tr, ptr, sizeof(tr)))
                return -EFAULT;
            ptr += sizeof(tr);
            binder_transaction(proc, thread, &tr, cmd == BC_REPLY);
            break;
        }
        case BC_REGISTER_LOOPER:
            ..
        case BC_ENTER_LOOPER:
            ...
            thread->looper |= BINDER_LOOPER_STATE_ENTERED;
            break;
        case BC_EXIT_LOOPER:
        // This will modify the data read,
        *consumed = ptr - buffer;
    }
    return 0;
}Copy the code

Binder_thread_write will further strip the binder_transaction_data tr from the CMD, handing it to binder_TRANSACTION. The rest are business related, but it involves the conversion process of a Binder entity and Handle, and the problem of two processes sharing some data in the kernel space in the same city, so further encapsulation and unencapsulation are carried out here. Binder_transaction and binder_work can be treated as process private, whereas binder_transaction is shared by two interacting processes: Binder_work is inserted into the work todo queue of a thread or process:

struct binder_thread {
    struct binder_proc *proc;
    struct rb_node rb_node;
    int pid;
    int looper;
    struct binder_transaction *transaction_stack;
    struct list_head todo;
    uint32_t return_error; /* Write failed, return error code in read buf */
    uint32_t return_error2; /* Write failed, return error code in read */
    wait_queue_head_t wait;
    struct binder_stats stats;
};Copy the code

Binder_transaction: Binder_transaction records the source and destination of the current transaction. Binder_transaction is the memory address of the binder_transaction data stored in the Binder after a copy.

struct binder_transaction {
    int debug_id;
    struct binder_work work;
    struct binder_thread *from; 
    struct binder_transaction *from_parent;
    struct binder_proc *to_proc;
    struct binder_thread *to_thread;
    struct binder_transaction *to_parent;
    unsigned need_reply:1;
    /* unsigned is_dead:1; * /    /* not used at the moment */
    struct binder_buffer *buffer;
    unsigned int    code;
    unsigned int    flags;
    long    priority;
    long    saved_priority;
    uid_t    sender_euid;
};Copy the code

The binder_transaction function does the following:

  • Create a new binder_TRANSACTION object and insert it into your own binder_TRANSACTION stack
  • Create a new binder_work object and insert it into the destination queue
  • Conversions between Binder and Handle (FLAT_binder_object)

      static voidbinder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply) { struct binder_transaction *t; struct binder_work *tcomplete; size_t *offp, *off_end; struct binder_proc *target_proc; struct binder_thread *target_thread = NULL; struct binder_node *target_node = NULL; * * key1** 
      if (reply) {
          in_reply_to = thread->transaction_stack;
          thread->transaction_stack = in_reply_to->to_parent;
          target_thread = in_reply_to->from;
          target_proc = target_thread->proc;
          }else {
          if (tr->target.handle) {
              struct binder_ref * ref;
                  ref = binder_get_ref(proc, tr->target.handle);
                  target_node = ref->node;
              } else{ target_node = binder_context_mgr_node; }.. . * * key2** t = kzalloc(sizeof( * t), GFP_KERNEL); . tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL); * * key3 **
      off_end = (void *)offp + tr->offsets_size;
    
      for (; offp < off_end; offp++) {
          struct flat_binder_object *fp;
          fp = (struct flat_binder_object *)(t->buffer->data + *offp);
          switch (fp->type) {
          case BINDER_TYPE_BINDER:
          case BINDER_TYPE_WEAK_BINDER: {
              struct binder_ref *ref;
              struct binder_node *node = binder_get_node(proc, fp->binder);
              if (node == NULL) {
                  node = binder_new_node(proc, fp->binder, fp->cookie);
              }..
              ref = (target_proc, node);                   if (fp->type == BINDER_TYPE_BINDER)
                  fp->type = BINDER_TYPE_HANDLE;
              else
                  fp->type = BINDER_TYPE_WEAK_HANDLE;
              fp->handle = ref->desc;
          } break;
          case BINDER_TYPE_HANDLE:
          case BINDER_TYPE_WEAK_HANDLE: {
              struct binder_ref *ref = binder_get_ref(proc, fp->handle);
              if (ref->node->proc == target_proc) {
                  if (fp->type == BINDER_TYPE_HANDLE)
                      fp->type = BINDER_TYPE_BINDER;
                  else
                      fp->type = BINDER_TYPE_WEAK_BINDER;
                  fp->binder = ref->node->ptr;
                  fp->cookie = ref->node->cookie;
              } else{ struct binder_ref *new_ref; new_ref = binder_get_ref_for_node(target_proc, ref->node); fp->handle = new_ref->desc; }}break; * * key4T ->work.type = BINDER_WORK_TRANSACTION; list_add_tail(&t->work.entry, target_list); tcomplete->type = BINDER_WORK_TRANSACTION_COMPLETE; list_add_tail(&tcomplete->entry, &thread->todo);if (target_wait)
          wake_up_interruptible(target_wait);
      return;Copy the code

    }

Key point 1 is to find the target process, key point 2 is to create binder_transaction and binder_work, key point 3 is to transform Binder entities and Handle, key point 4 is to insert binder_work into the target queue and wake up the corresponding wait queue. When handling the conversion between Binder entities and Handle, the following points should be noted:

  • Binder entities are first registered with other processes, ServiceManager, or AMS services in SystemServer
  • Binder driver assigns binder_ref to Client when the Client requests services. If the thread of the process requests a BINDER_TYPE_BINDER, fp->type = BINDER_TYPE_BINDER, Otherwise, fp->type = BINDER_TYPE_HANDLE.
  • The object in a Parcel in Android must be flat_binder_object

This completes the data structure that the process of writing the data goes through. For a brief look at the read process on the awakened side, the read starts with the binder_thread_read blocking in the kernel state. In the case of BC_TRANSACTION passed, binder_thread_read adds the BRXXX parameter depending on the scenario, Identifies the flow of data from the driver to user space:

enum BinderDriverReturnProtocol {

 BR_ERROR = _IOR_BAD('r'.0, int),
 BR_OK = _IO('r'.1),
 BR_TRANSACTION = _IOR_BAD('r'.2, struct binder_transaction_data),
 BR_REPLY = _IOR_BAD('r'.3, struct binder_transaction_data),

 BR_ACQUIRE_RESULT = _IOR_BAD('r'.4, int),
 BR_DEAD_REPLY = _IO('r'.5),
 BR_TRANSACTION_COMPLETE = _IO('r'.6),
 BR_INCREFS = _IOR_BAD('r'.7, struct binder_ptr_cookie),

 BR_ACQUIRE = _IOR_BAD('r'.8, struct binder_ptr_cookie),
 BR_RELEASE = _IOR_BAD('r'.9, struct binder_ptr_cookie),
 BR_DECREFS = _IOR_BAD('r'.10, struct binder_ptr_cookie),
 BR_ATTEMPT_ACQUIRE = _IOR_BAD('r'.11, struct binder_pri_ptr_cookie),

 BR_NOOP = _IO('r'.12),
 BR_SPAWN_LOOPER = _IO('r'.13),
 BR_FINISHED = _IO('r'.14),
 BR_DEAD_BINDER = _IOR_BAD('r'.15.void *),

 BR_CLEAR_DEATH_NOTIFICATION_DONE = _IOR_BAD('r'.16.void *),
 BR_FAILED_REPLY = _IO('r'.17),};Copy the code

Then the read thread creates a binder_transaction_data object based on the binder_TRANSACTION and passes it to user space via copy_to_user.

static int
binder_thread_read(struct binder_proc *proc, struct binder_thread *thread,
    void  __user *buffer, int size, signed long *consumed, int non_block)
{
    while (1) {
            uint32_t cmd;
         struct binder_transaction_data tr ;
            struct binder_work *w;
            struct binder_transaction *t = NULL;

        if(! list_empty(&thread->todo)) w = list_first_entry(&thread->todo, struct binder_work, entry);else if(! list_empty(&proc->todo) && wait_for_proc_work) w = list_first_entry(&proc->todo, struct binder_work, entry);else {
            if (ptr - buffer == 4 && !(thread->looper & BINDER_LOOPER_STATE_NEED_RETURN)) /* no data added */
                goto retry;
            break;
        }

    // Data size
        tr.data_size = t->buffer->data_size;
        tr.offsets_size = t->buffer->offsets_size;
    // The offset address must be added
        tr.data.ptr.buffer = (void *)t->buffer->data + proc->user_buffer_offset;
        tr.data.ptr.offsets = tr.data.ptr.buffer + ALIGN(t->buffer->data_size, sizeof(void *));
    / / write command
        if (put_user(cmd, (uint32_t __user *)ptr))
            return -EFAULT;
        // Write data structure to user space,
        ptr += sizeof(uint32_t);
        if (copy_to_user(ptr, &tr, sizeof(tr)))
            return -EFAULT;
        ptr += sizeof(tr);
}Copy the code

The upper-layer iocTRL is waked up. Assuming it is the server that is waked up, the request is normally executed, first mapping the data to the Parcel object using the Parcel’s ipcSetDataReference function. The specific requirements are then handled through BBinder’s Transact function;

status_t IPCThreadState::executeCommand(int32_t cmd)
{
    ...
    // Read the data request.
    case BR_TRANSACTION:
        {
            binder_transaction_data tr;
            Parcel buffer;
            buffer.ipcSetDataReference(
                reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                tr.data_size,
                reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
                tr.offsets_size/sizeof(size_t), freeBuffer, this); .// If the data is not null, the data is valid.
    if (tr.target.ptr) {
        // What is tr.cookie
        sp<BBinder> b((BBinder*)tr.cookie);
        const status_t error = b->transact(tr.code, buffer, &reply, tr.flags);
        if (error < NO_ERROR) reply.setError(error);

    }   Copy the code

Here b->transact(tr.code, buffer, &reply, tr.flags); Transact (mHandle, code, data, Reply, Flags) is handled in the same way that the Client calls transact(mHandle, code, data, Reply, Flags) to enter the corresponding business logic.

How do binders encapsulate layers of data as they transfer it — data structures used at different levels (command encapsulation. JPG

Release of transfer data driven by Binder (release timing)

In the process of communication with Binder, data is directly written to the target process kernel space from the user space that initiates the communication process, and this part of data is directly mapped to the user space and can only be released when the user space is used up. That is to say, the release time of kernel data in Binder communication should be controlled by the user space. The function used to free memory is binder_free_buf. Other data structures can be freed directly. The command used to free this function is BC_FREE_BUFFER. The upper user space common entrance is IPCThreadState: : freeBuffer:

void IPCThreadState::freeBuffer(Parcel* parcel, const uint8_t* data, size_t dataSize,
                                const size_t* objects, size_t objectsSize,
                                void* cookie)
{
    if(parcel ! = NULL) parcel->closeFileDescriptors(); IPCThreadState* state = self(); state->mOut.writeInt32(BC_FREE_BUFFER); state->mOut.writeInt32((int32_t)data); }Copy the code

So when does this function get called? The previous step in analyzing data delivery was to map the data in binder_transaction_data to the Parcel, but this was the key

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

    while (1) {... case BR_REPLY: { binder_transaction_data tr;// Note that there is no copy of the transfer data, only a copy of the pointer and the 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;// Free buffer, set data first, directly
            if (reply) {
                if ((tr.flags & TF_STATUS_CODE) == 0) {
                    // It involves data utilization and memory release
                    reply->ipcSetDataReference(
                        reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
                        tr.data_size,
                        reinterpret_cast<const size_t*>(tr.data.ptr.offsets),
                        tr.offsets_size/sizeof(size_t),
                        freeBuffer, this);Copy the code

A Parcel’s ipcSetDataReference function not only maps data to a Parcel object, but also to a data cleanup function

void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize,
    const size_t* objects, size_t objectsCount, release_func relFunc, void* relCookie)Copy the code

The function definition of release_func relFunc parameters, here is to specify the memory release function, specified here IPCThreadState: : freeBuffer function, in Native layer, Parcel after use, and walk after their own life cycle, It calls its destructor, which calls freeDataNoInit(), which indirectly calls the memory free function set above:

Parcel::~Parcel()
{
    freeDataNoInit();
}Copy the code

Once in kernel space, execute binder_free_buf to free the allocated memory and update binder_proc’s binder_buffer table to re-mark which memory blocks are used and which are not.

static void binder_free_buf(struct binder_proc *proc,
                struct binder_buffer *buffer)
{
    size_t size, buffer_size;
    buffer_size = binder_buffer_size(proc, buffer);
    size = ALIGN(buffer->data_size, sizeof(void *)) +
        ALIGN(buffer->offsets_size, sizeof(void *));
    binder_debug(BINDER_DEBUG_BUFFER_ALLOC,
             "binder: %d: binder_free_buf %p size %zd buffer"
             "_size %zd\n", proc->pid, buffer, size, buffer_size);

    if (buffer->async_transaction) {
        proc->free_async_space += size + sizeof(struct binder_buffer);
        binder_debug(BINDER_DEBUG_BUFFER_ALLOC_ASYNC,
                 "binder: %d: binder_free_buf size %zd "
                 "async free %zd\n", proc->pid, size,
                 proc->free_async_space);
    }
    binder_update_page_range(proc, 0,
        (void *)PAGE_ALIGN((uintptr_t)buffer->data),
        (void *)(((uintptr_t)buffer->data + buffer_size) & PAGE_MASK),
        NULL);
    rb_erase(&buffer->rb_node, &proc->allocated_buffers);
    buffer->free = 1;
    if(! list_is_last(&buffer->entry, &proc->buffers)) { struct binder_buffer *next = list_entry(buffer->entry.next, struct binder_buffer, entry);if(next->free) { rb_erase(&next->rb_node, &proc->free_buffers); binder_delete_free_buffer(proc, next); }}if(proc->buffers.next ! = &buffer->entry) { struct binder_buffer *prev = list_entry(buffer->entry.prev, struct binder_buffer, entry);if (prev->free) {
            binder_delete_free_buffer(proc, buffer);
            rb_erase(&prev->rb_node, &proc->free_buffers);
            buffer = prev;
        }
    }
    binder_insert_free_buffer(proc, buffer);
}Copy the code

Similarly, the Java layer frees memory by calling the Parcel’s freeData() function via JNI. In user space, each BR_TRANSACTION or BR_REPLY execution uses freeBuffer to send requests to free memory in the kernel

Simple Binder communication C/S model

Simple Binder communication model

I heard that you are good at Binder mechanism, to solve these problems (1) I heard that you are good at Binder mechanism, to solve these problems (2) I heard that you are good at Binder mechanism, to solve these problems (3)

For reference only, welcome correction