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

Posted 清风Coolbreeze

tags:

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

Binder承担了绝大部分android进程通信的职责,可以看做是Android的血管系统,负责不同服务模块进程间的通信。

在对Binder的理解上,可大可小,日常APP开发并不怎么涉及Binder通信知识,最多就是Service及AIDL的使用会涉及部分Binder知识。

Binder往小了说可总结成一句话:一种IPC进程间通信方式,负责进程A的数据,发送到进程B。

而往大了说,其实涉及的知识还是很多的,如Android 对于原Binder驱动的扩展、Zygote进程孵化中对于Binder通信的支持、Java层Binder封装,Native层对于Binder通信的封装、Binder讣告机制等等。很多分析Binder框架的文都是从ServiceManager、Binder驱动、addService、getService来分析等来分析的。

其实这些主要是针对系统提供的服务,但是bindService启动的服务走的却还是有很大不同的。

本篇文章主要简述Binder在面试中一些难以理解的点,但不会太细的跟踪分析,旨在抛砖,自己去发玉掘金,由于篇幅过大,已打算两题+解析作为一篇来发表;

如果你完整阅读过后对你有所帮助或者期待更新的话,请帮我点个关注,我会对你感激不尽。

binder_transaction堆栈及唤醒那个队列

  • 面试题一:Binder的定向制导,如何找到目标Binder,唤起进程或者线程(字节跳动)
  • 面试题二:Binder中的红黑树,为什么会有两棵binder_ref红黑树(腾讯)

面试题一:Binder的定向制导,如何找到目标Binder,唤起进程或者线程呢?(字节跳动)

Binder实体服务其实有两种:

  • 一是通过addService注册到ServiceManager中的服务,比如ActivityManagerService、PackageManagerService、PowerManagerService等,一般都是系统服务;

  • 还有一种是通过bindService拉起的一些服务,一般是开发者自己实现的服务。

这里先看通过addService添加的被ServiceManager所管理的服务。

有很多分析ServiceManager的文章,本文不分析ServiceManager,只是简单提一下,ServiceManager是比较特殊的服务,所有应用都能直接使用,因为ServiceManager对于Client端来说Handle句柄是固定的,都是0,所以ServiceManager服务并不需要查询,可以直接使用。

理解Binder定向制导的关键是理解Binder的四棵红黑树,先看一下binder_proc结构体,在它内部有四棵红黑树,threads,nodes,refs_by_desc,refs_by_node,nodes就是Binder实体在内核中对应的数据结构,binder_node里记录进程相关的binder_proc,还有Binder实体自身的地址等信息,nodes红黑树位于binder_proc,可以知道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;
    。。。
    struct list_head todo;
    wait_queue_head_t wait;
    。。。
};

现在假设存在一堆Client与Service,Client如何才能访问Service呢?

首先Service会通过addService将binder实体注册到ServiceManager中去,Client如果想要使用Servcie,就需要通过getService向ServiceManager请求该服务。

在Service通过addService向ServiceManager注册的时候,ServiceManager会将服务相关的信息存储到自己进程的Service列表中去,同时在ServiceManager进程的binder_ref红黑树中为Service添加binder_ref节点,这样ServiceManager就能获取Service的Binder实体信息。

而当Client通过getService向ServiceManager请求该Service服务的时候,ServiceManager会在注册的Service列表中查找该服务,如果找到就将该服务返回给Client,在这个过程中,ServiceManager会在Client进程的binder_ref红黑树中添加binder_ref节点,可见本进程中的binder_ref红黑树节点都不是本进程自己创建的,要么是Service进程将binder_ref插入到ServiceManager中去,要么是ServiceManager进程将binder_ref插入到Client中去。之后,Client就能通过Handle句柄获取binder_ref,进而访问Service服务。

getService之后,便可以获取binder_ref引用,进而获取到binder_proc与binder_node信息,之后Client便可有目的的将binder_transaction事务插入到binder_proc的待处理列表。

并且,如果进程正在睡眠,就唤起进程,其实这里到底是唤起进程还是线程也有讲究,对于Client向Service发送请求的状况,一般都是唤醒binder_proc上睡眠的线程:

struct binder_ref {
    int debug_id;
    struct rb_node rb_node_desc;
    struct rb_node rb_node_node;
    struct hlist_node node_entry;
    struct binder_proc *proc;
    struct binder_node *node;
    uint32_t desc;
    int strong;
    int weak;
    struct binder_ref_death *death;
};

面试题二:Binder中的红黑树,为什么会有两棵binder_ref红黑树(腾讯)

binder_proc中存在两棵binder_ref红黑树,其实两棵红黑树中的节点是复用的,只是查询方式不同,一个通过handle句柄,一个通过node节点查找。

个人理解:refs_by_node红黑树主要是为了binder驱动往用户空间写数据所使用的,而refs_by_desc是用户空间向Binder驱动写数据使用的,只是方向问题。比如在服务addService的时候,binder驱动会在在ServiceManager进程的binder_proc中查找binder_ref结构体。

但是如果没有就会新建binder_ref结构体,再比如在Client端getService的时候,binder驱动会在Client进程中通过 binder_get_ref_for_node为Client创建binder_ref结构体,并分配句柄,同时插入到refs_by_desc红黑树中,可见refs_by_node红黑树,主要是给binder驱动往用户空间写数据使用的。相对的refs_by_desc主要是为了用户空间往binder驱动写数据使用的,当用户空间已经获得Binder驱动为其创建的binder_ref引用句柄后,就可以通过binder_get_ref从refs_by_desc找到响应binder_ref,进而找到目标binder_node。

可见有两棵红黑树主要是区分使用对象及数据流动方向,看下面的代码就能理解:

// 根据32位的uint32_t desc来查找,可以看到,binder_get_ref不会新建binder_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) {
        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_get_ref并具备binder_ref的创建功能,相对应的看一下binder_get_ref_for_node,binder_get_ref_for_node红黑树主要通过binder_node进行查找,如果找不到,就新建binder_ref,同时插入到两棵红黑树中去

static struct binder_ref *binder_get_ref_for_node(struct binder_proc *proc,
                          struct binder_node *node)
{
    struct rb_node *n;
    struct rb_node **p = &proc->refs_by_node.rb_node;
    struct rb_node *parent = NULL;
    struct binder_ref *ref, *new_ref;
    while (*p) {
        parent = *p;
        ref = rb_entry(parent, struct binder_ref, rb_node_node);
        if (node < ref->node)
            p = &(*p)->rb_left;
        else if (node > ref->node)
            p = &(*p)->rb_right;
        else
            return ref;
    }

    // binder_ref 可以在两棵树里面,但是,两棵树的查询方式不同,并且通过desc查询,不具备新建功能
    new_ref = kzalloc(sizeof(*ref), GFP_KERNEL);
    if (new_ref == NULL)
        return NULL;
    binder_stats_created(BINDER_STAT_REF);
    new_ref->debug_id = ++binder_last_id;
    new_ref->proc = proc;
    new_ref->node = node;
    rb_link_node(&new_ref->rb_node_node, parent, p);
    // 插入到proc->refs_by_node红黑树中去
    rb_insert_color(&new_ref->rb_node_node, &proc->refs_by_node);
    // 是不是ServiceManager的
    new_ref->desc = (node == binder_context_mgr_node) ? 0 : 1;
    // 分配Handle句柄,为了插入到refs_by_desc
    for (n = rb_first(&proc->refs_by_desc); n != NULL; n = rb_next(n)) {
        ref = rb_entry(n, struct binder_ref, rb_node_desc);
        if (ref->desc > new_ref->desc)
            break;
        new_ref->desc = ref->desc + 1;
    }
    // 找到目标位置
    p = &proc->refs_by_desc.rb_node;
    while (*p) {
        parent = *p;
        ref = rb_entry(parent, struct binder_ref, rb_node_desc);
        if (new_ref->desc < ref->desc)
            p = &(*p)->rb_left;
        else if (new_ref->desc > ref->desc)
            p = &(*p)->rb_right;
        else
            BUG();
    }
    rb_link_node(&new_ref->rb_node_desc, parent, p);
    // 插入到refs_by_desc红黑树中区
    rb_insert_color(&new_ref->rb_node_desc, &proc->refs_by_desc);

    if (node) {
        hlist_add_head(&new_ref->node_entry, &node->refs);
        binder_debug(BINDER_DEBUG_INTERNAL_REFS,
                 "binder: %d new ref %d desc %d for "
                 "node %d\\n", proc->pid, new_ref->debug_id,
                 new_ref->desc, node->debug_id);
    } else {
        binder_debug(BINDER_DEBUG_INTERNAL_REFS,
                 "binder: %d new ref %d desc %d for "
                 "dead node\\n", proc->pid, new_ref->debug_id,
                  new_ref->desc);
    }
    return new_ref;
}

该函数调用在binder_transaction函数中,其实就是在binder驱动访问target_proc的时候,这也也很容易理解,Handle句柄对于跨进程没有任何意义,进程A中的Handle,放到进程B中是无效的。

总结

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

PS:关于我


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

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

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

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

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

Android 大厂高级面试必问36题以及算法合集(附:2022年Android 中高级面试题汇总以及面试题解析)

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

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

干货Android BAT高级面试必问36题以及算法合集(附:46份面试题+49份源码解析笔记+145份项目实战PDF)

最新Android高级面试知识点--事件分发Binder机制