听说你Binder机制学的不错,来面试下这几个问题(二)

简介: 听说你Binder机制学的不错,来面试下这几个问题(二)

本篇是第二篇,主要是涉及线程与进程的唤醒,数据传输的封装与解析,


  • Binder线程的睡眠与唤醒(请求线程睡在哪个等待队列上,唤醒目标端哪个队列上的线程)
  • Binder协议中BC与BR的区别
  • Binder在传输数据的时候是如何层层封装的--不同层次使用的数据结构(命令的封装)
  • Binder驱动传递数据的释放(释放时机)
  • 一个简单的Binder通信C/S模型


Client端线程睡眠在哪个队列上,唤醒Server端哪个等待队列上的线程


先看第一部分:发送端线程睡眠在哪个队列上?


发送端线程一定睡眠在自己binder_thread的等待队列上,并且,该队列上有且只有自己一个睡眠线程


再看第二部分:在Binder驱动去唤醒线程的时候,唤醒的是哪个等待队列上的线程?

理解这个问题需要理解binder_thread中的 struct binder_transaction * transaction_stack栈,这个栈规定了transaction的执行顺序:栈顶的一定先于栈内执行。

如果本地操作是BC_REPLY,一定是唤醒之前发送等待的线程,这个是100%的,但是如果是BC_TRANSACTION,那就不一定了,尤其是当两端互为服务相互请求的时候,场景如下:


  • 进程A的普通线程AT1请求B进程的B1服务,唤醒B进程的Binder线程,AT1睡眠等待服务结束
  • B进程的B1服务在执行的的时候,需要请求进程A的A1服务,则B进程的Binder线程BT1睡眠,等待服务结束。


这个时候就会遇到一个问题:唤醒哪个线程比较合适?是睡眠在进程队列上的线程,还是之前睡眠的线程AT1?答案是:之前睡眠等待B服务返回的线程AT1,具体看下面的图解分析


首先第一步A普通线程去请求B进程的B1服务,这个时候在A进程的AT1线程的binder_ref中会将binder_transaction1入栈,而同样B的Binder线程在读取binder_work之后,也会将binder_transaction1加入自己的堆栈,如下图:

image.png

而当B的Binder线程被唤醒后,执行Binder实体中的服务时,发现服务函数需要反过来去请求A端的A1服务,那就需要通过Binder向A进程发送请求,并新建binder_transaction2压入自己的binder_transaction堆栈,这个没有任何问题。但是,在A端入栈的时候,会面临一个抉择,写入那个队列?是binder_proc上的队列,还是正在等候B1服务返回的AT1线程的队列?


image.png

结果已经说过,是AT1的队列,为什么呢?因为AT1队列上的之前的binder_transaction1在等待B进程执行完,但是B端执行binder_transaction1时候,需要等待binder_transaction2执行完,也就是说,在binder_transaction2执行完毕前,A端的binder_transaction1一定是不会被执行的,也就是线程AT1在B执行binder_transaction2的时候,一定是空闲的,那么,不妨唤醒AT1线程,让它帮忙执行完binder_transaction2,执行完之后,AT1又会睡眠等待B端返回,这样,既不妨碍binder_transaction1的执行,同样也能提高AT1线程利用率,出栈的过程其实就简单了,

  • AT1 执行binder_transaction2,唤醒B端BT1 Binder线程,并且AT1继续睡眠(因为还有等待的transaction)
  • BT1 处理binder_transaction2结果,并执行完binder_transaction1,唤醒AT1
  • AT1处理binder_transaction1返回结果 执行结束


不妨再深入一点,如果A端binder_transaction2又需要B进程B2服务,这个时候是什么效果唤醒谁,答案是BT1,这就杜绝了两端循环请求的,不断增加线程池容量。

image.png

从这里可以看出,Binder其实设计的还是很巧妙的,让线程复用,提高了效率,还避免了新建不必要的Binder线程,这段优化在binder驱动实现代码如下:其实就是根据binder_transaction记录,处理入栈唤醒问题

static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply)
    {..
        while (tmp) {
                    // 找到对方正在等待自己进程的线程,如果线程没有在等待自己进程的返回,就不要找了
                    // 判断是不target_proc中,是不是有线程,等待当前线程
                    // thread->transaction_stack,这个时候,
                    // 是binder线程的,不是普通线程 B去请求A服务,
                    // 在A服务的时候,又请求了B,这个时候,A的服务一定要等B处理完,才能再返回B,可以放心用B
                        if (tmp->from && tmp->from->proc == target_proc)
                            target_thread = tmp->from;
                        tmp = tmp->from_parent;
          ...           }
        } }


Binder协议中BC与BR的区别


BC与BR主要是标志数据及Transaction流向,其中BC是从用户空间流向内核,而BR是从内核流线用户空间,比如Client向Server发送请求的时候,用的是BC_TRANSACTION,当数据被写入到目标进程后,target_proc所在的进程被唤醒,在内核空间中,会将BC转换为BR,并将数据与操作传递该用户空间。

image.png


Binder在传输数据的时候是如何层层封装的--不同层次使用的数据结构(命令的封装)


内核中,与用户空间对应的结构体对象都需要新建,但传输数据的数据只拷贝一次,就是一次拷贝的时候。


从Client端请求开始分析,暂不考虑java层,只考虑Native,以ServiceManager的addService为例,具体看一下


MediaPlayerService::instantiate();

MediaPlayerService会新建Binder实体,并将其注册到ServiceManager中:


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

这里defaultServiceManager其实就是获取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;
}

如果将代码简化其实就是


return gDefaultServiceManager = BpServiceManager (new BpBinder(0));

addService就是调用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;
}

这里会开始第一步的封装,数据封装,其实就是讲具体的传输数据写入到Parcel对象中,与Parcel对应是ADD_SERVICE_TRANSACTION等具体操作。比较需要注意的就是data.writeStrongBinder,这里其实就是把Binder实体压扁:


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

具体做法就是转换成flat_binder_object,以传递Binder的类型、指针之类的信息:

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);
}

接下来看 remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply); 在上面的环境中,remote()函数返回的就是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;
}

之后通过  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;
    }

writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);是进一步封装的入口,在这个函数中Parcel& data、handle、code、被进一步封装成binder_transaction_data对象,并拷贝到mOut的data中去,同时也会将BC_TRANSACTION命令也写入mOut,这里与binder_transaction_data对应的CMD是BC_TRANSACTION,binder_transaction_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;
}

mOut封装结束后,会通过waitForResponse调用talkWithDriver继续封装:


status_t IPCThreadState::talkWithDriver(bool doReceive)
{
    binder_write_read bwr;
    // Is the read buffer empty? 这里会有同时返回两个命令的情况 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.
    const size_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;
}

talkWithDriver会将mOut中的数据与命令继续封装成binder_write_read对象,其中bwr.write_buffer就是mOut中的data(binder_transaction_data+BC_TRRANSACTION),之后就会通过ioctl与binder驱动交互,进入内核,这里与binder_write_read对象对应的CMD是BINDER_WRITE_READ,进入驱动后,是先写后读的顺序,所以才叫BINDER_WRITE_READ命令,与BINDER_WRITE_READ层级对应的几个命令码一般都是跟线程、进程、数据整体传输相关的操作,不涉及具体的业务处理,比如BINDER_SET_CONTEXT_MGR是将线程编程ServiceManager线程,并创建0号Handle对应的binder_node、BINDER_SET_MAX_THREADS是设置最大的非主Binder线程数,而BINDER_WRITE_READ就是表示这是一次读写操作:

#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)

详细看一下binder_ioctl对于BINDER_WRITE_READ的处理,


static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    switch (cmd) {
    case BINDER_WRITE_READ: {
        struct binder_write_read bwr;
        ..
        <!--拷贝binder_write_read对象到内核空间-->
        if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {
            ret = -EFAULT;
            goto err;
        }
        <!--根据是否需要写数据处理是不是要写到目标进程中去-->
        if (bwr.write_size > 0) {
            ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed);
        }
      <!--根据是否需要写数据处理是不是要读,往自己进程里读数据-->
        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);
            <!--是不是要同时唤醒进程上的阻塞队列-->
            if (!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:
    ..
}

binder_thread_write(proc, thread, (void __user )bwr.write_buffer, bwr.write_size, &bwr.write_consumed)这里其实就是把解析的binder_write_read对象再剥离,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:
        // 这里会修改读取的数据,
        *consumed = ptr - buffer;
    }
    return 0;
}

binder_thread_write会进一步根据CMD剥离出binder_transaction_data tr,交给binder_transaction处理,其实到binder_transaction数据几乎已经剥离极限,剩下的都是业务相关的,但是这里牵扯到一个Binder实体与Handle的转换过程,同城也牵扯两个进程在内核空间共享一些数据的问题,因此这里又进行了一次进一步的封装与拆封装,这里新封装了连个对象 binder_transaction与binder_work,有所区别的是binder_work可以看做是进程私有,但是binder_transaction是两个交互的进程共享的:binder_work是插入到线程或者进程的work todo队列上去的:


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;
};

这里主要关心一下binder_transaction:binder_transaction主要记录了当前transaction的来源,去向,同时也为了返回做准备,buffer字段是一次拷贝后数据在Binder的内存地址。


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;
};

binder_transaction函数主要负责的工作:


  • 新建binder_transaction对象,并插入到自己的binder_transaction堆栈中
  • 新建binder_work对象,插入到目标队列
  • Binder与Handle的转换 (flat_binder_object)
static void binder_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;
   **关键点1** 
  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;
          }
        ..。
  **关键点2**
   t = kzalloc(sizeof( * t), GFP_KERNEL); 
   ...
   tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL);
 **关键点3 **
  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;
 **关键点4** 将binder_work 插入到目标队列
  t->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;
  • }

关键点1,找到目标进程,关键点2 创建binder_transaction与binder_work,关键点3 处理Binder实体与Handle转化,关键点4,将binder_work插入目标队列,并唤醒相应的等待队列,在处理Binder实体与Handle转化的时候,有下面几点注意的:


  • 第一次注册Binder实体的时候,是向别的进程注册的,ServiceManager,或者SystemServer中的AMS服务
  • Client请求服务的时候,一定是由Binder驱动为Client分配binder_ref,如果本进程的线程请求,fp->type = BINDER_TYPE_BINDER,否则就是fp->type = BINDER_TYPE_HANDLE。
  • Android中的Parcel里面的对象一定是flat_binder_object


如此下来,写数据的流程所经历的数据结构就完了。再简单看一下被唤醒一方的读取流程,读取从阻塞在内核态的binder_thread_read开始,以传递而来的BC_TRANSACTION为例,binder_thread_read会根据一些场景添加BRXXX参数,标识驱动传给用户空间的数据流向:

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),
};

之后,read线程根据binder_transaction新建binder_transaction_data对象,再通过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;
        }
    // 数据大小
        tr.data_size = t->buffer->data_size;
        tr.offsets_size = t->buffer->offsets_size;
    // 偏移地址要加上
        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 *));
    // 写命令
        if (put_user(cmd, (uint32_t __user *)ptr))
            return -EFAULT;
        // 写数据结构体到用户空间,
        ptr += sizeof(uint32_t);
        if (copy_to_user(ptr, &tr, sizeof(tr)))
            return -EFAULT;
        ptr += sizeof(tr);
}

上层通过ioctrl等待的函数被唤醒,假设现在被唤醒的是服务端,一般会执行请求,这里首先通过Parcel的ipcSetDataReference函数将数据将数据映射到Parcel对象中,之后再通过BBinder的transact函数处理具体需求;

status_t IPCThreadState::executeCommand(int32_t cmd)
{
    ...
    // read到了数据请求,这里是需要处理的逻辑 ,处理完毕,
    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 (tr.target.ptr) {
        // 这里什么是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);
    }   

这里的 b->transact(tr.code, buffer, &reply, tr.flags);就同一开始Client调用transact( mHandle, code, data, reply, flags)函数对应的处理类似,进入相对应的业务逻辑。


image.png


Binder驱动传递数据的释放(释放时机)


在Binder通信的过程中,数据是从发起通信进程的用户空间直接写到目标进程内核空间,而这部分数据是直接映射到用户空间,必须等用户空间使用完数据才能释放,也就是说Binder通信中内核数据的释放时机应该是用户空间控制的,内种中释放内存空间的函数是binder_free_buf,其他的数据结构其实可以直接释放掉,执行这个函数的命令是BC_FREE_BUFFER。上层用户空间常用的入口是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);
}

那什么时候会调用这个函数呢?在之前分析数据传递的时候,有一步是将binder_transaction_data中的数据映射到Parcel中去,其实这里是关键

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    int32_t cmd;
    int32_t err;
    while (1) {
    ...
        case BR_REPLY:
            {
            binder_transaction_data tr;
            // 注意这里是没有传输数据拷贝的,只有一个指针跟数据结构的拷贝,
            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,先设置数据,直接
            if (reply) {
                if ((tr.flags & TF_STATUS_CODE) == 0) {
                    // 牵扯到数据利用,与内存释放
                    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);

Parcel 的ipcSetDataReference函数不仅仅能讲数据映射到Parcel对象,同时还能将数据的清理函数映射进来

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

看函数定义中的release_func relFunc参数,这里就是指定内存释放函数,这里指定了IPCThreadState::freeBuffer函数,在Native层,Parcel在使用完,并走完自己的生命周期后,就会调用自己的析构函数,在其析构函数中调用了freeDataNoInit(),这个函数会间接调用上面设置的内存释放函数:

Parcel::~Parcel()
{
    freeDataNoInit();
}

这就是数据释放的入口,进入内核空间后,执行binder_free_buf,将这次分配的内存释放,同时更新binder_proc的binder_buffer表,重新标记那些内存块被使用了,哪些没被使用。

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);
}

Java层类似,通过JNI调用Parcel的freeData()函数释放内存,在用户空间,每次执行BR_TRANSACTION或者BR_REPLY,都会利用freeBuffer发送请求,去释放内核中的内存


简单的Binder通信C/S模型


image.png

目录
相关文章
|
5天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
19 2
|
25天前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
|
28天前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
48 1
|
1月前
|
监控 架构师 Java
从蚂蚁金服面试题窥探STW机制
在Java虚拟机(JVM)中,垃圾回收(GC)是一个至关重要的机制,它负责自动管理内存的分配和释放。然而,垃圾回收过程并非没有代价,其中最为显著的一个影响就是STW(Stop-The-World)机制。STW机制是指在垃圾回收过程中,JVM会暂停所有应用线程的执行,以确保垃圾回收器能够正确地遍历和回收对象。这一机制虽然保证了垃圾回收的安全性和准确性,但也可能对应用程序的性能产生显著影响。
29 2
|
1月前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
2月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
67 8
|
3月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android 消息处理机制估计都被写烂了,但是依然还是要写一下,因为Android应用程序是通过消息来驱动的,Android某种意义上也可以说成是一个以消息驱动的系统,UI、事件、生命周期都和消息处理机制息息相关,并且消息处理机制在整个Android知识体系中也是尤其重要,在太多的源码分析的文章讲得比较繁琐,很多人对整个消息处理机制依然是懵懵懂懂,这篇文章通过一些问答的模式结合Android主线程(UI线程)的工作原理来讲解,源码注释很全,还有结合流程图,如果你对Android 消息处理机制还不是很理解,我相信只要你静下心来耐心的看,肯定会有不少的收获的。
203 3
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
|
3月前
|
Java
【Java基础面试四十七】、 说一说你对Java反射机制的理解
这篇文章介绍了Java反射机制,它允许程序在运行时获取对象和类的真实信息,进行类和实例的创建,以及访问和修改成员变量和方法。
|
4月前
|
安全 Java 开发者
Java面试题:什么是Java 15中的密封类以及其限制?还有哪些其他特性用于限制类继承的机制吗?
Java面试题:什么是Java 15中的密封类以及其限制?还有哪些其他特性用于限制类继承的机制吗?
49 1