Binder是Android使用的进程间通信工具。Android本身是一个复杂的操作系统,运行着众多的应用和服务,它们之间的通信就显得尤为重要。应用程序在使用Binder进行进程间通信时,实现方法非常简单,只需要编写AIDL文件,系统在编译时就会生成IPC通信代码。这种简单的方式也使得应用开发者可以不用了解Binder底层的传输机制。但对于系统开发人员,熟知Binder的原理是必要的,在分析解决问题时多数情况都会和Binder纠缠在一起。本文讲解使用Binder的第一步,打开和初始化一个Binder设备。

打开Binder设备

Binder驱动在初始化时注册为一个字符设备“/dev/binder”,并实现了poll、ioctl、mmap等接口。应用层对binder的控制都是通过ioctl完成的,而IPC通信的内容就是通过ioctl的命令“BINDER_WRITE_READ”中传输的。

Android中大多数的Binder通信都是通过ProcessState来初始化Binder设备的。

frameworks/native/libs/binder/ProcessState.cpp#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))......ProcessState::ProcessState()    : mDriverFD(open_driver()) //打开binder设备    , mVMStart(MAP_FAILED)    , mManagesContexts(false)    , mBinderContextCheckFunc(NULL)    , mBinderContextUserData(NULL)    , mThreadPoolStarted(false)    , mThreadPoolSeq(1){    if (mDriverFD >= 0) {        // XXX Ideally, there should be a specific define for whether we        // have mmap (or whether we could possibly have the kernel module        // availabla).#if !defined(HAVE_WIN32_IPC)        // mmap the binder, providing a chunk of virtual address space to receive transactions.        // 映射binder地址空间        mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);         if (mVMStart == MAP_FAILED) {            // *sigh*            ALOGE("Using /dev/binder failed: unable to mmap transaction memory.\n");            close(mDriverFD);            mDriverFD = -1;         }#else        mDriverFD = -1; #endif    }       LOG_ALWAYS_FATAL_IF(mDriverFD < 0, "Binder driver could not be opened.  Terminating.");}

可以看到,ProcessState构造过程中先打开Binder设备获得设备描述符,然后通过mmap映射了(1M - 8K)大小的地址空间用于数据传输。先看一下open_driver()。

frameworks/native/libs/binder/ProcessState.cppstatic int open_driver()                                                                                                                                                                                    {    int fd = open("/dev/binder", O_RDWR);    if (fd >= 0) {        fcntl(fd, F_SETFD, FD_CLOEXEC);        int vers = 0;        status_t result = ioctl(fd, BINDER_VERSION, &vers);        if (result == -1) {            ALOGE("Binder ioctl to obtain version failed: %s", strerror(errno));            close(fd);            fd = -1;        }        if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {            ALOGE("Binder driver protocol does not match user space protocol!");            close(fd);            fd = -1;        }        size_t maxThreads = 15;        result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);        if (result == -1) {            ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));        }    } else {        ALOGW("Opening '/dev/binder' failed: %s\n", strerror(errno));    }    return fd;}

这里打开设备“/dev/binder”,然后验证版本信息。之后通过BINDER_SET_MAX_THREADS设置一个进程中最大的Binder线程数,在驱动中设置给proc->max_threads。代码中设置的值为15,但驱动中的计数是从0开始的,所以实际上Binder最大线程数是16。接下来看下驱动的open函数,比较简单,就是创建Binder进程结构体并初始化。

drivers/staging/android/binder.cstatic int binder_open(struct inode *nodp, struct file *filp){    struct binder_proc *proc; // Binder进程结构体    ......    proc = kzalloc(sizeof(*proc), GFP_KERNEL);    if (proc == NULL)        return -ENOMEM;    get_task_struct(current);    proc->tsk = current;    INIT_LIST_HEAD(&proc->todo);    init_waitqueue_head(&proc->wait);    proc->default_priority = task_nice(current); // 缺省优先级    binder_lock(__func__);    binder_stats_created(BINDER_STAT_PROC); // 记录binder状态    hlist_add_head(&proc->proc_node, &binder_procs);    proc->pid = current->group_leader->pid;    INIT_LIST_HEAD(&proc->delivered_death);    filp->private_data = proc; // 设备私有数据设置为binder进程结构体    binder_unlock(__func__);    ......}

Bind_proc用于管理Binder进程,一个进程只会有一个binder_proc,记录了Binder传输的所有信息。下面看一下binder_proc的定义。

drivers/staging/android/binder.cstruct binder_proc {    struct hlist_node proc_node;  // 该binder进程的节点, 挂载到binder_procs链表中    struct rb_root threads; // binder线程ID红黑树    struct rb_root nodes; // binder实体对象红黑树    struct rb_root refs_by_desc; // binder引用对象红黑树,以handle为key    struct rb_root refs_by_node; // binder引用对象红黑树,以node为key    int pid; // 进程ID    struct vm_area_struct *vma;  // 该进程的虚拟地址空间指针    struct mm_struct *vma_vm_mm; // 该进程的内存结构体    struct task_struct *tsk; // 该进程的task结构体    struct files_struct *files; // 该进程的file结构体    struct hlist_node deferred_work_node; // 延迟工作队列节点,挂载到binder_deferred_list链表中    int deferred_work; // 延迟工作的类型    void *buffer; // 映射的内核空间地址    ptrdiff_t user_buffer_offset; // 映射的内核空间与用户空间的偏移    struct list_head buffers; // 全部buffer链表    struct rb_root free_buffers; // 空闲buffer红黑树    struct rb_root allocated_buffers; // 已分配的buffer红黑树    size_t free_async_space; // 剩余异步传输空间大小    struct page **pages; // 物理内存页    size_t buffer_size; // 映射的buffer大小    uint32_t buffer_free; // 剩余可用buffer大小    struct list_head todo; // 进程工作链表    wait_queue_head_t wait; // 等待队列    struct binder_stats stats; // binder统计信息    struct list_head delivered_death; // 已经发布的死亡通知链表    int max_threads; // 最大线程数    int requested_threads; // 请求的线程数    int requested_threads_started; // 请求的线程已经启动的数量    int ready_threads; // 已经准备好的线程数    long default_priority; // 默认优先级    struct dentry *debugfs_entry; // debugf入口指针};

内存空间映射

创建binder_proc,完成初始化后,应用层会调用mmap()来映射地址空间。

drivers/staging/android/binder.cstatic 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;    if (proc->tsk != current)        return -EINVAL;    // 映射空间不能大于4M    if ((vma->vm_end - vma->vm_start) > SZ_4M)        vma->vm_end = vma->vm_start + SZ_4M;    ......    // fork的子进程无法复制映射空间,并且不允许修改写属性    vma->vm_flags = (vma->vm_flags | VM_DONTCOPY) & ~VM_MAYWRITE;    ......    // 获取内核虚拟地址空间    area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);    ......    proc->buffer = area->addr;    proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;    mutex_unlock(&binder_mmap_lock);    ......    // 创建物理页结构体    proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);    ......    proc->buffer_size = vma->vm_end - vma->vm_start;    vma->vm_ops = &binder_vm_ops;    vma->vm_private_data = proc;    // 分配一个物理页,并映射到虚拟地址空间    if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {    ......    // 创建buffers链表,插入第一个free buffer    buffer = proc->buffer;    INIT_LIST_HEAD(&proc->buffers);    list_add(&buffer->entry, &proc->buffers);    buffer->free = 1;    binder_insert_free_buffer(proc, buffer);    // 异步传输可用空间大小设置为映射大小的一半    proc->free_async_space = proc->buffer_size / 2;    barrier();    proc->files = get_files_struct(current);    proc->vma = vma;    proc->vma_vm_mm = vma->vm_mm;    ......}

Binder_mmap()主要完成了内核虚拟地址空间分配,地址空间映射,binder_proc中内存相关参数的初始化等工作。真正的物理页分配时通过binder_update_page_range()进行的,这里只分配了一个物理页放入free_buffers树中。之后的Binder传输过程中会根据需要分配和回收buffer。

drivers/staging/android/binder.cstatic int binder_update_page_range(struct binder_proc *proc, int allocate,                    void *start, void *end,                    struct vm_area_struct *vma){    void *page_addr;    unsigned long user_page_addr;    struct vm_struct tmp_area;    struct page **page;    struct mm_struct *mm;    ......    // 只有mmap时vma不为NULL,其他情况都会根据proc来获取内存相关数据    if (vma)        mm = NULL;    else        // 获取内存描述符,并增加用户计数,防止mm_struct被释放        mm = get_task_mm(proc->tsk);    if (mm) {        down_write(&mm->mmap_sem);        vma = proc->vma;        if (vma && mm != proc->vma_vm_mm) {            pr_err("%d: vma mm and task mm mismatch\n",                proc->pid);            vma = NULL;        }    }    // 回收内存时allocate为0    if (allocate == 0)        goto free_range;    ......    // 循环分配物理页,每次分配一页    for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {        int ret;        struct page **page_array_ptr;        page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];        BUG_ON(*page);        // 分配一个物理页,保存地址到proc中        *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);        ......        tmp_area.addr = page_addr;        tmp_area.size = PAGE_SIZE + PAGE_SIZE /* guard page? */;        page_array_ptr = page;        // 建立页表与物理页的映射        ret = map_vm_area(&tmp_area, PAGE_KERNEL, &page_array_ptr);        ......        user_page_addr =            (uintptr_t)page_addr + proc->user_buffer_offset;        // 插入物理页到用户虚拟地址空间        ret = vm_insert_page(vma, user_page_addr, page[0]);        ......    }    if (mm) {        up_write(&mm->mmap_sem);        // 减少内存描述符的用户计数        mmput(mm);    }    return 0;free_range:    for (page_addr = end - PAGE_SIZE; page_addr >= start;         page_addr -= PAGE_SIZE) {        page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];        if (vma)            // 解除用户虚拟地址空间与物理页的映射            zap_page_range(vma, (uintptr_t)page_addr +                proc->user_buffer_offset, PAGE_SIZE, NULL);err_vm_insert_page_failed:        // 解除物理页与内核页表的映射        unmap_kernel_range((unsigned long)page_addr, PAGE_SIZE);err_map_kernel_failed:        // 释放物理页        __free_page(*page);        *page = NULL;err_alloc_page_failed:        ;    }........}

ProcessState构造函数执行完,也就完成了对Binder设备的初始化和内存空间映射。应用层获得了Binder设备的描述符mDriverFD,之后通过ioctl操作就可以进行IPC传输。

第一个Binder线程

之前分析到,一个Binder进程最多可以有16个Binder线程,每个Binder线程都可以独立进行Binder传输。第一个Binder线程一般在创建ProcessState对象后,通过ProcessState::startThreadPool()来创建。

frameworks/native/libs/binder/ProcessState.cppclass PoolThread : public Thread{public:    PoolThread(bool isMain)                                                                                                                                                                                         : mIsMain(isMain)    {    }protected:    virtual bool threadLoop()    {        // 通过startThreadPool创建时,mIsMain是true,为主线程        IPCThreadState::self()->joinThreadPool(mIsMain);        return false;    }    const bool mIsMain;};......void ProcessState::startThreadPool(){    AutoMutex _l(mLock);    // 第一个线程必须通过startThreadPool来创建    if (!mThreadPoolStarted) {        mThreadPoolStarted = true;        spawnPooledThread(true);    }   }......void ProcessState::spawnPooledThread(bool isMain)                                                                                                                                                           {    if (mThreadPoolStarted) {        // 构造binder名字:Binder_XX        String8 name = makeBinderThreadName();        ALOGV("Spawning new pooled thread, name=%s\n", name.string());        sp t = new PoolThread(isMain);        t->run(name.string());    }}

最终真正的Binder线程创建是通过IPCThreadState::joinThreadPool()来实现的。

frameworks/native/libs/binder/IPCThreadState.cppvoid IPCThreadState::joinThreadPool(bool isMain){    ......    // 主线程使用命令BC_ENTER_LOOPER,以后再创建线程都通过BC_REGISTER_LOOPER    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);    ......    // 设置线程为前台group,保证Binder传输初始化    set_sched_policy(mMyThreadId, SP_FOREGROUND);         status_t result;    do {         // 清理命令队列        processPendingDerefs();        // now get the next command to be processed, waiting if necessary        // 获取一个命令并执行        result = getAndExecuteCommand();        ......        // 非主线程时,如果线程不在需要将退出。主线程将一直存活        if(result == TIMED_OUT && !isMain) {            break;        }        } while (result != -ECONNREFUSED && result != -EBADF);    ......     // 线程退出命令       mOut.writeInt32(BC_EXIT_LOOPER);    talkWithDriver(false);}

joinThreadPool()发送命令BC_ENTER_LOOPER创建第一个Binder线程。之后进入循环,通过getAndExecuteCommand()获取命令并执行。简单看下是如何与驱动交互的。

frameworks/native/libs/binder/IPCThreadState.cppstatus_t IPCThreadState::getAndExecuteCommand(){    status_t result;    int32_t cmd;     // 与Binder驱动进行交互    result = talkWithDriver();    if (result >= NO_ERROR) {        size_t IN = mIn.dataAvail();        ......        // 执行命令        result = executeCommand(cmd);        ......        // 执行命令时可能将线程设置为后台group,再次设置为前台group        set_sched_policy(mMyThreadId, SP_FOREGROUND);    }     return result;}......status_t IPCThreadState::talkWithDriver(bool doReceive)                                                                                                                                                     {    ......    // 写数据为mOut    bwr.write_size = outAvail;    bwr.write_buffer = (uintptr_t)mOut.data();    // This is what we'll read.    // 读数据为mIn    if (doReceive && needRead) {        bwr.read_size = mIn.dataCapacity();        bwr.read_buffer = (uintptr_t)mIn.data();    } else {        bwr.read_size = 0;        bwr.read_buffer = 0;    }    ......       // 使用BINDER_WRITE_READ对驱动进行读写        if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)    ......} 

应用对Binder驱动的读写是通过ioctl的BINDER_WRITE_READ命令完成的,读写数据为mIn和mOut。mIn和mOut为Parcel实例,Binder的数据都需要经过Parcel将数据序列化后才传输的,应用接收的数据再经过反序列化读出。我们先关心线程的创建,看一下驱动中对BC_ENTER_LOOPER的处理。

drivers/staging/android/binder.cstatic int binder_thread_write(struct binder_proc *proc,            struct binder_thread *thread,            binder_uintptr_t binder_buffer, size_t size,            binder_size_t *consumed){    ......        case BC_ENTER_LOOPER:            ......            // 设置thread looper状态            thread->looper |= BINDER_LOOPER_STATE_ENTERED;            break;    ......}......static struct binder_thread *binder_get_thread(struct binder_proc *proc){    struct binder_thread *thread = NULL;    struct rb_node *parent = NULL;    struct rb_node **p = &proc->threads.rb_node;    // 在proc threads树上查找线程    while (*p) {        parent = *p;        thread = rb_entry(parent, struct binder_thread, rb_node);        if (current->pid < thread->pid)            p = &(*p)->rb_left;        else if (current->pid > thread->pid)            p = &(*p)->rb_right;        else            break;    }    // 如果threads树上没有找到线程则创建一个线程,并插入到树中    if (*p == NULL) {        thread = kzalloc(sizeof(*thread), GFP_KERNEL);        if (thread == NULL)            return NULL;        binder_stats_created(BINDER_STAT_THREAD);        thread->proc = proc;        thread->pid = current->pid;        init_waitqueue_head(&thread->wait);        INIT_LIST_HEAD(&thread->todo);        rb_link_node(&thread->rb_node, parent, p);        rb_insert_color(&thread->rb_node, &proc->threads);        thread->looper |= BINDER_LOOPER_STATE_NEED_RETURN;        thread->return_error = BR_OK;        thread->return_error2 = BR_OK;    }    return thread;}......static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){    ......    // 在binder进程中获取当前线程的binder线程    thread = binder_get_thread(proc);    ......    switch (cmd) {    case BINDER_WRITE_READ: {        if (bwr.write_size > 0) {            // 写数据            ret = binder_thread_write(proc, thread, bwr.write_buffer, bwr.write_size, &bwr.write_consumed);        ......        if (bwr.read_size > 0) {            // 读数据            ret = binder_thread_read(proc, thread, bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK);        ......        if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {            ret = -EFAULT;            goto err;        }        break;BC_ENTER_LOOPER    }    ......}

整理一下第一个Binder线程创建的流程,

  • 应用在创建ProcessState实例后调用startThreadPool()创建了一个线程Binder_1。
  • Binder_1运行时调用IPCThreadState::joinThreadPool()跟驱动交互,发送命令BC_ENTER_LOOPER。
    驱动在读写数据时先创建一个Binder线程,加入到proc->threads树上。这是该红黑树上应该只有一个节点,其pid为Binder_1的pid。
  • 驱动执行命令BC_ENTER_LOOPER,设置Binder线程looper状态为BINDER_LOOPER_STATE_ENTERED。
  • 驱动调用binder_thread_read()等待数据写入。

后续Binder线程的创建

进程中的第一个Binder线程创建后,再创建Binder线程有两种方式。

  • 直接调用IPCThreadState::joinThreadPool():一些Native Service使用这个方式,像MediaServer,DrmServer。
  • 通过ProcessState::spawnPooledThread(false):处理BR_SPAWN_LOOPER命令时执行。

大部分后续Binder线程的创建都是通过BR_SPAWN_LOOPER命令来完成的。这个命令是由Binder驱动发出的。当Binder驱动检查到Binder线程不足时,就会发送BR_SPAWN_LOOPER给应用层,用来创建Binder线程。

drivers/staging/android/binder.cstatic int binder_thread_read(struct binder_proc *proc,                  struct binder_thread *thread,                  binder_uintptr_t binder_buffer, size_t size,                  binder_size_t *consumed, int non_block){    ......    // 是否从proc中获取工作项    wait_for_proc_work = thread->transaction_stack == NULL &&                list_empty(&thread->todo);    ......    // 设置状态为Binder线程正在等待处理    thread->looper |= BINDER_LOOPER_STATE_WAITING;    // 如果处理proc工作,则让ready_threads加1    if (wait_for_proc_work)        proc->ready_threads++;    binder_unlock(__func__);    ......    // 线程进入休眠,等待数据到来    if (wait_for_proc_work) {        ......            ret = wait_event_freezable_exclusive(proc->wait, binder_has_proc_work(proc, thread));    } else {        ......            ret = wait_event_freezable(thread->wait, binder_has_thread_work(thread));    }    binder_lock(__func__);    // 如果处理proc工作,则让ready_threads减1,表面Binder线程开始处理数据    if (wait_for_proc_work)        proc->ready_threads--;    // 移除Binder线程等待处理状态    thread->looper &= ~BINDER_LOOPER_STATE_WAITING;    ......    // 如果没有剩余的可用Binder线程,并且没有达到最大线程数,则发送BR_SPAWN_LOOPER    if (proc->requested_threads + proc->ready_threads == 0 &&        proc->requested_threads_started < proc->max_threads &&        (thread->looper & (BINDER_LOOPER_STATE_REGISTERED |         BINDER_LOOPER_STATE_ENTERED)) /* the user-space code fails to */         /*spawn a new thread if we leave this out */) {        proc->requested_threads++;        binder_debug(BINDER_DEBUG_THREADS,                 "%d:%d BR_SPAWN_LOOPER\n",                 proc->pid, thread->pid);        if (put_user(BR_SPAWN_LOOPER, (uint32_t __user *)buffer))            return -EFAULT;        binder_stat_br(proc, thread, BR_SPAWN_LOOPER);    }    return 0;}

当进程中没有剩余可用的Binder线程时,驱动就会发送BR_SPAWN_LOOPER命令,应用层接收到该命令则执行spawnPooledThread(false)。这个函数在上面分析过,会向驱动发送BC_REGISTER_LOOPER命令。

drivers/staging/android/binder.cstatic int binder_thread_write(struct binder_proc *proc,            struct binder_thread *thread,            binder_uintptr_t binder_buffer, size_t size,            binder_size_t *consumed){    ......        case BC_REGISTER_LOOPER:            ......            } else {                // 更新proc中线程状态计数                proc->requested_threads--;                proc->requested_threads_started++;            }            // 设置thread looper状态            thread->looper |= BINDER_LOOPER_STATE_REGISTERED;            break;    ......}

可以看到,proc中线程相关的计数有三个,

  • ready_threads:这个计数会在处理proc工作项时更新,表明有多少Binder线程正在等待处理命令。
  • requested_threads:驱动申请创建Binder线程时将该计数+1,应用层完成线程创建后向驱动注册时发送BC_REGISTER_LOOPER会将计数-1。所以Binder线程创建流程结束后该计数应该为0。
  • requested_threads_started:表明已经启动了多少Binder线程。但是Binder主线程时通过BC_ENTER_LOOPER建立的,不更新该计数。所以最大的Binder线程数是(max_threads + 1)。

Binder线程创建流程

根据上面分析,可以知道Binder线程有两种创建方式,

  • Binder主线程:在Binder进程初始化时创建了一个线程,并发送BC_ENTER_LOOPER给Binder驱动,注册为主线程。主线程在一个Binder进程中只有一个,且不能销毁。
  • Binder副线程:在Binder进程没有空余Binder线程时,由Binder驱动发送BR_SPAWN_LOOPER创建。应用层创建一个线程通过BC_REGISTER_LOOPE进行注册。Binder辅线程可以有多个,在不需要时可以销毁。

上图是对Binder线程创建流程的简单整理。可以看到如果Binder主线程接收到消息就会再创建一个Binder线程,之后如果接收消息时没有空余线程才会创建。所以在一些Native Service中,会在初始化时就调用joinThreadPool()创建一个副线程,这样Service在开始运行时就会有两个Binder线程,因为它们很明确一定会使用跨进程通信。

更多相关文章

  1. GitHub 标星 2.5K+!教你通过玩游戏的方式学习 VIM!
  2. 如何在后台运行Linux命令?
  3. No.11 使用firewall配置的防火墙策略的生效模式
  4. 一款霸榜 GitHub 的开源 Linux 资源监视器!
  5. Android常见面试题&字节跳动、阿里、腾讯2019实习生Android岗部
  6. Android的服务(Service)(一)生命周期
  7. Android(安卓)Phone进程启动过程详解
  8. 基于JSON RPC的一种Android跨进程调用解决方案了解一下?
  9. Android(安卓)SurfaceView入门学习

随机推荐

  1. android中的布局简要介绍
  2. android 存储数据与文件
  3. Android短信编解码方式
  4. Android(安卓)Studio witing for debugge
  5. android 与c#服务端DES加密不一致问题解
  6. Activity的布局
  7. 2013.03.19(2)———android ActivityGroup
  8. Android(安卓)- 视频提取图片方法
  9. Android程序的退出活动
  10. 【Android】获取图片和视频缩略图