让你一看就明白的binder机制

写在前面

网上有很多学习android binder机制的文章和博客,但是大部分或者是深入native不能自拔,看的云里雾里(本人一直使用java,C语言较渣);或者是只讲理论缺乏实际编程的过程。所以就想总结下binder的基本理论并附带一个基于Aidl的进程间的通讯的Demo,希望能对初步接触android binder机制的小伙伴们提供些帮助。当然本人能力有限,如有错误之处还请指教。


binder机制是什么?

我们一直在说binder机制,那么binder机制到底是什么东西呢?讲binder之前,我们需要先来了解下IPC(inter process communication)—-进程间通讯或者是跨进程通讯,其实也就是指的是两个进程之间通讯(数据交换)的过程。

每一个操作系统都有各自的IPC机制。windows可以通过剪贴板、管道或者是邮槽进行线程间通讯;Linux上可以通过命名管道、共享内同、信号量等进行进程间通讯。Android是基于Linux的移动操作系统,但是它有着自己比较特色的进程间通讯方式那就是binder。binder可以轻松地实现进程间的通讯。


binder架构

网络上的流传的binder架构根据是否使用serviceManager分为两种:一个是含有serviceManager参与的binder架构,一种是client与service直接使用binder进行进程通讯的binder架构。当然本质上其实他们的过程都是一样的。现在为了尽可能详细介绍,还是决定分开介绍下。第一部分是含有serviceManager的binder通讯过程(实名binder),侧重于整个流程的介绍。第二部分则侧重应用级代码的介绍(匿名binder)。

含有serviceManager的binder机制

Binder框架定义了四个角色:Server,Client,ServiceManager以及Binder驱动。其中Server,Client,ServiceManager运行于用户空间,驱动运行于内核空间。这四个角色的关系和互联网类似:Server是服务器,Client是客户终端,ServiceManager是域名服务器(DNS),驱动是路由器。

  • Binder 驱动

    和路由器一样,Binder驱动虽然默默无闻,却是通信的核心。尽管名叫‘驱动’,实际上和硬件设备没有任何关系,只是实现方式和设备驱动程序是一样的。它工作于内核态,驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,数据包在进程之间的传递和交互等一系列底层支持。

  • ServiceManager 与实名Binder

    和DNS类似,ServiceManager的作用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder实体的引用。注册了名字的Binder叫实名Binder,就象每个网站除了有IP地址外还有自己的网址。Server创建了Binder实体,为其取一个字符形式可读易记的名字,将这个Binder连同名字以数据包的形式通过Binder驱动发送给ServiceManager,通知ServiceManager注册一个名叫张三的Binder,它位于某个Server中。驱动为这个穿过进程边界的Binder创建位于内核中的实体节点以及SMgr对实体的引用,将名字及新建的引用打包传递给ServiceManager。ServiceManager收数据包后,从中取出名字和引用填入一张查找表中。

  • Client 获得实名Binder的引用

    Server向ServiceManager注册了Binder实体及其名字后,Client就可以通过名字获得该Binder的引用了。Client也利用保留的0号引用向ServiceManager请求访问某个Binder:我申请获得名字叫张三的Binder的引用。ServiceManager收到这个连接请求,从请求数据包里获得Binder的名字,在查找表里找到该名字对应的条目,从条目中取出Binder的引用,将该引用作为回复发送给发起请求的Client。

图形化过程图如下所示:

这里写图片描述

以上就是实名binder(通过service向serviceManager进行注册)的跨进程处理流程过程。

匿名binder与client

在我们日常开发中,我们需要根据各种各样的需求来定义我们自己功能的service,如果该service和client同处于一个进程,那么他们的调用以及数据交换没有任何问题。当不处于一个进程的时候,该service也没有像前一部分那样向serviceManager进行注册(匿名binder),那么我们该怎样去实现跨进程通讯呢?

首先将图形化流程图展示如下:

这里写图片描述

上面图表中的方法我们会在接下来的demo实践中逐个介绍,我们可以先从整体上来看下这部分的逻辑:

binder通信是一种client-server的通信结构,
1.从表面上来看,是client通过获得一个server的代理接口,对server进行直接调用;
2.实际上,代理接口中定义的方法与server中定义的方法是一一对应的;
3.client调用某个代理接口中的方法时,代理接口的方法会将client传递的参数打包成为Parcel对象;
4.代理接口将该Parcel发送给内核中的binder driver.
5.server会读取binder driver中的请求数据,如果是发送给自己的,解包Parcel对象,处理并将结果返回;
6.整个的调用过程是一个同步过程,在server处理的时候,client会block住。

Binder如何精确制导,找到目标Binder

Binder实体服务如本文所述其实有两种,一是通过addService注册到ServiceManager中的服务,比如ActivityManagerService、PackageManagerService、PowerManagerService等,一般都是系统服务;还有一种是通过bindService拉起的一些服务,一般是开发者自己实现的服务。这里先看通过addService添加的被ServiceManager所管理的服务。有很多分析ServiceManager的文章,本文不分析ServiceManager,只是简单提一下,ServiceManager是比较特殊的服务,所有应用都能直接使用,因为ServiceManager对于Client端来说Handle句柄是固定的,都是0,所以ServiceManager服务并不需要查询,可以直接使用。

理解Binder定向制导的关键是理解Binder的四棵红黑树,本文定界为binder的初始介绍文章,所以不再过度涉及JNI层面的东西。

Binder一次拷贝原理

Android选择Binder作为主要进程通信的方式同其性能高也有关系,Binder只需要一次拷贝就能将A进程用户空间的数据为B进程所用。这里主要涉及两个点:

  • Binder的map函数,会将内核空间直接与用户空间对应,用户空间可以直接访问内核空间的数据
  • A进程的数据会被直接拷贝到B进程的内核空间(一次拷贝)

一次拷贝原理图示

这里写图片描述

数据拷贝操作:当数据从用户空间拷贝到内核空间的时候,是直从当前进程的用户空间接拷贝到目标进程的内核空间,这个过程是在请求端线程中处理的,操作对象是目标进程的内核空间

而由于Binder内核空间的数据能直接映射到用户空间,这里就不在需要拷贝到用户空间。这就是一次拷贝的原理。内核空间的数据映射到用户空间其实就是添加一个偏移地址,并且将数据的首地址、数据的大小都复制到一个用户空间的Parcel结构体

系统服务与bindService等启动的服务的区别

服务可分为系统服务与普通服务,系统服务一般是在系统启动的时候,由SystemServer进程创建并注册到ServiceManager中的。而普通服务一般是通过ActivityManagerService启动的服务,或者说通过四大组件中的Service组件启动的服务。这两种服务在实现跟使用上是有不同的,主要从以下几个方面:

  • 服务的启动方式
  • 服务的注册与管理
  • 服务的请求使用方式

首先看一下服务的启动上,

系统服务一般都是SystemServer进程负责启动,比如AMS,WMS,PKMS,电源管理等,这些服务本身其实实现了Binder接口,作为Binder实体注册到ServiceManager中,被ServiceManager管理,而SystemServer进程里面会启动一些Binder线程,主要用于监听Client的请求,并分发给响应的服务实体类,可以看出,这些系统服务是位于SystemServer进程中(有例外,比如Media服务)。

而bindService类型的服务,这类服务一般是通过Activity的startService或者其他context的startService启动的,这里的Service组件只是个封装,主要的是里面Binder服务实体类,这个启动过程不是ServcieManager管理的,而是通过ActivityManagerService进行管理的,同Activity管理类似。

再来看一下服务的注册与管理:

系统服务一般都是通过ServiceManager的addService进行注册的,这些服务一般都是需要拥有特定的权限才能注册到ServiceManager。

而bindService启动的服务可以算是注册到ActivityManagerService,只不过ActivityManagerService管理服务的方式同ServiceManager不一样,而是采用了Activity的管理模型,详细的可以自行分析

最后看一下使用方式

使用系统服务一般都是通过ServiceManager的getService得到服务的句柄,这个过程其实就是去ServiceManager中查询注册系统服务。

而bindService启动的服务,主要是去ActivityManagerService中去查找相应的Service组件,最终会将Service内部Binder的句柄传给Client。

这里写图片描述

Binder请求的同步与异步

很多人都会说,Binder是对Client端同步,而对Service端异步,其实并不完全正确,在单次Binder数据传递的过程中,其实都是同步的。只不过,Client在请求Server端服务的过程中,是需要返回结果的,即使是你看不到返回数据,其实还是会有个成功与失败的处理结果返回给Client,这就是所说的Client端是同步的。

至于说服务端是异步的,可以这么理解:在服务端在被唤醒后,就去处理请求,处理结束后,服务端就将结果返回给正在等待的Client线程,将结果写入到Client的内核空间后,服务端就会直接返回了,不会再等待Client端的确认,这就是所说的服务端是异步的。

Binder传输数据的大小限制

虽然APP开发时候,Binder对程序员几乎不可见,但是作为Android的数据运输系统,Binder的影响是全面性的,所以有时候如果不了解Binder的一些限制,在出现问题的时候往往是没有任何头绪,比如在Activity之间传输BitMap的时候,如果Bitmap过大,就会引起问题,比如崩溃等,这其实就跟Binder传输数据大小的限制有关系,在上面的一次拷贝中分析过,mmap函数会为Binder数据传递映射一块连续的虚拟地址,这块虚拟内存空间其实是有大小限制的,不同的进程可能还不一样。

普通的由Zygote孵化而来的用户进程,所映射的Binder内存大小是不到1M的,准确说是 110241024) - (4096 *2) :这个限制定义在ProcessState类中,如果传输说句超过这个大小,系统就会报错,因为Binder本身就是为了进程间频繁而灵活的通信所设计的,并不是为了拷贝大数据而使用。

基于binder跨进程通讯的DEMO

以上就是关于binder机制的总体介绍了,大部分都是文字介绍,接下里我们将基于AIDL去实现一个跨进程通讯的经典Demo,并在该demo中介绍binder的应用级别函数的作用以及调用过程。

基于binder的跨进程通讯之使用AIDL实现Demo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值