IPC与Binder,Binder线程池,AIDL demo - Android

一篇文章了解相见恨晚的 Android Binder 进程间通讯机制- https://blog.csdn.net/freekiteyu/article/details/70082302

-- 为什么 Android 要采用 Binder 作为 IPC 机制?https://www.zhihu.com/question/39440766/answer/89210950

AIDL中支持的数据类型有:Java基本类型,String,CharSequence, List, Map,其他AIDL定义的AIDL接口,实现Parcelable的类

  binder底层是共享内存方式。基于aidl远程服务本质上沿袭了分布式计算的原理。

-- Linux现有的所有进程间IPC方式:
 1. 管道:在创建时分配一个page大小的内存,缓存区大小比较有限;
 2. 消息队列:信息复制两次,额外的CPU消耗;不合适频繁或信息量大的通信;
 3. 共享内存:无须复制,共享缓冲区直接付附加到进程虚拟地址空间,速度快;但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决;
 4. 套接字:作为更通用的接口,传输效率低,主要用于不通机器或跨网络的通信;
 5. 信号量:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
 6. 信号: 不适用于信息交换,更适用于进程中断控制,比如非法内存访问,杀死某个进程等;

-- 从5个角度来展开对Binder的分析:
(1)从性能的角度 数据拷贝次数:Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,但共享内存方式一次内存拷贝都不需要;从性能角度看,Binder性能仅次于共享内存。
(2)从稳定性的角度Binder是基于C/S架构的,简单解释下C/S架构,是指客户端(Client)和服务端(Server)组成的架构,Client端有什么需求,直接发送给Server端去完成,架构清晰明朗,Server端与Client端相对独立,稳定性较好;而共享内存实现方式复杂,没有客户与服务端之别, 需要充分考虑到访问临界资源的并发同步问题,否则可能会出现死锁等问题;从这稳定性角度看,Binder架构优越于共享内存。仅仅从以上两点,各有优劣,还不足以支撑google去采用binder的IPC机制,那么更重要的原因是:
(3)从安全的角度传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Android作为一个开放的开源体系,拥有非常多的开发平台,App来源甚广,因此手机的安全显得额外重要;对于普通用户,绝不希望从App商店下载偷窥隐射数据、后台造成手机耗电等等问题,传统Linux IPC无任何保护措施,完全由上层协议来确保。 Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志,前面提到C/S架构,Android系统中对外只暴露Client端,Client端将任务发送给Server端,Server端会根据权限控制策略,判断UID/PID是否满足访问权限,目前权限控制很多时候是通过弹出权限询问对话框,让用户选择是否运行。Android 6.0,也称为Android M,在6.0之前的系统是在App第一次安装时,会将整个App所涉及的所有权限一次询问,只要留意看会发现很多App根本用不上通信录和短信,但在这一次性权限权限时会包含进去,让用户拒绝不得,因为拒绝后App无法正常使用,而一旦授权后,应用便可以胡作非为。
 (4)从语言层面的角度大家多知道Linux是基于C语言(面向过程的语言),而Android是基于Java语言(面向对象的语句),而对于Binder恰恰也符合面向对象的思想,将进程间通信转化为通过对某个Binder对象的引用调用该对象的方法,而其独特之处在于Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,而它的引用却遍布于系统的各个进程之中。可以从一个进程传给其它进程,让大家都能访问同一Server,就像将一个对象或引用赋值给另一个引用一样。Binder模糊了进程边界,淡化了进程间通信过程,整个系统仿佛运行于同一个面向对象的程序之中。从语言层面,Binder更适合基于面向对象语言的Android系统,对于Linux系统可能会有点“水土不服”。
  Android OS中的Zygote进程的IPC采用的是Socket(套接字)机制,Android中的Kill Process采用的signal(信号)机制等等。而Binder更多则用在system_server进程与上层App层的IPC交互。
  (5) 从公司战略的角度总所周知,Linux内核是开源的系统,所开放源代码许可协议GPL保护,该协议具有“病毒式感染”的能力,怎么理解这句话呢?受GPL保护的Linux Kernel是运行在内核空间,对于上层的任何类库、服务、应用等运行在用户空间,一旦进行SysCall(系统调用),调用到底层Kernel,那么也必须遵循GPL协议。
  而Android 之父 Andy Rubin对于GPL显然是不能接受的,为此,Google巧妙地将GPL协议控制在内核空间,将用户空间的协议采用Apache-2.0协议(允许基于Android的开发商不向社区反馈源码),同时在GPL协议与Apache-2.0之间的Lib库中采用BSD证授权方法,有效隔断了GPL的传染性,仍有较大争议,但至少目前缓解Android,让GPL止步于内核空间,这是Google在GPL Linux下 开源与商业化共存的一个成功典范。

-- 通过Binder传递的对象有两种,Parcel和 IBinder。
  Binder Server 的实现是线程池的方式,而不是单线程队列的方式,区别在于,单线程队列的话,Server 的代码是线程安全的,线程池的话,Server 的代码则不是线程安全的,需要开发者自己做好多线程同步。
  Binder IPC 属于 C/S 架构,包括 Client、Driver、Server 三个部分;
  Client 可以手动调用 Driver 的 transact 接口,也可以通过 AIDL 生成的 Proxy 调用;
  Server 中会启动一个「线程池」来处理 Client 的调用请求,处理完成后将结果返回给 Driver,Driver 再返回给 Client。
 -- ContentProvider 中的 CRUD 是否是线程安全的?又例如在使用 AIDL 的时候,在 Service 中实现的接口是否是线程安全的?
  这里就回答了开篇提问的两个问题:Service 中通过 AIDL 提供的接口并不是线程安全的,同理 ContentProvider 底层也是使用 Binder,同样不是线程安全的,至于是否需要做多线程保护,看业务而定,最好是做好多线程同步,以防万一。

> Android中mmap原理及应用简析-  https://juejin.im/post/5cd239116fb9a0321d73c127
 mmap是Linux中常用的系统调用API,用途广泛,Android中也有不少地方用到,比如匿名共享内存,Binder机制等。
 mmap属于系统调用,用户控件间接通过swi指令触发软中断,进入内核态(各种环境的切换),进入内核态之后,便可以调用内核函数进行处理。 mmap->mmap64->__mmap2->sys_mmap2-> sys_mmap_pgoff ->do_mmap_pgoff.
 Binder机制中mmap的最大特点是一次拷贝即可完成进程间通信。Android应用在进程启动之初会创建一个单例的ProcessState对象,其构造函数执行时会同时完成binder mmap,为进程分配一块内存,专门用于Binder通信。
 Linux的内存分用户空间跟内核空间,同时页表有也分两类,用户空间页表跟内核空间页表,每个进程有一个用户空间页表,但是系统只有一个内核空间页表。而Binder mmap的关键是:也更新用户空间对应的页表的同时也同步映射内核页表,让两个页表都指向同一块地址,这样一来,数据只需要从A进程的用户空间,直接拷贝拷贝到B所对应的内核空间,而B多对应的内核空间在B进程的用户空间也有相应的映射,这样就无需从内核拷贝到用户空间了。
  binder一次拷贝的关键是,完成内存的时候,同时完成了内核空间跟用户空间的映射,也就是说,同一份物理内存,既可以在用户空间,用虚拟地址访问,也可以在内核空间用虚拟地址访问。

  普通文件的访问方式有两种:第一种是通过read/write系统调访问,先在用户空间分配一段buffer,然后,进入内核,将内容从磁盘读取到内核缓冲,最后,拷贝到用户进程空间,至少牵扯到两次数据拷贝;同时,多个进程同时访问一个文件,每个进程都有一个副本,存在资源浪费的问题。

- 普通文件mmap原理
普通文件的访问方式有两种:
  第一种是通过read/write系统调访问,先在用户空间分配一段buffer,然后,进入内核,将内容从磁盘读取到内核缓冲,最后,拷贝到用户进程空间,至少牵扯到两次数据拷贝;同时,多个进程同时访问一个文件,每个进程都有一个副本,存在资源浪费的问题。
  另一种是通过mmap来访问文件,mmap()将文件直接映射到用户空间,文件在mmap的时候,内存并未真正分配,只有在第一次读取/写入的时候才会触发,这个时候,会引发缺页中断,在处理缺页中断的时候,完成内存也分配,同时也完成文件数据的拷贝。并且,修改用户空间对应的页表,完成到物理内存到用户空间的映射,这种方式只存在一次数据拷贝,效率更高。同时多进程间通过mmap共享文件数据的时候,仅需要一块物理内存就够了。

- 共享内存中mmap的使用
共享内存是在普通文件mmap的基础上实现的,其实就是基于tmpfs文件系统的普通mmap。

--  Android Linux进程间通讯:
 1.Linux的虚拟内存机制导致内存的隔离,进而导致进程隔离
 2.进程隔离的出现导致对内存的操作被划分为用户空间和内核空间
 3.用户空间需要跨权限去访问内核空间,必须使用系统调用去实现
 4.系统调用需要借助内核模块/驱动去完成
前三步决定了进程间通讯需要借助内核模块/驱动去实现,而Binder驱动就是内核模块/驱动中用来实现进程间通讯的

Linux提供有管道、消息队列、信号量、内存共享、套接字等跨进程方式.

-- Binder对象和Binder驱动
 Binder对象:Binder机制中进行进程间通讯的对象,对于Service端为Binder本地对象,对于Client端为Binder代理对象;
 Binder驱动:Binder机制中进行进程间通讯的介质,Binder驱动会对具有跨进程传递能力的对象做特殊处理,自动完成代理对象和本地对象的转换。
 由于Binder驱动会对具有跨进程传递能力的对象做特殊处理,自动完成代理对象和本地对象的转换,因此在驱动中保存了每一个跨越进程的Binder对象的相关信息,Binder本地对象(或Binder实体)保存在binder_node的数据结构,Binder代理对象(或Binder引用/句柄)保存在binder_ref的数据结构
 BinderProxy的transact方法是native方法,它的实现在native层,它会去借助Binder驱动完成数据的传输,当完成数据传输后,会唤醒Server端,调用了Server端本地对象的onTransact函数。

- Android中要实现IPC,有好多种方式:
1.在Intent中附加extras来传递信息。

2.共享文件。

3.SharedPreferences(不建议在进程间通信中使用,因为在多进程模式下,系统对SharedPreferences的读/写会变得不可靠,面对高并发的读/写访问,有很大几率会丢失数据)。

4.基于Binder的AIDL。

5.基于Binder的Messenger(翻译为“信使”,其实Messenger本质上也是AIDL,只不过系统做了封装以方便上层调用)。
6.Socket。

7.天生支持跨进程访问的ContentProvider。

-- Binder线程池,Android Binder 连接池;  AIDL demo
Binder线程池的主要作用就是将每个业务模块的Binder请求统一转发到远程Servie中去执行,从而避免了重复创建Service的过程。
https://github.com/breezehan/AndroidKnowledgeSummary/blob/master/ipc/src/main/java/com/breezehan/ipc/binderpool/BinderPool.java
Binder线程池- https://blog.csdn.net/hds2011/article/details/77428640

IPC与Binder,Binder线程池,AIDL demo - Android_第1张图片

IPC与Binder,Binder线程池,AIDL demo - Android_第2张图片

-- Binder IPC原理

IPC与Binder,Binder线程池,AIDL demo - Android_第3张图片

-- Binder架构

IPC与Binder,Binder线程池,AIDL demo - Android_第4张图片

-- Android Binder IPC(进程间通信)的过程

IPC与Binder,Binder线程池,AIDL demo - Android_第5张图片


 

你可能感兴趣的:(Android工作机制,原理及进阶)