首页 文章 精选 留言 我的

精选列表

搜索[图像理解],共10000篇文章
优秀的个人博客,低调大师

Android - 从浅到懂理解 Binder

文章目录 背景 为什么需要跨进程通信(IPC) 为什么是Binder? 用户空间/内核空间 系统调用:内核态/用户态 传统 IPC 的原理 内核模块 / "驱动" Binder IPC 机制实现原理 总结 参考 背景 在插件化使用时,进程间通信使用了AIDL进行跨进程通信,而AIDL底层的实现是使用Binder机制。 在深入了解了AIDL之后,我们还需要再深入学习Binder。 为什么需要跨进程通信(IPC) 一个进程一般是对应一个App,你不会希望别的进程(App)能够轻而易举的能操作你的App吧,所以你的App只能访问App内部的数据。 但是有些场景是需要通过一个App去操作另一个App的,比如:从App中调用系统的文件管理,实现文件读写。比如从App中读取手机通信录的联系人信息。这种情况就需要实现进程间通信了。 为什么是Binder? Android 使用的 Linux 内核拥有着非常多的跨进程通信机制,比如:管道,消息队列,共享内存,System V,Socket等; 那么Android系统中的Binder究竟有何过人之处呢? 上述的进程间通信存在的问题: Socket 作为一款通用接口,其传输效率低,开销大,主要用在跨网络的进程间通信和本机上进程间的低速通信。 消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。 共享内存虽然无需拷贝,但控制复杂,难以使用。 传统IPC没有任何安全措施,完全依赖上层协议来确保。 Binder 的优势是:性能、稳定、安全。 性能 | IPC方式 | 数据拷贝次数| | ---- | ---- | | 共享内存 | 0 | | Binder | 1 | | Socket/管道/消息队列 | 2 | 稳定 Binder是基于C/S架构。通过客户端(Client)给服务端(Server)发送指令而服务端根据指令返回数据的方式实现。 职责明确且互相独立,因此不易出错稳定性高。 安全 传统Linux IPC的接收方无法获得发送方进程可靠的UID/PID,从而无法鉴别对方身份; 而Android作为一个开源系统,拥有非常多的开发平台,App来源甚广,因此手机的安全显得额外重要; 对于普通用户,绝不希望从商店下载的App能偷窥隐私数据、后台造成手机耗电等等问题。 Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志 而Binder通信可以获得通信进程的UID,有了UID就可以鉴别进程的身份。 同时 Binder 支持实名 Binder, 保证了安全性。 在分析性能时,谈到了App数据缓存区和内核缓存区。 App数据缓存区用于进程间数据隔离,而内核缓存区的数据是可以共享的。 为了进一步了解进程间通信,我们还需要去了解 用户空间/内核空间 内核态/用户态 内核模块/驱动 用户空间/内核空间 内核空间(Kernel Space)是系统内核运行的空间 用户空间(User Space)是用户程序运行的空间。 为了保证安全性,它们之间是隔离的。但是有的时候用户空间是需要去访问内核空间的。 比如:文件读写操作。 而用户空间访问内核空间的唯一方式就是系统调用。 系统调用:内核态/用户态 Linux 使用两级保护机制:0 级供系统内核使用,3 级供用户程序使用。 通过系统调用这个统一入口接口,所有的资源访问都是在内核的控制下执行,以免导致对用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。 当一个进程执行系统调用而陷入内核代码中执行时,我们就称进程处于内核态。此时处理器处于特权级最高的(0级)内核代码中执行 当进程在执行用户自己的代码时,则称其处于用户态。此时处理器在特权级最低的(3级)用户代码中运行。 系统调用主要通过如下两个函数来实现: copy_from_user() //将数据从用户空间拷贝到内核空间 copy_to_user() //将数据从内核空间拷贝到用户空间 传统的IPC就是使用上述两个系统调用的方法来实现进程间通信 。 传统 IPC 的原理 消息发送方将要发送的数据存放在用户的内存缓存区中,通过系统调用进入内核态。 内核程序在内核空间开辟一块内核缓存区,操作系统调用copy_from_user() 函数将数据从用户空间的内存缓存区拷贝到内核空间的内核缓存区中。 接收方进程在自己的用户空间开辟一块内存缓存区,然后内核程序调用copy_to_user() 函数将数据从内核缓存区拷贝到接收进程的内存缓存区。 这样数据发送方进程和数据接收方进程就完成了一次数据传输,也就是进程间通信。 内核模块 / “驱动” 通过系统调用,用户空间可以访问内核空间, 那么如果一个用户空间想与另外一个用户空间进行通信怎么办呢? 很自然想到的是让操作系统内核添加支持; 传统的Linux通信机制,比如Socket, 管道等都是内核支持的; 但是Binder并不是Linux内核的一部分,它是怎么做到访问内核空间的呢? Linux的动态可加载内核模块(Loadable Kernel Module,LKM)机制解决了这个问题; 该模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。 它在运行时被链接到内核作为内核的一部分在内核空间运行。 Android 系统通过添加一个内核模块运行在内核空间,用户进程之间通过这个模块作为桥梁,就可以完成通信。 在Android系统中,这个运行在内核空间的,负责各个用户进程通过 Binder通信的内核模块叫做Binder驱动; TIP: 驱动程序一般指的是设备驱动程序(Device Driver),是一种可以使计算机和设备通信的特殊程序。 相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作; 驱动就是操作硬件的接口,为了支持Binder通信过程,Android通过软件层面实现的Binder驱动,它类似于硬件接口用于和内核交互。 因此这个模块被称之为驱动。 前面说到了Binder的数据拷贝只有一次,而传统的IPC除了共享文件外都是最少两次数据拷贝 那么Binder驱动是如何实现的呢? Binder IPC 机制实现原理 有点深奥,晦涩难懂。以后慢慢啃 Binder IPC 机制中实现数据拷贝仅一次的原理是用到了内存映射。数据拷贝是在 内存映射: 首先映射是指建立一种关系,而这里的内存映射顾名思义就是将用户空间的一块内存区域映射到内核空间。在映射的过程中数据并没有拷贝,只是双方建立了链接。这个链接在物理上是不存在,只是逻辑上存在。也就是说这个联系我们看不见摸不着,只是我们代码上给它们进行了关联。 映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。 而我们如何在代码上进行关联呢? 答案是通过操作系统调用 mmap() 方法来实现,但是 mmap() 通常是用在有物理介质的文件系统上的。 而Binder 并不存在物理介质,因此mmap() 方法 并不是为了在物理介质和用户空间之间建立映射, mmap() 方法会返回一个指针ptr,该指针指向逻辑地址中的空间,这时候还没有数据拷贝。 要实现诗句拷贝需要将逻辑地址转换为物理地址,这个过程需要通过MMU(MemoryManagementUnit 内存管理单元)实现。 由于第一次数据通信双方还没建立映射,MMU在地址映射表中是无法找到与指针ptr相对应的物理地址的,也就是MMU失败,将产生一个缺页中断, 缺页中断的中断响应函数会在swap 分区中寻找相对应的页面, 如果找不到(也就是该文件从来没有被读入内存的情况),则会通过mmap()建立的映射关系,从硬盘上将文件读取到物理内存中。 TIP: swap 分区通常被称为交换分区,这是一块特殊的硬盘空间,即当实际内存不够用的时候,操作系统会从内存中取出一部分暂时不用的数据,放在交换分区中,从而为当前运行的程序腾出足够的内存空间。 所以数据拷贝是通过缺页中断机制将用户数据写入内存中。 一次完整的Binder IPC 通信过程通常是这样: 首先 Binder 驱动在内核空间创建一个数据接收缓存区; 接着在内核空间开辟一块内核缓存区,建立发送方进程和接收方进程对内科缓存区的映射关系; 发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。 总结 Binder是Android系统提供的进程间通信的一种方式。 之所以提供Binder是因为传统的IPC机制存在一些问题 性能:传统的IPC机制,如:Socket,管道,消息队列在通信时数据会经历两次拷贝。而Binder仅一次。 稳定:Binder基于C/S架构模式,代码结构清晰不易出错。 安全:Android是开源系统,众多软件鱼龙混杂。传统的IPC机制,通信双方不能鉴别身份。而Binder提供了UID用于标识进程,进而能鉴别通信双方。 传统的IPC通信机制原理是用到了系统调用的两个方法 copy_from_user() : 将数据从用户空间拷贝到内核空间 copy_to_user() : 将数据从内核空间拷贝到用户空间 实现过程是: 发送方将数据存入用户的内存缓存区 接收方在在自己进程开辟用户的内存缓存区 内核空间开辟内核缓存区 发送方从用户态进入内核态,并通过系统调用copy_from_user()将发送方的内存缓存区的数据拷贝放入内核缓存区,然后通过copy_to_user()将数据拷贝到接收方的内存缓存区。 这就是传统IPC机制通信需要两次数据拷贝的问题。 而Binder仅需要一次数据拷贝。 它的原理是用到了内存映射和系统调用 mmap() 方法 通过内存映射的方式实现发送方-内核-接收方之间的对应关系。 再通过系统调用 mmap() 方法返回Binder驱动中的逻辑地址,而逻辑地址要和物理地址转换需要通过MMU MMU在连接逻辑地址和物理地址的时候会调用缺页中断方法在swap 分区中寻找相对应的数据。如果没找到,说明数据不存在,则需要进行数据拷贝数据拷贝使用的还是系统调用 copy_from_user()方法。 由于存在映射关系,所以数据拷贝一次即可实现发送方和接收方的进程通信。 参考 Binder学习指南 为什么 Android 要采用 Binder 作为 IPC 机制? Android跨进程通信:图文详解 Binder机制 原理 Android Bander设计与实现 - 设计篇 写给 Android 应用工程师的 Binder 原理剖析! 内存映射原理 系统调用mmap详解整理 Linux swap分区及作用详解 本文同步分享在 博客“_龙衣”(CSDN)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

Java 8 Stream原理解析

说起 Java 8,我们知道 Java 8 大改动之一就是增加函数式编程,而 Stream API 便是函数编程的主角,Stream API 是一种流式的处理数据风格,也就是将要处理的数据当作流,在管道中进行传输,并在管道中的每个节点对数据进行处理,如过滤、排序、转换等。 首先我们先看一个使用Stream API的示例,具体代码如下: code1 Stream example 这是个很简单的一个Stream使用例子,我们过滤掉空字符串后,转成int类型并计算出最大值,这其中包括了三个操作:filter、mapToInt、sum。相信大多数人再刚使用Stream API的时候都会有个疑问,Stream是指怎么实现的,是每一次函数调用就执行一次迭代吗?答案肯定是否,因为如果真的是每一次函数调用就执行一次迭代,这个效率是很难接受的,Stream也不会那么受欢迎。 其实Stream内部是通过流水线(Pipeline)的方式来实现的,基本思想是在迭代的时候顺着流水线尽可能的执行更多的操作,从而避免多次迭代。为了对Stream的操作有更清晰的认识,我们汇总了Stream的所有操作。 从上表可以看出Stream将所有操作分为两类:中间操作和终止操作。其中中间操作分为无状态和有状态,终止操作分为非短路操作和短路操作,下面是针对这几个操作的含义说明: 1、中间操作:中间操作只是一种标记,只有结束操作才会触发实际计算 无状态:指元素的处理不受前面元素的影响; 有状态:有状态的中间操作必须等到所有元素处理之后才知道最终结果,比如排序是有状态操作,在读取所有元素之前并不能确定排序结果。 2、终止操作:顾名思义,就是得出最后计算结果的操作 短路操作:指不用处理全部元素就可以返回结果; 非短路操作:指必须处理所有元素才能得到最终结果。 Stream流水线解决方案 通过上面的介绍,我们了解到Stream在执行中间操作时仅仅是记录,当用户调用终止操作时,会在一个迭代里将已经记录的操作顺着流水线全部执行掉。沿着这个思路,有几个问题需要解决: 用户的操作如何记录? 操作如何叠加? 叠加之后的操作如何执行? 1、操作如何记录 图1-1 关于操作如何记录,在JDK源码注释中多次用(操作)stage来标识用户的每一次操作,而通常情况下Stream的操作又需要一个回调函数,所以一个完整的操作是由数据来源、操作、回调函数组成的三元组来表示。而在具体实现中,使用实例化的ReferencePipeline来表示,即图1-1中的Head、StatelessOp、StatefulOp的实例。接下来我们来看下Stream几个常用方法的源码。 code2 Collection.Stream() code3StreamSupport.stream() code4 ReferencePipeline.map() 从上面源码中可以看出来,我们调用stream()方法时最终会创建一个Head实例来表示流操作的头,当调用map()方法时则会创建无状态的中间操作实例StatelessOp,同样调用其他操作对应的方法也会生成一个ReferencePipeline实例,在这里就不一一列举。在用户调用一系列操作后,最终会形成一个双向链表,如下图所示: 图1-2 2、操作如何叠加 上面我们说明了Stream是通过stage记录操作,但stage只保存当前操作,它并不知道下个stage如何操作,需要什么操作。所以要执行的话还需要某种协议将各个stage关联起来。jdk中就是使用Slink接口来实现的,Slink接口定义begin()、end()、cancellationRequested()、accept()四个方法,如下表所示。 往回看code3 ReferencePipeline.map()的方法,我们会发现我们在创建一个ReferencePipeline实例的时候,需要重写opWrapSink方法来生成对应Sink实例。而且通过阅读源码会发现常用的操作都会创建一个ChainedReference实例。我们可以看下code5 ChainedReference抽象类的源码实现,因为ChainedReference只是个抽象实现,不携带具体操作的特性,所以是更能体现作者的设计理念。 通过查看源码可以发现ChainedReference会持有下一个操作的Slink,并在调用begin、end、cancellationRequested方法会调用下一个操作的Slink的相应方法,以此来达到叠加的效果。 code5ChainedReference 3、叠加之后的操作如何执行 Sink完美封装了Stream每一步操作,并给出了[处理->转发]的模式来叠加操作。这一连串的齿轮已经咬合,就差最后一步拨动齿轮启动执行。是什么启动这一连串的操作呢?也许你已经想到了启动的原始动力就是结束操作(Terminal Operation),一旦调用某个结束操作,就会触发整个流水线的执行。 结束操作之后不能再有别的操作,所以结束操作不会创建新的流水线阶段(Stage),直观的说就是流水线的链表不会在往后延伸了。结束操作会创建一个包装了自己操作的Sink,这也是流水线中最后一个Sink,这个Sink只需要处理数据而不需要将结果传递给下游的Sink(因为没有下游)。对于Sink的[处理->转发]模型,结束操作的Sink就是调用链的出口。 我们再来考察一下上游的Sink是如何找到下游Sink的。一种可选的方案是在PipelineHelper中设置一个Sink字段,在流水线中找到下游Stage并访问Sink字段即可。但Stream类库的设计者没有这么做,而是设置了一个Sink AbstractPipeline.opWrapSink(int flags, Sink downstream)方法来得到Sink,该方法的作用是返回一个新的包含了当前Stage代表的操作以及能够将结果传递给downstream的Sink对象。为什么要产生一个新对象而不是返回一个Sink字段?这是因为使用opWrapSink()可以将当前操作与下游Sink(上文中的downstream参数)结合成新Sink。试想只要从流水线的最后一个Stage开始,不断调用上一个Stage的opWrapSink()方法直到最开始(不包括stage0,因为stage0代表数据源,不包含操作),就可以得到一个代表了流水线上所有操作的Sink,用代码表示就是这样: code6AbstractPipeline.wrapSink 现在流水线上从开始到结束的所有的操作都被包装到了一个Sink里,执行这个Sink就相当于执行整个流水线,执行Sink的代码如下: code7AbstractPipeline.copyInto 上述代码首先调用wrappedSink.begin()方法告诉Sink数据即将到来,然后调用spliterator.forEachRemaining()方法对数据进行迭代,最后调用wrappedSink.end()方法通知Sink数据处理结束。逻辑如此清晰。 作者:Huang Rongpeng

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册