对冗余挑拣重点,对重点深入补充,输出结构清晰的精简版

  1. 深入 binder 驱动内部
    binder_ioctl
    binder_get_thread
    binder_ioctl_write_read
    binder_thread_write
    binder_transaction
    binder_thread_read
    小结
  2. binder Q&A
    如何找到目标进程 Binder 实体
    如何实现 Binder 线程的睡眠与唤醒
  3. 最后

深入 binder 驱动内部

前两篇文章都有提到 binder_ioctl 方法,在 Binder 机制 [上] 中介绍了 binder_ioctl 支持的命令;Binder 机制 [中] 中提到 IPCThreadState 会调用到 binder_ioctl 方法。

书中对 binder 驱动内部调用的讲解没有分为较清晰的步骤,一口气就是 20 页篇幅的源码详解,理解起来有些难度,容易迷失。在细读了三四遍后,终于感觉对整体有些掌握了,结合前面的学习与自己的理解,将一次 IPC 调用中 binder 驱动的工作分为以下 5 步:

1.准备数据,根据命令分发给具体的方法去处理
2.找到目标进程的相关信息
3.将数据一次拷贝到目标进程所映射的物理内存块
4.记录待处理的任务,唤醒目标线程
5.调用线程进入休眠
6.目标进程直接拿到数据进行处理,处理完后唤醒调用线程
7.调用线程返回处理结果

与上篇文章一样仍以 getService() 为例,按照上面的工作步骤为脉络,深入分析驱动层中的执行逻辑,彻底搞定 binder 驱动!

binder_ioctl

在 IPCThreadState 中这样调用了 binder_ioctl() 方法:

    ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr)

binder_ioctl() 方法中会根据 BINDER_WRITE_READ、BINDER_SET_MAX_THREADS 等不同 cmd 转调到不同的方法去执行,这里我们只关注 BINDER_WRITE_READ,简化后代码如下:

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){    int ret;    //拿到调用进程在 binder_open() 中记录的 binder_proc    struct binder_proc *proc = filp->private_data;    struct binder_thread *thread;    binder_lock(__func__);    //获取调用线程 binder_thread    thread = binder_get_thread(proc);    switch (cmd) {    case BINDER_WRITE_READ:        //处理 binder 数据读写,binder IPC 通信的核心逻辑    ret = binder_ioctl_write_read(filp, cmd, arg, thread);    if (ret)    goto err;    break;    case BINDER_SET_MAX_THREADS:{...} //设置 binder 最大线程数    case BINDER_SET_CONTEXT_MGR:{...} //设置 service 大管家,即 ServiceManager    case BINDER_THREAD_EXIT:{...} //binder 线程退出命令,释放相关资源    case BINDER_VERSION: {...} //获取 binder 驱动版本号    ...}

在 Binder 机制 [上] 中详细介绍过 binder_open() 方法,它主要做了两个工作:1.创建及初始化每个进程独有一份的、用来存放 binder 相关数据的 binder_proc 结构体,2.将 binder_proc 记录起来,方便后续使用。正是通过 file 来记录的:

static int binder_open(struct inode *nodp, struct file *filp){    ...    filp->private_data = proc;    ...}

拿到调用进程后,进一步通过 binder_get_thread() 方法拿到调用线程,然后就交给 binder_ioctl_write_read() 方法去执行具体的 binder 数据读写了,可见 binder_ioctl() 方法本身的逻辑非常简单,将数据 arg 透传了出去。下面分别来看 binder_get_thread()、binder_ioctl_write_read() 这两个方法。

binder_get_thread

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 中获取红黑树根节点    //查找 pid 等于当前线程 id 的thread,该红黑树以 pid 大小为序存放    while (*p) {        parent = *p;        thread = rb_entry(parent, struct binder_thread, rb_node);        if (current->pid < thread->pid) //current->pid 是当前调用线程的 id            p = &(*p)->rb_left;        else if (current->pid > thread->pid)            p = &(*p)->rb_right;        else            break;    }    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);  //加入到 proc 的 threads 红黑树中        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;}

binder_thread 是用来描述线程的结构体,binder_get_thread() 方法中逻辑也很简单,首先从调用进程 proc 中查找当前线程是否已被记录,如果找到就直接返回,否则新建一个返回,并记录到 proc 中。也就是说所有调用 binder_ioctl() 的线程,都会被记录起来。

binder_ioctl_write_read

此方法分为两部分来看,首先是整体:

static int binder_ioctl_write_read(struct file *filp,unsigned int cmd, unsigned long arg,struct binder_thread *thread){    int ret = 0;    struct binder_proc *proc = filp->private_data;    unsigned int size = _IOC_SIZE(cmd);    void __user *ubuf = (void __user *)arg; //用户传下来的数据赋值给 ubuf    struct binder_write_read bwr;    //把用户空间数据 ubuf 拷贝到 bwr    if (copy_from_user(&bwr, ubuf, sizeof(bwr))) {    ret = -EFAULT;    goto out;    }    处理数据...    //将读写后的数据写回给用户空间    if (copy_to_user(ubuf, &bwr, sizeof(bwr))) {    ret = -EFAULT;    goto out;    }out:return ret;}

起初看到 copy_from_user() 方法时难以理解,因为它看起来是将我们要传输的数据拷贝到内核空间了,但目前还没有看到 server 端的任何线索,bwr 跟 server 端没有映射关系,那后续再将 bwr 传输给 server 端的时候又要拷贝,这样岂不是多次拷贝了?

其实这里的 copy_from_user() 方法并没有拷贝要传输的数据,而仅是拷贝了持有传输数据内存地址的 bwr。后续处理数据时会根据 bwr 信息真正的去拷贝要传输的数据。

处理完数据后,会将处理结果体现在 bwr 中,然后返回给用户空间处理。那是如何处理数据的呢?所谓的处理数据,就是对数据的读写而已:

    if (bwr.write_size > 0) {//写数据    ret = binder_thread_write(proc,              thread,             bwr.write_buffer, bwr.write_size,             &bwr.write_consumed);        trace_binder_write_done(ret);        if (ret < 0) { //写失败            bwr.read_consumed = 0;            if (copy_to_user(ubuf, &bwr, sizeof(bwr)))                ret = -EFAULT;            goto out;        }    }    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);        trace_binder_read_done(ret);        if (!list_empty(&proc->todo))            wake_up_interruptible(&proc->wait);//唤醒等待状态的线程        if (ret < 0) { //读失败            if (copy_to_user(ubuf, &bwr, sizeof(bwr)))                ret = -EFAULT;            goto out;        }    }

可见 binder 驱动内部依赖用户空间的 binder_write_read 决定是要读取还是写入数据:其内部变量 read_size>0 则代表要读取数据,write_size>0 代表要写入数据,若都大于 0 则先写入,后读取。

至此焦点应该集中在 binder_thread_write() 和 binder_thread_read(),下面分析这两个方法。

binder_thread_write

在上面的 binder_ioctl_write_read() 方法中调用 binder_thread_write() 时传入了 bwr.write_buffer、bwr.write_size 等,先搞清楚这些参数是什么。

最开始是在用户空间 IPCThreadState 的 transact() 中通过 writeTransactionData() 方法创建数据并写入 mOut 的,writeTransactionData 方法代码如下:

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.ptr = 0;    tr.target.handle = handle; //目标 server 的 binder 的句柄    tr.code = code; //请求码,getService() 服务对应的是 GET_SERVICE_TRANSACTION    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(binder_size_t);        tr.data.ptr.offsets = data.ipcObjects();    } else {...}    mOut.writeInt32(cmd); // transact 传入的 cmd 是 BC_TRANSACTION    mOut.write(&tr, sizeof(tr)); //打包成 binder_transaction_data    return NO_ERROR;}

然后在 IPCThreadState 的 talkWithDriver() 方法中对 write_buffer 赋值:

    bwr.write_buffer = (uintptr_t)mOut.data();

搞清楚了数据的来源,再来看 binder_thread_write() 方法,binder_thread_write() 方法中处理了大量的 BC_XXX 命令,代码很长,这里我们只关注当前正在处理的 BC_TRANSACTION 命令,简化后代码如下:

static int binder_thread_write(struct binder_proc *proc,        struct binder_thread *thread,        binder_uintptr_t binder_buffer, size_t size,        binder_size_t *consumed){    uint32_t cmd;    void __user *buffer = (void __user *)(uintptr_t)binder_buffer; //就是 bwr.write_buffer    void __user *ptr = buffer + *consumed; //数据起始地址    void __user *end = buffer + size; //数据结束地址    while (ptr < end && thread->return_error == BR_OK) { //可能有多个命令及对应数据要处理,所以要循环        if (get_user(cmd, (uint32_t __user *)ptr)) // 读取一个 cmd            return -EFAULT;        ptr += sizeof(uint32_t); //跳过 cmd 所占的空间,指向要处理的数据        switch (cmd) {            case BC_TRANSACTION:            case BC_REPLY: {                 struct binder_transaction_data tr; //与 writeTransactionData 中准备的数据结构体对应                 if (copy_from_user(&tr, ptr, sizeof(tr))) //拷贝到内核空间 tr 中                    return -EFAULT;                 ptr += sizeof(tr); //跳过数据所占空间,指向下一个 cmd                 binder_transaction(proc, thread, &tr, cmd == BC_REPLY); //处理数据                 break;            }            处理其他 BC_XX 命令...        }    *consumed = ptr - buffer; //被写入处理消耗的数据量,对应于用户空间的 bwr.write_consumed

binder_thread_write() 中从 bwr.write_buffer 中取出了 cmd 和 cmd 对应的数据,进一步交给 binder_transaction() 处理,需要注意的是,BC_TRANSACTION、BC_REPLY 这两个命令都是由 binder_transaction() 处理的。

简单梳理一下,由 binder_ioctl -> binder_ioctl_write_read -> binder_thread_write ,到目前为止还只是在准备数据,没有看到跟目标进程相关的任何处理,都属于 “准备数据,根据命令分发给具体的方法去处理” 第 1 个工作。而到此为止,第 1 个工作便结束,下一步的 binder_transaction() 方法终于要开始后面的工作了。

binder_transaction

binder_transaction() 方法中代码较长,先总结它干了哪些事:对应开头列出的工作,此方法中做了非常关键的 2-4 步:

  • 找到目标进程的相关信息
  • 将数据一次拷贝到目标进程所映射的物理内存块
  • 记录待处理的任务,唤醒目标线程

以这些工作为线索,将代码分为对应的部分来看,首先是找到目标进程的相关信息,简化后代码如下:

static void binder_transaction(struct binder_proc *proc,       struct binder_thread *thread,       struct binder_transaction_data *tr, int reply){    struct binder_transaction *t; //用于描述本次 server 端要进行的 transaction    struct binder_work *tcomplete; //用于描述当前调用线程未完成的 transaction    binder_size_t *offp, *off_end;    struct binder_proc *target_proc; //目标进程    struct binder_thread *target_thread = NULL; //目标线程    struct binder_node *target_node = NULL; //目标 binder 节点    struct list_head *target_list; //目标 TODO 队列    wait_queue_head_t *target_wait; //目标等待队列    if(reply){         in_reply_to = thread->transaction_stack;        ...处理 BC_REPLY,暂不关注    }else{         //处理 BC_TRANSACTION        if (tr->target.handle) { //handle 不为 0            struct binder_ref *ref;            //根据 handle 找到目标 binder 实体节点的引用            ref = binder_get_ref(proc, tr->target.handle);            target_node = ref->node; //拿到目标 binder 节点        } else {             // handle 为 0 则代表目标 binder 是 service manager            // 对于本次调用来说目标就是 service manager            target_node = binder_context_mgr_node;        }    }    target_proc = target_node->proc; //拿到目标进程    if (!(tr->flags & TF_ONE_WAY) && thread->transaction_stack) {    struct binder_transaction *tmp;    tmp = thread->transaction_stack;    while (tmp) {            if (tmp->from && tmp->from->proc == target_proc)                target_thread = tmp->from; //拿到目标线程            tmp = tmp->from_parent;    }    }    target_list = &target_thread->todo; //拿到目标 TODO 队列    target_wait = &target_thread->wait; //拿到目标等待队列

binder_transaction、binder_work 等结构体在上一篇中有介绍,上面代码中也详细注释了它们的含义。比较关键的是 binder_get_ref() 方法,它是如何找到目标 binder 的呢?这里暂不延伸,下文再做分析。

继续看 binder_transaction() 方法的第 2 个工作,将数据一次拷贝到目标进程所映射的物理内存块

    t = kzalloc(sizeof(*t), GFP_KERNEL); //创建用于描述本次 server 端要进行的 transaction    tcomplete = kzalloc(sizeof(*tcomplete), GFP_KERNEL); //创建用于描述当前调用线程未完成的 transaction    if (!reply && !(tr->flags & TF_ONE_WAY)) //将信息记录到 t 中:        t->from = thread; //记录调用线程    else        t->from = NULL;    t->sender_euid = task_euid(proc->tsk);    t->to_proc = target_proc; //记录目标进程    t->to_thread = target_thread; //记录目标线程    t->code = tr->code; //记录请求码,getService() 对应的是 GET_SERVICE_TRANSACTION    t->flags = tr->flags;    //实际申请目标进程所映射的物理内存,准备接收要传输的数据    t->buffer = binder_alloc_buf(target_proc, tr->data_size,        tr->offsets_size, !reply && (t->flags & TF_ONE_WAY));    //申请到 t->buffer 后,从用户空间将数据拷贝进来,这里就是一次拷贝数据的地方!!    if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)        tr->data.ptr.buffer, tr->data_size)) {        return_error = BR_FAILED_REPLY;        goto err_copy_data_failed;    }

为什么在拷贝之前要先申请物理内存呢?在 Binder 机制 [上] 中介绍 binder_mmap() 时详细分析过,虽然 binder_mmap() 直接映射了 (1M-8K) 的虚拟内存,但却只申请了 1 页的物理页面,等到实际使用时再动态申请。也就是说,在 binder_ioctl() 实际传输数据的时候,再通过 binder_alloc_buf() 方法去申请物理内存。

至此已经将要传输的数据拷贝到目标进程,目标进程可以直接读取到了,接下来要做的就是将目标进程要处理的任务记录起来,然后唤醒目标进程,这样在目标进程被唤醒后,才能知道要处理什么任务。

最后来看 binder_transaction() 方法的第 3 个工作,记录待处理的任务,唤醒目标线程

if (reply) { //如果是处理 BC_REPLY,pop 出来栈顶记录的 transaction(实际上是删除链表头元素)binder_pop_transaction(target_thread, in_reply_to);} else if (!(t->flags & TF_ONE_WAY)) {        //如果不是 oneway,将 server 端要处理的 transaction 记录到当前调用线程t->need_reply = 1;t->from_parent = thread->transaction_stack;thread->transaction_stack = t;} else {...暂不关注 oneway 的情况}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); //唤醒目标线程

再次梳理一下,至此已经完成了前四个工作:

1.准备数据,根据命令分发给具体的方法去处理
2.找到目标进程的相关信息
3.将数据一次拷贝到目标进程所映射的物理内存块
4.记录待处理的任务,唤醒目标线程

其中第 1 个工作涉及到的方法为 binder_ioctl() -> binder_get_thread() -> binder_ioctl_write_read() -> binder_thread_write() ,主要是一些数据的准备和方法转跳,没做什么实质的事情。而 binder_transaction() 方法中做了非常重要的 2-4 工作。

剩下的工作还有:

5.调用线程进入休眠
6.目标进程直接拿到数据进行处理,处理完后唤醒调用线程
7.调用线程返回处理结果

可以想到,5 和 6 其实没有时序上的限制,而是并行处理的。下面先来看第 5 个工作:调用线程是如何进入休眠等待服务端执行结果的。

binder_thread_read

在唤醒目标线程后,调用线程就执行完 binder_thread_write() 写完了数据,返回到 binder_ioctl_write_read() 方法中,接着执行 binder_thread_read() 方法。

而调用线程的休眠就是在此方法中触发的,下面将 binder_thread_read() 分为两部分来看,首先是是否阻塞当前线程的判断逻辑:

static 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){    void __user *buffer = (void __user *)(uintptr_t)binder_buffer; //bwr.read_buffer    void __user *ptr = buffer + *consumed; //数据起始地址    void __user *end = buffer + size; //数据结束地址    if (*consumed == 0) {        if (put_user(BR_NOOP, (uint32_t __user *)ptr))            return -EFAULT;        ptr += sizeof(uint32_t);    }    //是否要准备睡眠当前线程    wait_for_proc_work = thread->transaction_stack == NULL &&            list_empty(&thread->todo);    if (wait_for_proc_work) {        if (non_block) { //non_block 为 false            if (!binder_has_proc_work(proc, thread))                ret = -EAGAIN;        } else        ret = wait_event_freezable_exclusive(proc->wait,                         binder_has_proc_work(proc, thread));    } else {        if (non_block) { //non_block 为 false            if (!binder_has_thread_work(thread))                ret = -EAGAIN;        } else            ret = wait_event_freezable(thread->wait,                         binder_has_thread_work(thread));    }

consumed 即用户空间的 bwr.read_consumed,这里是 0 ,所以将一个 BR_NOOP 加到了 ptr 中。

怎么理解 wait_for_proc_work 条件呢?在 binder_transaction() 方法中将 server 端要处理的 transaction 记录到了当前调用线程 thread->transaction_stack 中;将当前调用线程待处理的任务记录到了 thread->todo 中。所以这里的 thread->transaction_stack 和 thread->todo 都不为空,wait_for_proc_work 为 false,代表不准备阻塞当前线程。

但 wait_for_proc_work 并不是决定是否睡眠的最终条件,接着往下看,其中 non_block 恒为 false,那是否要睡眠当前线程就取决于 binder_has_thread_work() 的返回值,binder_has_thread_work() 方法如下:

static int binder_has_thread_work(struct binder_thread *thread){    return !list_empty(&thread->todo) || thread->return_error != BR_OK ||        (thread->looper & BINDER_LOOPER_STATE_NEED_RETURN);}

thread->todo 不为空,所以 binder_has_thread_work() 返回 true,当前调用线程不进入休眠,继续往下执行。你可能会有疑问,不是说调用线程的休眠就是在 binder_thread_read() 方法中触发的吗?确实是,只不过不是本次,先接着分析 binder_thread_read() 继续往下要执行的逻辑:

struct binder_work *w;w = list_first_entry(&thread->todo, struct binder_work,entry);switch (w->type) {    case BINDER_WORK_TRANSACTION_COMPLETE: {        cmd = BR_TRANSACTION_COMPLETE;        if (put_user(cmd, (uint32_t __user *)ptr))            return -EFAULT;        ptr += sizeof(uint32_t);        binder_stat_br(proc, thread, cmd);        list_del(&w->entry); //删除 binder_work 在 thread->todo 中的引用        kfree(w);    }    case BINDER_WORK_NODE{...}    case BINDER_WORK_DEAD_BINDER{...}    ...

在上面 binder_transaction() 方法最后,将 BINDER_WORK_TRANSACTION_COMPLETE 类型的 binder_work 加入到 thread->todo 中。而这里就是对这个 binder_work 进行处理,将一个 BR_TRANSACTION_COMPLETE 命令加到了 ptr 中。

梳理一下目前的逻辑,至此已经顺序执行完 binder_thread_write()、binder_thread_read() 方法,并且在 binder_thread_read() 中往用户空间传输了两个命令:BR_NOOP 和 BR_TRANSACTION_COMPLETE。

本次 binder_ioctl() 调用就执行完了,然后会回到 IPCThreadState 中,上一篇文章中详细分析了 IPCThreadState 中的代码,这里就不再展开,简单概括一下后续执行的逻辑:

mIn 中有 BR_NOOP 和 BR_TRANSACTION_COMPLETE 两个命令,首先处理 BR_NOOP 命令,此命令什么也没做,由于 talkWithDriver() 处于 while 循环中,会再一次进入 talkWithDriver(),但因为此时 mIn 中还有数据没读完,不会调用 binder_ioctl()。

然后处理 BR_TRANSACTION_COMPLETE 命令,如果是 oneway 就直接结束本次 IPC 调用,否则再一次进入 talkWithDriver(),第二次进入 talkWithDriver 时,bwr.write_size = 0,bwr.read_size > 0,所以会第二次调用 binder_ioctl() 方法。第二次执行 binder_ioctl() 时,bwr.write_size = 0,bwr.read_size > 0,所以不会再执行 binder_thread_write() 方法,而只执行 binder_thread_read() 方法。

第二次执行 binder_thread_read() 时,thread->todo 已经被处理为空,但是 thread->transaction_stack 还不为空,wait_for_proc_work 仍然为 false,但最终决定是否要休眠的条件成立了: binder_has_thread_work(thread) 返回 false,由此当前调用线程通过 wait_event_freezable() 进入休眠。

小结

至此还剩下两个工作:

6.目标进程直接拿到数据进行处理,处理完后唤醒调用线程
7.调用线程返回处理结果

但是已经不用再看代码了,因为上述方法已经覆盖了剩下的工作。对于 getService() 来说,目标进程就是 Service Manager,相关的代码在 Binder 机制 [上] 也已经做过详细的分析。用图来概括整体的工作。调用进程逻辑:

Service Manager 端逻辑:

本节完整的分析了一次 IPC 调用中 binder 驱动内部具体的执行逻辑,此部分也是 binder 机制中最难的,而将最难的部分掌握后,可以极大的提高信心。

想要完全掌握本节内容,前提是必须对前两篇 binder 文章已经熟悉,因为 binder 驱动和用户空间的 IPCThreadState 以及 servicemanager 关联是十分紧密的,如果没有掌握,是无法真正理清本节内容的。

binder Q&A

在上面分析 binder 驱动代码过程中,主要是根据文章开头的七个工作为线索,为了不偏离主线,在遇到比较重要的知识点时都没做延伸,这里以 Q&A 的方式补充分析。

如何找到目标进程 Binder 实体

servicemanager 的 binder 实体固定为 binder_context_mgr_node,直接返回即可。如何获取其他 binder 实体呢?在上面的 binder_transaction() 方法中是这样获取目标 binder 的:

    //根据 handle 找到目标 binder 实体节点的引用    ref = binder_get_ref(proc, tr->target.handle);

binder_get_ref() 方法如下:

static struct binder_ref *binder_get_ref(struct binder_proc *proc,                uint32_t desc){    struct rb_node *n = proc->refs_by_desc.rb_node; //取红黑树根结点    struct binder_ref *ref;    while (n) { //遍历查询指定 desc(handle) 值的 binder 实体        ref = rb_entry(n, struct binder_ref, rb_node_desc);        if (desc < ref->desc)            n = n->rb_left;        else if (desc > ref->desc)            n = n->rb_right;        else            return ref;    }    return NULL;}

可见是在调用进程的 binder_proc 结构体中获取到的,那目标 binder 实体又是什么时候存储到调用进程中的呢?

对于实名的 Server,当它利用 addService() 把自身加到 ServiceManager 中时,会"路过" binder 驱动,binder 驱动就会把这一 binder 实体记录到 ServiceManager 的 binder_proc->refs_by_desc 和 binder_proc->refs_by_node 中。

当一个 Binder Client 通过 getService() 向 ServiceManager 发起查询时,ServiceManager 就可以准确的告诉 Client 目标 Binder 实体,将其也记录到 Client 的 binder_proc->refs_by_desc 和 binder_proc->refs_by_node 中。

上面说的是通过 addService() 将自身注册到 Service Manager 中的 Server,任何 Binder Client 都能通过 Service Manager 获取到,这种叫做 “实名” Server。 而 Android 中还存在另一种 Binder Server,并不在 Service Manager 中注册,比如我们自定义的 Service,这种可以叫做 “匿名” Server。

如何找到一个匿名 Server 的 binder 实体呢?匿名 Server 一般要通过其他实名 Server 为中介来传递,比如 IWindowSession 是靠 WindowManagerService 来传递的,当 Binder Client 调用 openSession 真正生成一个 Session 对象,这个对象作为 reply 第一次 “路过” binder 驱动时,就会被记录到 Client 的 binder_proc->refs_by_desc 和 binder_proc->refs_by_node 中。

如何实现 Binder 线程的睡眠与唤醒

答案在上文中已经有很详细的分析了,这里再概括一下:

唤醒:在 binder_transaction() 方法中写完数据后,通过 wake_up_interruptible(target_wait) 唤醒目标线程

睡眠:在 binder_thread_read() 中,通过 wait_event_freezable_exclusive() 或 wait_event_freezable() 调用进入睡眠

最后

最后再对 binder 的整体架构做一个简要的概述。对于一个比较典型的、两个应用之间的 IPC 通信流程而言:

Client 通过 ServiceManager 或 AMS 获取到的远程 binder 实体,一般会用 Proxy 做一层封装,比如 ServiceManagerProxy、 AIDL 生成的 Proxy 类。而被封装的远程 binder 实体是一个 BinderProxy

BpBinder 和 BinderProxy 其实是一个东西:远程 binder 实体,只不过一个 Native 层、一个 Java 层,BpBinder 内部持有了一个 binder 句柄值 handle。

ProcessState 是进程单例,负责打开 Binder 驱动设备及 mmap;IPCThreadState 为线程单例,负责与 binder 驱动进行具体的命令通信。

由 Proxy 发起 transact() 调用,会将数据打包到 Parcel 中,层层向下调用到 BpBinder ,在 BpBinder 中调用 IPCThreadState 的 transact() 方法并传入 handle 句柄值,IPCThreadState 再去执行具体的 binder 命令。

由 binder 驱动到 Server 的大概流程就是:Server 通过 IPCThreadState 接收到 Client 的请求后,层层向上,最后回调到 Stub 的 onTransact() 方法。

当然这不代表所有的 IPC 流程,比如 Service Manager 作为一个 Server 时,便没有上层的封装,也没有借助 IPCThreadState,而是初始化后通过 binder_loop() 方法直接与 binder 驱动通信的。

链接:细读《深入理解 Android 内核设计思想》系列

关注公众号,Get 更多知识点

更多相关文章

  1. android 在子线程中如何把自定义对象传到handler中处理
  2. Android(安卓)返回上一个界面刷新数据
  3. 【Android(安卓)开发教程】在Activity和Service之间建立链接
  4. bitmap设置图片尺寸缩小,避免内存溢出/OutOfMemoryError的优化方
  5. HttpPost发送JSON数据中文乱码问题。
  6. Android之Searchable
  7. android怎么用APK调用JNI简单实例
  8. Android中Shared Preferences、Files、Network、SQLite数据库编
  9. Android(安卓)Handler 用法解析

随机推荐

  1. Android——消息机制中的Message Pool是
  2. android UI进阶之布局的优化
  3. Android(安卓)引入FFmpeg库so文件
  4. Android WebView 访问https显示空白页
  5. Android Fragment重叠问题
  6. android在进行创建项目gen下没有自动生成
  7. 浅析Android——Android(安卓)8.0(O)后台
  8. 在android的学习中遇到的一些小问题
  9. android基础知识——android生命周期
  10. Android 系统启动分析