Binder如何实现进程间通信的
这个问题重点考察 2个大的部分
- Binder C/S通信架构
在这个问题中简略回答
- Binder跨进程通信的具体过程
Binder C/S通信架构
这里涉及到Binder的设计相关,通常需要介绍下C/S结构,然后可以用一个网页访问的例子做类比,对比下Binder概念和cs中的概念。
一次完整的进程间通信必然至少包含两个进程,通常我们称通信的双方分别为客户端进程(Client)和服务端进程(Server),由于进程隔离机制的存在,通信双方就需要借助 Binder 来实现
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 驱动的交互来间接的实现跨进程通信。
Client、Server、ServiceManager、Binder 驱动这几个组件在通信过程中扮演的角色就如同互联网中服务器(Server)、客户端(Client)、DNS域名服务器(ServiceManager)以及路由器(Binder 驱动)之前的关系。
通常我们访问一个网页的步骤是这样的:首先在浏览器输入一个地址,如 http://www.baidu.com 然后按下回车键。但是并没有办法通过域名地址直接找到我们要访问的服务器,因此需要首先访问 DNS 域名服务器,域名服务器中保存了http://www.baidu.com 对应的 ip 地址 10.xx.xx.xx,然后通过这个 ip 地址才能放到到 http://www.baidu.com对应的服务器。
其中binder概念与服务端概念对比如下
Binder 驱动 Binder 驱动就如同路由器一样,是整个通信的核心;驱动负责进程之间 Binder 通信的建立,Binder 在进程之间的传递,Binder 引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。
ServiceManager 与实名 Binder(BBinder) ServiceManager 和 DNS 类似,作用是将字符形式的 Binder 名字转化成 Client 中对该 Binder 的引用(BpBinder),使得 Client 能够通过 Binder 的名字获得对 Binder 实体的引用。注册了名字的 Binder 叫实名 Binder,就像网站一样除了除了有 IP 地址意外还有自己的网址。 Server 创建了 Binder,并为它起一个字符形式,可读易记得名字,将这个 Binder 实体连同名字一起以数据包的形式通过 Binder 驱动发送给 ServiceManager ,通知 ServiceManager 注册一个名为“张三”的 Binder,它位于某个 Server 中。驱动为这个穿越进程边界的 Binder 创建位于内核中的实体节点以及 ServiceManager 对实体的引用,将名字以及新建的引用打包传给 ServiceManager。ServiceManger 收到数据后从中取出名字和引用填入查找表。
细心的读者可能会发现,ServierManager 是一个进程,Server 是另一个进程,Server 向 ServiceManager 中注册 Binder 必然涉及到进程间通信。当前实现进程间通信又要用到进程间通信,这就好像蛋可以孵出鸡的前提却是要先找只鸡下蛋!Binder 的实现比较巧妙,就是预先创造一只鸡来下蛋。ServiceManager 和其他进程同样采用 Bidner 通信,ServiceManager 是 Server 端,有自己的 Binder 实体,其他进程都是 Client,需要通过这个 Binder 的引用来实现 Binder 的注册,查询和获取。ServiceManager 提供的 Binder 比较特殊,它没有名字也不需要注册。当一个进程使用 BINDER_SET_CONTEXT_MGR 命令将自己注册成 ServiceManager 时 Binder 驱动会自动为它创建 Binder 实体(这就是那只预先造好的那只鸡)。 其次这个 Binder 实体的引用在所有 Client 中都固定为 0 而无需通过其它手段获得。也就是说,一个 Server 想要向 ServiceManager 注册自己的 Binder 就必须通过这个 0 号引用和 ServiceManager 的 Binder 通信。类比互联网,0 号引用就好比是域名服务器的地址,你必须预先动态或者手工配置好。要注意的是,这里说的 Client 是相对于 ServiceManager 而言的,一个进程或者应用程序可能是提供服务的 Server,但对于 ServiceManager 来说它仍然是个 Client。
Client 获得实名 Binder 的引用(BpBinder) Server 向 ServiceManager 中注册了 Binder 以后, Client 就能通过名字获得 Binder 的引用了。Client 也利用保留的 0 号引用向 ServiceManager 请求访问某个 Binder: 我申请访问名字叫张三的 Binder 引用。ServiceManager 收到这个请求后从请求数据包中取出 Binder 名称,在查找表里找到对应的条目,取出对应的 Binder 引用作为回复发送给发起请求的 Client。 从面向对象的角度看,Server 中的 Binder 实体现在有两个引用:一个位于 ServiceManager 中,一个位于发起请求的 Client 中。如果接下来有更多的 Client 请求该 Binder,系统中就会有更多的引用指向该 Binder ,就像 Java 中一个对象有多个引用一样。
Binder通信过程
了解完binder架构之后 我们重点介绍下通信过程
- 首先需要注册服务端,只有注册了服务端,客户端才有通讯的目标,服务端通过 ServiceManager 注册服务,注册的过程就是向 Binder 驱动的全局链表 binder_procs 中插入服务端的信息(binder_proc 结构体,每个 binder_proc 结构体中都有 todo 任务队列),然后向 ServiceManager 的 svcinfo 列表中缓存一下注册的服务。
- 有了服务端,客户端就可以跟服务端通讯了,通讯之前需要先获取到服务,拿到服务的代理,也可以理解为引用。比如下面的代码:
//获取WindowManager服务引用 WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);获取服务端的方式就是通过 ServiceManager 向 svcinfo 列表中查询一下返回服务端的代理,svcinfo 列表就是所有已注册服务的通讯录,保存了所有注册的服务信息。
- 有了服务端的引用我们就可以向服务端发送请求了,通过 BinderProxy 将我们的请求参数发送给 ServiceManager,通过共享内存的方式使用内核方法 copy_from_user() 将我们的参数先拷贝到内核空间,这时我们的客户端进入等待状态,然后 Binder 驱动向服务端的 todo 队列里面插入一条事务,执行完之后把执行结果通过 copy_to_user() 将内核的结果拷贝到用户空间(这里只是执行了拷贝命令,并没有拷贝数据,binder只进行一次拷贝),唤醒等待的客户端并把结果响应回来,这样就完成了一次通讯。