zoukankan      html  css  js  c++  java
  • binder机制

    首先来回答,什么时候使用多进程?

    在推送、保活、插件化、内存不足、webview时会使用多进程。

    多进程的优点

    主要是为了解决安全、内存不足的问题。

    进程之间的内存划分如图所示,进程之间是相互隔离的,不同进程之间也是相互隔离的,所以无法进行直接通信;不同进程之间也无法直接相互调用函数和获取数据。

    那么不同进程之间是如何进行通信呢?

    不同进程之间的通信是通过第三方介质来实现的。对于不同进程来说,他们共享操作系统的内核空间,所以,内核空间作为进程间通信的第三方介质再也合适不过了。如管道,消息队列,共享内存、信号、信号量等,都是在内核空间中得以实现的。当然也会有socket套接字等,但是这些方式暂时不在本文的讨论范围之内。还有在android中的广播、binder机制等。

    传统IPC传输数据实现进程间通信的示意图

    由图可知,两个进程之间的用户空间是相互隔离的。而操作系统内核并没有提供将数据直接从一个进程用户空间拷贝到另一个用户空间的函数,只有copy_from_user()将数据拷贝到内核空间;由于各个进程共享内核空间,可以继续使用copy_to_user()函数将数据拷贝到另一个进程的用户空间中,从而实现两个进程的通信。

    使用示意图来看一下binder机制的原理

    两个进程使用Binder机制来实现通信时,Binder驱动负责管理数据接收缓存。在Binder驱动中实现了mmap()系统调用。mmap()函数通常是用在物理存储介质的文件系统上,直接将物理内存映射到进程的用户空间上。但是Binder驱动却没有物理介质,它是将自己注册成为了misc device,并且向上层提供一个节点/dev/binder,但是这个binder结点并不与任何的硬件设备相对应。Binder驱动调用mmap()函数不是为了在物理介质和用户空间来做映射,而是用来创建用于数据接收的缓存空间的。使用mmap()函数创建完接收缓存区的映射好后,接收缓存区就可以做为缓存池来接收和存放数据了。

    Binder驱动接收数据包的结构为binder_transaction_data,但这只是消息头,真正的传输数据位于data.buffer所指向的内存,也就是发送数据进程的用户空间的内存中。来接收这块数据的内存不需要接收方提供,而是来自mmap()映射的这片缓存池。在数据从发送方进程向接收方进程发送时,Binder驱动会根据发送数据包的大小,使用最佳匹配算法从缓存池中找到一块大小合适的空间,使用copy_from_user()将数据从发送数据进程的用户空间内复制到这块大小合适的内存空间中。也就是为了实现用户空间到用户空间的拷贝,mmap()分配的物理内存除了映射进了接收方进程里,还映射进了内核空间。这个内核空间也就是Binder驱动根据最佳匹配算法在缓存池中找到的大小合适的空间,所以调用copy_from_user()将数据拷贝进内核空间也相当于拷贝进了接收方的用户空间,这就是Binder只需一次拷贝的‘秘密’。要注意的是,存放binder_transaction_data结构本身的内存空间还是得由接收者提供,但这些数据大小固定,数量也不多,不会给接收方造成不便。

    需要注意的时,上述示意图中的用户空间、内核空间的内存地址都是虚拟内存空间地址,是通过页表这样一个数据结构来映射到物理内存上的,页表就是进程虚拟内存空间地址到物理内存空间地址的映射的数据结构。 上图中内核缓存区、数据接收缓存区和接收数据进程内存放接收数据的内存都映射在了同一块物理内存上。

    首先要理清一个概念:client拥有自己Binder的实体,以及Server的Binder的引用;Server拥有自己Binder的实体,以及Client的Binder的引用。我们也可以从接收方和发送方的方式来理解:

    • 从client向Server发数据:Client为发送方,拥有Binder的实体;Server为接收方,拥有Binder的引用
    • 从server向client发数据:Server为发送方,拥有Binder的实体;client为接收方,拥有Binder的引用

    也就是说,我们在建立了C/S通路后,无需考虑谁是Client谁是Server,只要理清谁是发送方谁是接收方,就能知道Binder的实体和引用在哪边。

    建立CS通路后的流程:

    1. 发送方会通过Binder实体请求发送操作。
    2. Binder驱动会处理这个操作请求,把发送方的数据放入写缓存(binder_write_read.write_buffer) (对于接收方为读缓冲区),并把read_size(接收方读数据)置为数据大小(对于具体的实现后面会介绍);
    3. 接收方之前一直在阻塞状态中,当写缓存中有数据,则会读取数据,执行命令操作
    4. 接收方执行完后,会把返回结果同样用binder_transaction_data结构体封装,写入写缓冲区(对于发送方,为读缓冲区)

    Android为什么要增加Binder方式呢?

    首先看一下Binder与传统IPC的对比

    首先简单描述一下Socket的缺点,Socket是一个通用的接口,传输效率较低开销较大。至于依赖上层协议和访问接入点开放的问题请见于在后面对于共享内存的描述中。

    那么,为什么共享内存控制复杂,易用性差呢?

    这是因为两个进程(或者多个进程)之间发送数据端和接收数据端所使用的是同一块物理内存,所以这要使用信号量等其它手段来保证进程操作的同步与互斥;同时,也极易造成数据的不安全问题;并且通信的进程之间没有客户端与服务端的区别,在使用时比较麻烦。

    至于安全性,当采用Binder机制进行通信的时候,系统会为每个app分配UID,而系统作为管制进程的统治者,会为每个进程分配唯一且真实的UID,当进程出问题时就可以根据这个UID来查找到相应的进程并对其进行处理和管制。而采用共享内存等传统IPC方式进行通信时,进程识别是通过上层协议来实现的,就是进程自己把自己的pid上传给系统进行登记并识别。也就是说进行标记通信进程的是进程自己本身,在标记时,进程本身可能会给出一个错误的进程标记,当出问题时系统就难以找到肇事者,这样就会造成进程的不安全。这就相当于我们在入住酒店进行登记一样,在进行登记时是我们自己在记录本上进行自己信息的登记,而我们自己可能基于某种目的而故意将自己的信息登记错误,这样在出问题的时候,警方就很难根据登记在记录册上的信息来查找到我们。注意,前者是系统给出进程标记的UID,而后者则是进程自己本身得到进程标记,这是造成安全性问题的原因之一。其二,对于传统IPC来说,访问的接入点时开放的,任何知道接入点的对象都可以进行接入,就像公交车一样,我们只要知道站台在哪里,就可以上车;而Binder机制同时支持实名和匿名,就像滴滴叫车服务,我们提供目的地和出发点,系统就会反馈回一个滴滴车主来叫你上车并提供服务,那么反馈滴滴车主的是滴滴系统,只要系统不反馈接入点,那么我们就永远无法上车,这样就保证了系统的安全性,在Binder机制中则相同。

    AIDL

    前面我们说到,安卓的进程通信是通过Binder机制。但是通过Binder机制实现通信的话需要进行一系列的配置,也就是按照Binder的规矩办事才能实现跨进程的通信。而实现Biner机制的代码相对来说较为复杂,那么AIDL就是用来解决这个问题的。AIDL就是调用Binder机制时根据相应的规则通过AIDL工具和AIDL接口来自动生成一定的代码,而我们只需要向其中添加一部分代码就可以实现堆Binder机制的调用。至于AIDL的配置暂且不在这里展开

    那么客户端client是如何通过bindService让服务器Service对象返回IBinder对象的;以及返回IBinder对象之后,客户端client拿到IBinder对象之后又是如何像同进程一样调用的。

    在Client和Service端的AIDL文件中都分别具有Proxy和Stub,用来传输数据使用。Proxy和Stub就相当于是火车站的进站口和出站口,比如广州和深圳。当我们乘坐火车从广州去深圳时,就需要从广州的进站口进入,上车到达深圳站之后从深圳站的出站口出站,返回时则相反,从深圳的进站口进入,从广州的出站口出站。虽然每个火车站都有出站口和进站口,但我们在单趟行程中只使用每个火车站的一个端口。在Client和Service端传送数据时也是相似的道理,每一端都有Proxy和Stub,Proxy和Stub分别相当于进站口和出站口。同样的,进入进站口之后,为方便数据传输,首先对传输的数据进行一次打包,然后就需要登车,经过列车搬运到达目的地,这个登车的过程在机制中的喻体为mRemote.transact(),mRemote对象是在Proxy对象的构造函数中传进来的,为android.os.IBinder类型的对象。经过这个函数之后数据就进入到了Binder,进入到了Binder之后就进入到了底层(c/c++书写完成的)。底层就相当于列车转运,一直把数据转运到了另一端。当列车到站后就需要进行下车操作,下车操作在机制中的喻体为onTransact(),下车后经过switch()语句在多个出站口中选择自己的目的出站口并进入到stub出站口中。

    Binder机制是一种面向对象的IPC机制。

    Binder机制使用C/S通信方式:一个进程作为Server提供诸如视频/音频解码,视频捕获,第hi本查询,网络连接等服务,多个进程作为Client向Server发起服务请求,获得所需要的服务。那么,想要实现C/S通信,必须实现以下两点:一是Server必须有确定的访问接入点或者说地址来接收Client的请求,并且Client可以通过某种途径获取Server的地址;二是指定Command-Replay协议来传输数据。例如在网络通信中Server的访问接入点就是Server主机的IP地址+端口号,传输协议为TCP协议。那么在Binder机制中,Binder实体可以看作是Server提供的实现某个特定服务的访问接入点,Client通过这个“地址”向Server发送请求来使用该服务;对于Client而言,Binder可以看成是通向Server的管道入口,要想和某个Server通信,首先必须建立这个管道并获得管道入口。

    那么Binder机制中面向对象的思想体现在何处呢?

    体现在作为访问接入点的Binder及其在Client的入口中:Binder是一个实体,是位于Server中的对象。这个Bnder实体对象提供了一套方法用以实现对服务的请求,就像类中的成员函数。而遍布于Client中的入口可以看作是指向这个Binder对象的“指针”(为了方便理解,这里暂且称为指针),一旦获得了这个“指针”就可以调用该Binder实体对象的方法来访问Server服务。而在Client看来,尽管Binder对象的实体位于远端Server中,但是通过指向这个Binder实体对象的“指针”来调用Binder对象中提供的方法和通过指针来调用其它任何本地对象(如函数指针来是实现回调)的方法并无区别。这个指针也可以使用“句柄”来描述Binder对象在Client中的存在方式,句柄就比如文件描述符。

    如上段所术,面向对象的思想使得将进程间通信转化为通过某个Binder对象的引用(就是上段所说的Binder对象的指针)来实现对该对象中方法的调用,但是其独特之处在于Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程,而对这个Binder对象的引用却遍布于系统的各个进程之中(并不是每个进程之中都有,而是通过SM与该Servr建立C-S的进程才有)。这个对Binder对象引用可以是强类型,也可以是弱类型,并且可以从一个进程传递给其他进程以便于大家都能够访问到同一个Server,这就像将一个对象或引用赋值給另一个引用一样。从这个角度来看,Binder模糊了进程边界,淡化了进程间通信进程,仿佛运行在同一个面向对象的程序之中。形形色色的Binder对象以及星罗棋布的引用仿佛粘接各个程序的胶水。

    值得注意的是,这里虽然是使用面向对象的思想,但是其底层实现都是C语言实现,并没有类和对象的概念。

    Binder通信模型

    Binder框架定义了四个角色:Server,Client, ServiceManager以及Binder驱动。其中Server, Client和SM均运行在用户空间,驱动运行于内核空间。

    Binder驱动

    和路由器一样,Binder虽然默默无闻,却是Binder机制通信的核心,尽管名叫驱动,但是与硬件设备无任何联系,只是实现方式和设备驱动程序一样。工作与内核空间,负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。

    Server

    在Server中创建了Binder实体,为其取一个字符形式,可读易记的名字。将这个Binder实体连同名字以数据包的形式通过Binder驱动发送给SM,用于通知SM进行注册,注册名为Binder名,位置位于Server中。驱动会为这个Binder实体对象创建位于内核空间中的实体节点以及SM对实体的引用,将Binder实体名字以及新建的引用打包发给SM。SM收取到数据包后,从中取出名字和引用填入一张查找表中。例如,Server1中有一个实体张三,那么在SM中的注册为 Binder名:张三,位于Server1中。

    注意,上述的Binder实体并没有发送给SM,是将实体传递到了Binder驱动之中,Binder驱动会为这个Binder实体在内核空间中创建相对应的实体结点和SM对于这个实体的引用,Binder实体中只是止步于内核空间。同时传递给Binder驱动的还有Binder实体的名字,Binder驱动在内核空间中创建完成后将这个Binder的名字和新建的引用发送给SM。SM收到数据包之后,就从中取出Binder的名字和引用并将其填入一张查找表中,这张查找表如同页表一样,也只是体现了一种映射关系。

    前面我们提到,Binder驱动为Server中的Binder实体创建一个位于内核空间中的实体结点,属于mRemote类型对象。驱动中创建的Binder实体也叫‘节点’,隶属于提供实体的进程,由struct binder_node结构来表示。每个进程都有一棵红黑树用于存放创建好的节点,以Binder在用户空间的指针作为索引。每当在传输数据中侦测到一个代表Binder实体的 flat_binder_object,先以该结构的binder指针为索引搜索红黑树;如果没找到就创建一个相应的新节点添加到该树中。由于对于同一个进程来说内存地址是唯一的,所以不会重复建设造成混乱。

    和Binder实体一样,Binder的引用也是驱动根据传输数据中的flat_binder_object创建的,隶属于获得该引用的进程,也就是获得该引用的Client,用struct binder_ref结构体表示。

    就象一个对象有很多指针一样,同一个Binder实体可能有很多引用,不同的是这些引用可能分布在不同的进程中。和实体一样,每个进程使用红黑树存放所有该进程正在使用的引用。但Binder的引用可以通过两个键值索引:

    • 对应Binder节点在内核中的地址。注意这里指的是驱动创建于内核中的binder_node结构的地址,而不是Binder实体在用户进程中的地址。Binder节点在内核中 的地址是唯一的,用做索引不会产生二义性;但Binder实体可能来自不同用户进程,而Binder实体在不同用户进程中的地址可能重合,不能用来做索引。驱动利用该红黑树在一个 进程中快速查找某个Binder实体所对应的引用(一个实体在一个进程中只建立一个引用)。
    • 引用号。引用号是驱动为引用分配的一个32位标识,在一个进程内是唯一的,而在不同进程中可能会有同样的值,这和进程的打开文件号很类似。引用号将返回给应用程序,可以看作Binder引用在用户进程中的句柄。除了0号引用在所有进程里都保留给SM,其它值由驱动在创建引用时动态分配。向Binder 发送数据包时,应用程序通过将引用号填入binder_transaction_data结构的target.handle域中表明该数据包的目的 Binder。驱动根据该引用号在红黑树中找到引用的binder_ref结构,进而通过其node域知道目标Binder实体所在的进程及其它相关信息,实现数据包的路由。

    SM与实名Binder

    SM作用

    1. Server注册服务。Server作为众多Service的拥有者,当它想向Client提供服务时,得先去Service Manager那儿注册自己的服务。Server可以向SM注册一个或多个服务。
    2. Client申请服务。Client作为Service的使用者,当它想使用服务时,得向SM申请自己所需要的服务。Client可以申请一个或多个服务

    当Client申请服务成功后,Client就可以使用服务了。

    SM一方面管理Server所提供的服务,同时又响应Client的请求并为之分配相应的服务。扮演的角色相当于月老,两边牵线。这种通信方式的好处是:一方面,service和Client请求便于管理,另一方面在应用程序开发时,只需为Client建立到Server的连接,就可花很少时间和精力去实现Server相应功能。

    SM的作用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder实体的引用,从而获得Client想要的服务。注册了名字的Binder叫实名Binder,就象每个网站除了有IP地址外还有自己的网址。

    那么,可以发现,SM是一个进程,运行在用户空间,而Server也是一个进程,那么将Server中的Binder名字以发送给SM进行注册时本身就是一个进程间通信,那么这个发送Binder名字的目的也是在于实现进程间通信,这样一来就相当于鸡生蛋,蛋生鸡的循环之中。要想实现这个循环,至少先有其一,在Binder机制中式怎么实现这个呢?

    如果把SM看作Server端,让它在Binder驱动一运行起来时就有自己的Binder实体(代码中设置ServiceManager的Binder其handle值恒为0)。这个Binder实体没有名字也不需要注册,所有的client都认为handle值为0的binder引用是用来与SM通信的(代码中是这么实现的),那么这个问题就解决了。那么,Client和Server中这么达成协议了(handle值为0的引用是专门与SM通信之用的),还不行,还需要让SM有handle值为0的实体才算大功告成。怎么实现的呢?!当一个进程调用Binder驱动时,使用BINDER_SET_CONTEXT_MGR命令(在驱动的binder_ioctl中)将自己注册成SM时,Binder驱动会自动为它创建Binder实体。这个Binder的引用对所有的Client都为0。

    Client获取Biner实名的引用

    Server向SM注册了Binder实体及其名字后,Client就可以向SM发送包含Binder名字的数据包获得该Binder的引用了,Client也利用保留的handle值为0的引用向SM请求访问某个Binder。SM收到这个服务连接请求之后,从Client的请求数据包里获得Binder的名字,在查找表里找到该名字对应的条目,从条目中取出Binder的引用,将该引用作为回复发送给发起请求的Client。从面向对象的角度,这个Binder对象现在有了两个引用:一个位于SMgr中,一个位于发起请求的Client中。如果接下来有更多的Client请求该Binder,系统中就会有更多的引用指向该Binder,就象java里一个对象存在多个引用一样。而且类似的这些指向Binder的引用是强类型,从而确保只要有引用Binder实体就不会被释放掉。通过以上过程可以看出,SMgr象个火车票代售点,收集了所有火车的车票,可以通过它购买到乘坐各趟火车的票-得到某个Binder的引用。

    匿名Binder

    并不是所有Binder都需要注册给SM广而告之的。Server端可以通过已经建立的Binder连接将创建的Binder实体传给Client,当然这条已经建立的Binder连接必须是通过实名Binder实现。由于这个Binder没有向SM注册名字,所以是个匿名Binder。Client将会收到这个匿名Binder的引用,通过这个引用向位于Server中的实体发送请求。匿名Binder为通信双方建立一条私密通道,只要Server没有把匿名Binder发给别的进程,别的进程就无法通过穷举或猜测等任何方式获得该Binder的引用,向该Binder发送请求。

    首先我们调用bindService去调用ServiceManager对象,返回IBinder对象,也就是AMS服务。具体流程如上图。

    AMS也是系统服务,全称是ActivityManagerService,是四大组件管理服务。

    其中可能存在错误,理解正在进一步加深。

    参考:https://www.xuebuyuan.com/3263204.html

  • 相关阅读:
    P1093 奖学金
    华容道
    回文数
    P1654 OSU!
    Noip P1063 能量项链
    Noip 寻宝
    NOIP 2009 普及组 第三题 细胞分裂
    拦截器
    OGNL
    Struts2 配置详解
  • 原文地址:https://www.cnblogs.com/hxhlrq/p/14154387.html
Copyright © 2011-2022 走看看