Android 开发高级面试题集合—— Binder 篇 <二>

Posted 清风Coolbreeze

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 开发高级面试题集合—— Binder 篇 <二>相关的知识,希望对你有一定的参考价值。

上一篇,我们讲了字节跳动和腾讯的高级面试题,这一篇,我们来细究阿里和美团的二面真题。

  • (阿里巴巴)Binder一次拷贝原理(直接拷贝到目标线程的内核空间,内核空间与用户空间对应)
  • (美团到家)Binder传输数据的大小限制(内核4M 上层限制1m-8k),传输Bitmap过大,就会崩溃的原因,Activity之间传输BitMap

Alibaba配图

面试题三:Binder一次拷贝原理(阿里巴巴)

android选择Binder作为主要进程通信的方式同其性能高也有关系,Binder只需要一次拷贝就能将A进程用户空间的数据为B进程所用。这里主要涉及两个点:

  • Binder的map函数,会将内核空间直接与用户空间对应,用户空间可以直接访问内核空间的数据

  • A进程的数据会被直接拷贝到B进程的内核空间(一次拷贝)

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

mmap函数属于系统调用,mmap会从当前进程中获取用户态可用的虚拟地址空间(vm_area_struct vma),并在mmap_region中真正获取vma,然后调用file->f_op->mmap(file, vma),进入驱动处理,之后就会在内存中分配一块连续的虚拟地址空间,并预先分配好页表、已使用的与未使用的标识、初始地址、与用户空间的偏移等等,通过这一步之后,就能把Binder在内核空间的数据直接通过指针地址映射到用户空间,供进程在用户空间使用,这是一次拷贝的基础,一次拷贝在内核中的标识如下:

    struct binder_proc {
    struct hlist_node proc_node;
    // 四棵比较重要的树 
    struct rb_root threads;
    struct rb_root nodes;
    struct rb_root refs_by_desc;
    struct rb_root refs_by_node;
    int pid;
    struct vm_area_struct *vma; //虚拟地址空间,用户控件传过来
    struct mm_struct *vma_vm_mm;
    struct task_struct *tsk;
    struct files_struct *files;
    struct hlist_node deferred_work_node;
    int deferred_work;
    void *buffer; //初始地址
    ptrdiff_t user_buffer_offset; //这里是偏移
    
    struct list_head buffers;//这个列表连接所有的内存块,以地址的大小为顺序,各内存块首尾相连
    struct rb_root free_buffers;//连接所有的已建立映射的虚拟内存块,以内存的大小为index组织在以该节点为根的红黑树下
    struct rb_root allocated_buffers;//连接所有已经分配的虚拟内存块,以内存块的开始地址为index组织在以该节点为根的红黑树下
    
    }

上面只是在APP启动的时候开启的地址映射,但并未涉及到数据的拷贝,下面看数据的拷贝操作。

当数据从用户空间拷贝到内核空间的时候,是直从当前进程的用户空间接拷贝到目标进程的内核空间,这个过程是在请求端线程中处理的,操作对象是目标进程的内核空间。看如下代码:

static void binder_transaction(struct binder_proc *proc,
                   struct binder_thread *thread,
                   struct binder_transaction_data *tr, int reply){
                   ...
        在通过进行binder事物的传递时,如果一个binder事物(用struct binder_transaction结构体表示)需要使用到内存,
        就会调用binder_alloc_buf函数分配此次binder事物需要的内存空间。
        需要注意的是:这里是从目标进程的binder内存空间分配所需的内存
        //从target进程的binder内存空间分配所需的内存大小,这也是一次拷贝,完成通信的关键,直接拷贝到目标进程的内核空间
        //由于用户空间跟内核空间仅仅存在一个偏移地址,所以也算拷贝到用户空间
        t->buffer = binder_alloc_buf(target_proc, tr->data_size,
            tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));
        t->buffer->allow_user_free = 0;
        t->buffer->debug_id = t->debug_id;
        //该binder_buffer对应的事务    
        t->buffer->transaction = t;
        //该事物对应的目标binder实体 ,因为目标进程中可能不仅仅有一个Binder实体
        t->buffer->target_node = target_node;
        trace_binder_transaction_alloc_buf(t->buffer);
        if (target_node)
            binder_inc_node(target_node, 1, 0, NULL);
        // 计算出存放flat_binder_object结构体偏移数组的起始地址,4字节对齐。
        offp = (size_t *)(t->buffer->data + ALIGN(tr->data_size, sizeof(void *)));
           // struct flat_binder_object是binder在进程之间传输的表示方式 //
           // 这里就是完成binder通讯单边时候在用户进程同内核buffer之间的一次拷贝动作 //
          // 这里的数据拷贝,其实是拷贝到目标进程中去,因为t本身就是在目标进程的内核空间中分配的,
        if (copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)) {
            binder_user_error("binder: %d:%d got transaction with invalid "
                "data ptr\\n", proc->pid, thread->pid);
            return_error = BR_FAILED_REPLY;
            goto err_copy_data_failed;
        }

可以看到binder_alloc_buf(target_proc, tr->data_size,tr->offsets_size, !reply && (t->flags & TF_ONE_WAY))函数在申请内存的时候,是从target_proc进程空间中去申请的,这样在做数据拷贝的时候copy_from_user(t->buffer->data, tr->data.ptr.buffer, tr->data_size)),就会直接拷贝target_proc的内核空间,而由于Binder内核空间的数据能直接映射到用户空间,这里就不在需要拷贝到用户空间。

这就是一次拷贝的原理。内核空间的数据映射到用户空间其实就是添加一个偏移地址,并且将数据的首地址、数据的大小都复制到一个用户空间的Parcel结构体,具体可以参考Parcel.cpp的Parcel::ipcSetDataReference函数。

面试题四:Binder传输数据的大小限制(内核4M 上层限制1m-8k),传输Bitmap过大,就会崩溃的原因,Activity之间传输BitMap(美团到家)

虽然APP开发时候,Binder对程序员几乎不可见,但是作为Android的数据运输系统,Binder的影响是全面性的,所以有时候如果不了解Binder的一些限制,在出现问题的时候往往是没有任何头绪,比如在Activity之间传输BitMap的时候,如果Bitmap过大,就会引起问题,比如崩溃等,这其实就跟Binder传输数据大小的限制有关系,在上面的一次拷贝中分析过,mmap函数会为Binder数据传递映射一块连续的虚拟地址,这块虚拟内存空间其实是有大小限制的,不同的进程可能还不一样。

普通的由Zygote孵化而来的用户进程,所映射的Binder内存大小是不到1M的,准确说是 110241024) - (4096 *2) :这个限制定义在ProcessState类中,如果传输说句超过这个大小,系统就会报错。

因为Binder本身就是为了进程间频繁而灵活的通信所设计的,并不是为了拷贝大数据而使用的:

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

而在内核中,其实也有个限制,是4M,不过由于APP中已经限制了不到1M,这里的限制似乎也没多大用途:

static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
    int ret;
    struct vm_struct *area;
    struct binder_proc *proc = filp->private_data;
    const char *failure_string;
    struct binder_buffer *buffer;
    //限制不能超过4M
    if ((vma->vm_end - vma->vm_start) > SZ_4M)
        vma->vm_end = vma->vm_start + SZ_4M;
    ...
    }

有个特殊的进程ServiceManager进程,它为自己申请的Binder内核空间是128K,这个同ServiceManager的用途是分不开的,ServcieManager主要面向系统Service,只是简单的提供一些addServcie,getService的功能,不涉及多大的数据传输,因此不需要申请多大的内存:

int main(int argc, char **argv)
{
    struct binder_state *bs;
    void *svcmgr = BINDER_SERVICE_MANAGER;

        // 仅仅申请了128k
    bs = binder_open(128*1024);
 if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\\n", strerror(errno));
        return -1;
    }

    svcmgr_handle = svcmgr;
    binder_loop(bs, svcmgr_handler);
    return 0;
}   

总结

好了,这篇就写到这里。如果觉得我写的不错的朋友或者很期待下次更新的朋友可以点个赞+关注哦!

PS:关于我


本人是一个拥有6年开发经验的帅气Android攻城狮,目前是菊厂某组任职 Android 架构师,记得看完点赞,养成习惯,关注这个喜欢写干货的程序员。

另外耗时两年整理收集的Android一线大厂面试完整考点PDF出炉,【完整版】已更新在我的【Github】,如有面试、进阶需要的朋友们可以去参考参考,如果对你有帮助,可以点个Star哦!

地址:【https://github.com/733gh/xiongfan】

0人点赞

面试

以上是关于Android 开发高级面试题集合—— Binder 篇 <二>的主要内容,如果未能解决你的问题,请参考以下文章

面试专题2021年字节阿里网易等 Handler 面试题集合,Android高级开发必备!

Android 开发高级面试题集合——Binder篇

最新Android高级面试之Java集合

面试专题2021年大厂 Handler 面试题集合

干货2021最新《Android高级开发面试题2.0》中高级程序员必备“面试宝典”

Android高级开发面试题以及笞案整理