美文网首页Android开发Android技术知识Android科普知识库
万变不离其宗|跨进程通信破障一击

万变不离其宗|跨进程通信破障一击

作者: 蚍蜉一生 | 来源:发表于2024-07-21 20:18 被阅读0次

    提到跨进程通信,你会想到什么?

    Socket、管道、共享内存、Binder、消息队列、信号量……

    如果让你讲讲他们如何使用以及背后的原理,是不是瞬间头大——

    那再让你设计一套跨进程通信机制,你是不是想说 :“ 啊 ~ 啊~ 我就是个废物,求放过!”

    其实,很多时候我们觉得一个事物很难理解,只是因为没有了解它出现主要背景;

    若了解了一个事物出现的主要背景,脑子稍微转一下,你也可以是那个伟大的创造者——

    遇到难点不要着急,了解下它的子概念历史,背景,充分理解它是在什么场景下解决什么问题的方案

    当你了解一个事物的本质和真面目,那么浮在它之上的各种概念和方法就会触类旁通、一览无余——

    回归正题,我们想理解跨进程通信,那就要搞清楚它是什么?

    跨进程通信,这五个字包含了两个子概念:进程和通信,

    了解了通信的本质,掌握了进程的设计初衷和运行机制,我们才能正确理解“跨进程通信”

    首先我们一起探究下通信的本质

    通信简化来看就是一方发送信息,一方接收信息

    具体来说就是一方把信息放到一个地方,一个人去这个地方获取这个信息

    双方要想通信就要协商好一个双方都可以触及的空间(或者事物),这里的触及有两方面的含义

    第一,双方都能够找到这个空间(或事物)

    第二,发送方有权限、有能力把信息投放到这个空间(或事物)中;接收方有权限,有能力从这个空间(或事物)中去获取信息。

    仔细想一想,通信是不是这样呢……

    有人给你写信,需要知道你家在那里,还要有权限把信放到你家邮箱中

    你在山里大声呼叫你的同伴,一是能够知道你同伴所在的空间范围 第二 保证你的声音能在这个范围内传播,这样你同伴才能接收到你的呼唤~

    你给一个女孩子打电话,不仅要知道她的电话号,还要她没有把你拉黑,有把你声音展示在她耳朵旁边空间的权限,你们才能正常交流~

    接下来 谈谈进程——

    进程是计算机世界中资源管理的基本单位,每一个进程都在独立的内存空间空运行,与其他进程隔离

    跨进程通信

    如上图,当进程运行的时候,他们之间是相互隔离的。

    就是说进程1,是不知道进程2中任何变量的地址,也没有权限去获取它们的值,

    这就好比,你不知道邻居家钱藏在哪里,也没有权限去拿到钱一样~

    若你再问进程为啥要相互隔离呢,我只能说制度设计如此,为了整个系统更加高效安全稳定地运行……

    虽然进程之间是相互隔离的,但是每个进程跟内核是联通的,他们之间是间接联接在一起的——

    也就是说:跨进程通信一定是要通过内核,受到内核的监管和限制!

    接下来,我们再回到通信本质——通信就是:一方把信息投放在一个地方,另一个方能找到这个地方,并且能够有权限把信息从这里取走!

    按照这个思路,我们可以瞬间想到无数种跨进程通信方案——

    比如一,信息从进程1的(用户)内存空间复制到内核内存某个位置,进程2去内核空间这个位置去取这个数据;

    比如二,信息从进程2的(用户)内存空间通过内核输出到磁盘上某个地址上,进程2从磁盘上这个位置去读这个数据;

    比如三,信息从进程1的内存空间,通过内核发送到系统某个内核缓存队列上,进程2从这个缓存中取这个数据,如果不跨设备,这个本质上也是通过内核内存作为中转通信;

    比如四、信息从进程1的内存空间,通过内核输出到某个外设,比如打印机上,打印一个二维码,进程2也通过内核调用摄像头拍摄这个二维码获取到这个信息……

    是不是思路打开了~

    只要找到一个通信双方都能触及,且能承载信息的空间或事物,两个进程便可以通信!

    基于这个理论,构建一个基础的跨进程通信方案,大概分为三步:

    • 协商信息交流地址:确定一个地址,然后把这个地址给到通信双方;

    • 打通信息交流通道:为发送方构建投放信息的通道,将信息放到上一步确定的地址上;为接收方构建获取信息能力,能够从上一步确定的地址上获取信息;

    • 进行信息安全管理:通信一般是保密的,为了做到这个 ,一般有三种做法:1) 信息交换地址只有通信双方知道;2) 来这个地址存放信息时候进行身份验证 ;3)通信信息加密。(如果是在同一个系统内部,大部分跨进程方案并没有进行信息安全管理)

    了解跨进程通信的原理和基本步骤,我们现在再来看下目前比较流行的跨进程通信方案~

    放心!肯定事半功倍,一路平推!!

    首先看Socket——

    Socket是CS架构,C是客户端,S是服务端,客户端负责发送数据,服务端负责不停地接收数据~

    双方要协商好一个文件名,当打开这个文件的时候,这个文件名就在内核中有一个一一对应且系统全局唯一的文件描述符,

    这个文件描述符就可以作为一个内核缓冲队列的标识

    所以我们可以说文件名一一对应内核缓冲区

    我们把协商好的文件名告诉客户端和服务端,那他们都能根据这个文件名去找到内核缓冲区了

    接下来就是客户端往该内核缓冲区投放数据,服务端则不停的去这个内核缓冲区取数据,通了!通了!

    在上述过程中,要注意一点,两个进程需要能够打开这个文件才能获取到文件描述符,所以文件名不仅用来标识缓冲区,

    还可以通过文件名来控制对缓冲区的访问权限,信息安全管理,搞定!

    地址、通道、信息安全全部搞定,Socket通信也就没有障碍了,接下来就可以查阅各种博客,进入实操了~

    其次看下共享内存

    共享内存,顾名思义就是多个进程共同拥有一块内存,发送方把信息投放在这快内存上,接收方也从该内存区域取走信息,以此进行跨进程通信~

    继续我们地址、通道、信息安全三板斧研究法……

    共享的东西当然需要放到公共区域——内核内存区域

    操作系统内核都提供创建共享内存的方法,一般是需要传入一个唯一Id、共享内存大小、对共享内存的权限;

    注意这个唯一Id,这个id相当于这块共享内存的地址的标记

    通信双方需要拿到这个id,才能找到这块共享内存

    这个Id呢,就需要提前约定或者通过其他跨进程通信方式进行传递

    我们通过这个id,调用内核方法来创建一块共享内存,内核方法会返回一个地址值,将这个地址存储在本进程的一个内存变量中,这就叫做内存映射!

    内存映射本质 就是把数据源的地址(这个可以是内核内存、也可以是磁盘其他等等) 放到当前用户内存空间中。

    当进程使用该地址上数据时候,系统会根据这个地址找到真正数据位置,并进行读写。

    它类似于我们常说的对象引用,不过对象引用是在一个进程内,无需操作内核转换就直接能够根据引用的地址找到对象所在,而内存映射是跨进程寻找,需要内核帮忙转换成真实物理地址寻找~

    对象引用就好比一个村里,大家都认识,你妈让你去买只鸡,只要告诉你是去张三还是李四家你就能自己找到,这里的张三家、李四家就是一个虚拟地址~;内存映射,好比 你老板说让你去美国马斯克家买一个火箭,你就必须想办法把马斯克家换成一个真实的坐标,然后才能去这个真实坐标处找到他买火箭~

    完成这内存映射后,在进程看来,这块区域就像自己内存一样随意读写~

    哎,这么简单吗?这就完成了吗?

    不要忘记还有一板斧,信息安全,信息安全包括数据一致性和数据隐私保护两个方面,这也是使用共享内存的难点~

    数据一致性方面一般是使用跨进程的互斥锁,读写锁、信号量等手段来保证同步机制,

    毫无疑问,这些跨进程的锁,肯定也是通过内核实现,比如互斥锁其实还是一个共享内存变量,

    使用同步机制就要考虑死锁、锁的生成和回收时机,有点麻烦~

    数据隐私保护,包含访问控制和数据加密,它们都是备选的,这里不展开~

    我们只需要知道,如果需要这两个都需要进程自己做,而跨进程的访问控制和数据加密又是另外一些难点~

    所以说 内存共享原理简单、性能优越,但是需要自己搞定的事情有点多,使用起来有点麻烦~

    最后谈一谈Binder

    Binder是一个Android系统内部高效、安全的跨进程通信方案

    Binder通信整体流程类似于Socket通信,但是利用了内存映射提升读写速度,外加一个全局资源管理器——Binder驱动,主要负责安全和负载

    Binder原理

    我们之前讲过,通信的本质在于发送方向一个地方投放信息,接收方从这个地方取信息,Socket如此、Binder也是如此

    在Binder通信中,服务端创建Binder的时候,就会根据包名、描述符生成一个唯一标识符(可以看下AIDL生成的Binder源码,都包含这个),这个标识符就会对应了 一个内核地址

    客户端要么同样根据包名,描述符得到这个唯一标识符,要么去ServiceManager这个公示板去查一些系统服务的标识符,

    拿到这个标识符,就能通过Binder驱动跟这个标识符对应的地址沟通

    但不同于socket,Binder服务端并不是把这个内核地址中的信息复制到用户空间,而是采用内存映射,在自己用户空间中存储了这个内核区域的地址

    服务端读写这部分内存的时候,系统会根据地址直接到内核中操作——

    嗯,Binder的这个不错,结合了socket和共享内存的优点,不过别着急,精彩继续——

    回想下Socket,客户端和服务端协商好一个地方后,客户端就向这边存信息,服务端则要起一个线程,这个线程就一直等着,如果有信息,内核就会把它唤醒,它就开始取信息,但如果信息多就慢慢来了,还有如果这个协商的地方被别人知道了,谁都可以来读写数据,数据安全性需要靠其他手段来保证,比如加密~

    鉴于此,Binder机制增加了一个全局管理员——Binder驱动

    Binder驱动是内核的一部分,所有通过Binder机制通信的都要经过它,它知道所有进程信息和他们发送的消息——

    Binder驱动就像一个邮局一样,谁来发信息、都要拿好身份证、它要检查进程UID/PID等信息,根据地址标识找到接收方,通知接收方用多少个线程(唤醒+负载控制)来获取信息,具体展开如下:

    1. 客户端向通过Binder驱动向服务端发信息,Binder驱动首先校验进程身份;

    2. 身份校验通过后,Binder驱动就根据客户端携带的唯一标识符来检索属于哪个服务端以及对应的内核缓冲区;

    3. 将信息发送到唯一标识对应的服务端的内核缓冲区中;

    4. 通知服务端,有个客户端(pid/uid)是什么给你发信息了,信息量有多少,大概需开辟N个线程来处理;

    5. 服务端根据Binder驱动指示开辟并唤醒N个线程来处理信息,处理后Binder驱动会把结果给到客户端。

    看起来挺复杂,但是1-5都是Android系统的Binder机制完成,我们只需要根据AIDL这个格式定义好信息发送内容和服务端的信息处理方法即可,非常的简便易用,安全高效。

    最后再强调一点,Binder是一个Android系统内部高效、安全的跨进程通信方案,这是因为如果跨系统就很难构建一个像系统内核一样了解全局信息的机构,但还是可以借鉴它的思想,比如负载均衡系统的构建~

    按照我们的地址、通道、安全三板斧理解,其他几个跨进程通信方案也没有太大难度,万变不离其宗,这里就不再赘述,Binder机制比较复杂,我们后续会继续从如何构建一个完美的跨进程通信方案来尝试聊聊Binder的细节,拜拜~

    相关文章

      网友评论

        本文标题:万变不离其宗|跨进程通信破障一击

        本文链接:https://www.haomeiwen.com/subject/xyfehjtx.html