安卓面试

Binder的介绍与原理分析

2019-05-27  本文已影响0人  莫库施勒

介绍

1,首先进程间的通信方式:管道、消息队列、共享内存、信号量、信号、socket套接字、信号等
2, binder 的优势:

通信

跨进程通信是需要内核空间做支持的。传统的 IPC 机制如管道、Socket 都是内核的一部分,因此通过内核支持来实现进程间通信自然是没问题的。

但是 Binder 并不是 Linux 系统内核的一部分,通过 Linux 的动态内核可加载模块(Loadable Kernel Module,LKM)的机制。

模块是具有独立功能的程序,它可以被单独编译,但是不能独立运行。它在运行时被链接到内核作为内核的一部分运行。这样,Android 系统就可以通过动态添加一个内核模块运行在内核空间,用户进程之间通过这个内核模块作为桥梁来实现通信。在 Android 系统中,这个模块就是 Binder 驱动(Binder Dirver)

Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。

原理

进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘-->内核空间-->用户空间);通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。

一次完整的 Binder IPC 通信过程通常如下:
1, 首先 Binder 驱动在内核空间创建一个数据接收缓存区;
2, 接着在内核空间开辟一块内核缓存区,建立内核缓存区数据接收缓存区之间的映射关系,以及数据接收缓存区和接收进程用户空间地址的映射关系;
3, 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

通信过程

通信模型

Binder 是基于 C/S 架构的,由Client、Server、ServiceManager、Binder 驱动 四部分组成。其中 Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。其中 Service Manager 和 Binder 驱动由系统提供,而 Client、Server 由应用程序来实现。Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 来访问设备文件 /dev/binder,从而实现与 Binder 驱动的交互来间接的实现跨进程通信。


通信结构模型

Binder 驱动
Binder 驱动是整个通信的核心;驱动负责进程之间 Binder 通信的建立,Binder 在进程之间的传递,Binder 引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。
ServiceManager 与 Server Binder
Server 创建了 Binder,并为它起一个字符形式,可读易记得名字,将这个 Binder 实体连同名字一起以数据包的形式通过 Binder 驱动发送给 ServiceManager ,通知 ServiceManager 注册 Binder。驱动为这个穿越进程边界的 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager。ServiceManger 收到数据后从中取出名字和引用填入查找表。
我们知道,ServierManager 是一个进程,Server 是另一个进程,Server 向 ServiceManager 中注册 Binder 必然涉及到进程间通信。ServiceManager 和其他进程同样采用 Binder 通信,ServiceManager 是 Server 端,有自己的 Binder 实体,其他进程都是 Client。ServiceManager 提供的 Binder 比较特殊,它没有名字也不需要注册。当一个进程使用 BINDERSETCONTEXT_MGR 命令将自己注册成 ServiceManager 时Binder 驱动会自动为它创建 Binder 实体。其次这个 Binder 实体的引用在所有 Client 中都固定为 0 而无需通过其它手段获得。也就是说,一个 Server 想要向 ServiceManager 注册自己的 Binder 就必须通过这个 0 号引用和 ServiceManager 的 Binder 通信。
ServiceManager 与 Client Binder
Server 向 ServiceManager 中注册了 Binder 以后, Client 就能通过名字获得 Binder 的引用了。Client 也利用保留的 0 号引用向 ServiceManager 请求访问某个 Binder,ServiceManager 收到这个请求后从请求数据包中取出 Binder 名称,在查找表里找到对应的条目,取出对应的 Binder 引用作为回复发送给发起请求的 Client。从面向对象的角度看,Server 中的 Binder 实体现在有两个引用:一个位于 ServiceManager 中,一个位于发起请求的 Client 中。如果接下来有更多的 Client 请求该 Binder,系统中就会有更多的引用指向该 Binder 。

大致总结出 Binder 通信过程:
1, 一个进程使用 BINDERSETCONTEXT_MGR 命令通过 Binder 驱动将自己注册成为 ServiceManager;
2, Server 通过驱动向 ServiceManager 中注册 Binder(Server 中的 Binder 实体),表明可以对外提供服务。驱动为这个 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager,ServiceManger 将其填入查找表。
3, Client 通过名字,在 Binder 驱动的帮助下从 ServiceManager 中获取到对 Binder 实体的引用,通过这个引用就能实现和 Server 进程的通信。


通信过程模型

其中是从绿色的箭头开始,server -> Binder 驱动 -> ServiceManager , Client -> Binder 驱动 -> ServiceManager -> Binder驱动 -> Client

Binder 通信协议
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult){
    ...
  while (1) {
    if ((err=talkWithDriver()) < NO_ERROR) break;
     cmd = mIn.readInt32();
    switch (cmd) {
       <!--关键点1 -->
      case BR_TRANSACTION_COMPLETE:
            if (!reply && !acquireResult) goto finish;
            break;
     <!--关键点2 -->
      case BR_REPLY:
            {
                binder_transaction_data tr;
                  // free buffer,先设置数据,直接
                if (reply) {
                    if ((tr.flags & TF_STATUS_CODE) == 0) {
                        // 牵扯到数据利用,与内存释放
                        reply->ipcSetDataReference(...)
            }
            goto finish;
    }
 finish:
 ...
return err;
}

客户端通过talkWithDriver等待结果返回,如果没有返回值,直接break,否则会执行到关键点2,就上图来说,就是发送了 BR_TRANSACTION,而不会有 BC_REPLY。

Android 对 Binder 的支持

由于Android 的app都是从Zygote进程fork出来的,Zygote.forkAndSpecialize()用来 fork 新进程,通过RuntimeInit.nativeZygoteInit来初始化一些环境,通过 runSelectLoop来循环监听 socket,等待fork请求。


Android对Binder支持原理

首先,ProcessState::self()函数会调用open()打开/dev/binder设备,这个时候能够作为Client通过Binder进行远程通信;其次,proc->startThreadPool()负责新建一个binder线程,监听Binder设备,这样进程就具备了作为Binder服务端的资格。每个APP的进程都会通过onZygoteInit打开Binder,既能作为Client,也能作为Server,这就是Android进程天然支持Binder通信的原因。

问:Android APP有多少Binder线程,是固定的么
答:Android APP上层应用的进程一般是开启一个Binder线程,而对于SystemServer或者media服务等使用频率高,服务复杂的进程,一般都是开启两个或者更多。驱动会根据目标进程中是否存在足够多的Binder线程来告诉进程是不是要新建Binder线程,所以是不固定的

int main(int argc, char** argv)
{      ...
        ProcessState::self()->startThreadPool();
        IPCThreadState::self()->joinThreadPool();
 } 

参考:
https://zhuanlan.zhihu.com/p/35519585
http://gityuan.com/2014/01/01/binder-gaishu/
https://juejin.im/post/58c90816a22b9d006413f624

上一篇 下一篇

猜你喜欢

热点阅读