Android 进程间通信(二) -- 理解 Binder 的机制

2024-06-07 20:08

本文主要是介绍Android 进程间通信(二) -- 理解 Binder 的机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

参考 写给 Android 应用工程师的 Binder 原理剖析 一些文字和图片均参考该文

系列文章
Android 进程间通信(一) – Android 多进程模式
Android 进程间通信(二) – 理解 Binder 的机制
Android 进程间通信(三) --通过 AIDL 理解Binder,并手写Binder服务

上一章中,已经理解了进程之间通信的一些基本知识,这一章来好好学习 Binder。

一、为啥使用Binder

我们知道,Android 底层使用了大量的 Binder 来进行进程之间的通信。那为啥要新设计个 Binder ,而不是用传统的IPC 通信方式呢?
主要是考虑到以下几个方面:

  1. 性能方面:Socket 作为通过接口,但传输效率低,开销大,且阻塞 IO,一般用于跨网络的进程间通信;而消息队列和管道,则采用用存储-转阿发方法,至少经过两次拷贝;共享内存虽然无需拷贝,但实现复杂,控制也麻烦。

图片原来于 Android Binder 设计与实现在这里插入图片描述

  1. 稳定性:Binder 基于C/S架构,client 有什么需求就丢给 server,架构清晰,又相互独立。
  2. 安全性:Android 为每个应用都分配了自己的UID,用来鉴别身份,而传统的 IPC 则无法做到。

想要了解 Binder ,想了解 传统的 Linux 的IPC 机制。

1.1、linux 的 IPC 机制

Linux 采用了虚拟地址空间地址,操作系统将虚拟内存分为 用户空间 (User space) 和内核空间 (Kernel) ,普通的应用程序运行在用户空间,而系统内核运行在内核空间;这也是我们常说的两进程之间的数据不共享,不通过特殊手段不共享的问题。

在这里插入图片描述
从上图可以看出来传统的Linux跨进程涉及的一些点。

  • 进程隔离
  • 进程空间划分:用户空间(User space) 和 内核空间(Kerner space)
  • 系统调用状态:内核态和用户态

1.1.1 进程隔离

在操作系统中,两个进程之间的数据时不共享的,必须通过特殊的通信机制,进程间数据才能共享。

1.1.2 进程空间划分

现在的操作系统都是采用虚拟存储器,对于 32 位系统而言,它的寻址地址就是 2的32次方,即 4GB;
对操作系统而言,其中核心的部分称为内核,它的权限最高,可以访问受限的内存空间,也可以访问硬件设备,为了保护用户进程不能直接操作内核,保证内核的安全性,从逻辑上,将虚拟空间分户为用户空间和内核空间。
其中将高地址的1G划分为内核空间,而低地址的3G划分为用户空间;
在这里插入图片描述

1.1.3 系统调用: 内核态,用户态

虽然有以上哪些划分,但两进程之间不可能永远不通信;当两进程需要进行数据交互时,就需要系统调用来实现。系统调用时用户空间访问内核空间的唯一方式,保证了所有的资源访问都是在内核控制下进行了,避免了越权访问,提供系统稳定性。

Linux 使用两级保护机制:0 级供系统内核使用,3 级供用户程序使用。
当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核运行态(内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。

当进程在执行用户自己的代码的时候,我们称其处于用户运行态(用户态)。此时处理器在特权级最低的(3级)用户代码中运行。

系统调用如下两个函数来实现:

copy_from_user() //将数据从用户空间拷贝到内核空间
copy_to_user() //将数据从内核空间拷贝到用户空间

一般来说,A进程和B进程是无法和对方数据交互,但有些情况下,就需要两个进程之间有交互,而这个交互过程就叫做 IPC (Inter Process Communication ,进程间通信),IPC 的实质是 数据的交互。IPC 的通信过程如下:

  • A进程发送方,把要发送的数据放到 用户空间的内存缓存区
  • 内核程序在内核空间开辟一块内核缓存区,并将A进程用户空间的数据,通过 copy_from_user 从内存缓冲区,拷贝到内核空间的内核缓冲区。
  • B进程接收方,也在用户空间开辟一块 内存缓存区,准备接受数据
  • 内核程序将内核缓存区通过 copy_to_user 将数据拷贝到用户空间的内存缓存区。

在这里插入图片描述
通过以上过程,IPC 一次就完成了,但这种方式有比较大的缺陷:

  • 由于不知道需要多大内存用于存放数据,因此都是尽可能开辟大的内存,会导致浪费
  • 性能较低,整个过程需要经过A进程 内存缓存区 - 内核缓存区 - 内存缓存区,需要两次拷贝。

1.2 Binder IPC通信原理

为了克服传统 IPC 的不足;Android 引入了 Binder 机制。Binder 在数据交互这块,可以充当是一个桥梁的作用,让两个进程之间能够相互通信。

从上面知道,数据的通信少不了内核的帮助,而Binder不属于内核,但通过 Linex 的 LKM 机制:

模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行

因此,Binder 作为模块存在于内核中,即成为 Binder 驱动。回顾上一节,传统IPC进程之间的通信需要两次的数据拷贝,Binder 却可以借助 Linux 的另一个特性,只用一次性拷贝,就能实现 IPC 过程,这个就是内存映射

Binder IPC 涉及到的内存映射通过 mmap() 来实现,mmap()是操作系统中一个内存映射的方法;简单来说,内存映射,就是把 用户空间的一块内存区域映射到内核空间,映射建立之后,用户空间的数据,就能反映到这块内核空间的内存来,这样,当用户空间的数据修改,内核空间的数据也会跟着被改动

内存映射能减少拷贝次数,实现用户空间和内核空间的效率互动。两个空间各自修改的数据能直接反射在映射的内存区域,从而被对方空间及时感知。

一次完整的 Binder IPC 通信过程是这样的:

  1. Binder 驱动在内核空间建立内核缓存区
  2. Binder 驱动在内核空间创建接收数据缓存区,并与内核缓存区建立映射,以及与接收进程的 用户空间地址建立映射关系
  3. 发送方进程通过系统调用 copy_from_user() 将数据copy 到内核中的内核缓存区,由于内核缓存区与接收数据缓存区有映射,接收数据缓存区又与接收方的用户空间地址有映射,所以,数据直接就到接收方的用户空间上了。
    如下图:

在这里插入图片描述

二. Binder 通信模型

上面介绍了 Binder IPC 的通信原理,这里实现层是如何设计的。

上面说到,Binder 是基于 C/S 架构的,由一系列组件组成,包括 Client 、Server、ServerManager,Binder驱动等

其中 Client 、Server、ServerManager 运行在用户空间,Binder 驱动运行在内核空间。ServerManager 和 Binder 驱动是由系统提供,而 Client 、Server则由用户自己创建。

Client 、Server、ServerManager,均是通过系统调用 open、mmap 和ioctl 来访问设备文件 /dev/binder 的,从而实现与 Binder 的交互来 间接实现进程之间的数据通信。
如下图:
在这里插入图片描述

  • Binder 驱动,已经解释过了,就是两个两个进程之间的桥梁
  • ServiceManager :它是binder的服务大管家,它的作用只有一个,注册和查询。一个Binder注册的时候,会携带对应的字符串,而 client 在获取这个binder 的时候,就可以通过这个字符串,通过 ServiceManager 查询拿到 binder。它也是一个looper循环。

三. Binder 的代理模式

从上面已经清楚,Client、Server 借助Binder驱动,完成跨进程的实现机制。
但有个问题,A 进程想要获取B进程的 object是,驱动是不是真的就把 object 返回 A ?

当然不是,当数据在 Binder 驱动 流过时,会对数据进行一层转换;当 A 想获取 B 的object 对象时,驱动并不会把 object 对象给 A,而是返回了一个跟 object 一模一样的代理对象 objectProxy,objectProxy 不具备 object 方法的能力;当A 使用 objectProxy 时,只需要把参数通过 objectProxy 给 Binder 驱动就可以了,看起来就像调用了 object 了。

当B接收到 A 进程的消息时,发现这个是 objectProxy,就去查询自己的表达那,一旦发现这个 B 进程的object 代理对象,就会通知 B 调用 object方法,并把结果返回给自己 或者 A 进程。如下:
在这里插入图片描述
这个章节,在 AIDL 的时候再来详细分析。

这篇关于Android 进程间通信(二) -- 理解 Binder 的机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1040173

相关文章

Android使用ImageView.ScaleType实现图片的缩放与裁剪功能

《Android使用ImageView.ScaleType实现图片的缩放与裁剪功能》ImageView是最常用的控件之一,它用于展示各种类型的图片,为了能够根据需求调整图片的显示效果,Android提... 目录什么是 ImageView.ScaleType?FIT_XYFIT_STARTFIT_CENTE

Python如何精准判断某个进程是否在运行

《Python如何精准判断某个进程是否在运行》这篇文章主要为大家详细介绍了Python如何精准判断某个进程是否在运行,本文为大家整理了3种方法并进行了对比,有需要的小伙伴可以跟随小编一起学习一下... 目录一、为什么需要判断进程是否存在二、方法1:用psutil库(推荐)三、方法2:用os.system调用

C++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

Android实现在线预览office文档的示例详解

《Android实现在线预览office文档的示例详解》在移动端展示在线Office文档(如Word、Excel、PPT)是一项常见需求,这篇文章为大家重点介绍了两种方案的实现方法,希望对大家有一定的... 目录一、项目概述二、相关技术知识三、实现思路3.1 方案一:WebView + Office Onl

Android实现两台手机屏幕共享和远程控制功能

《Android实现两台手机屏幕共享和远程控制功能》在远程协助、在线教学、技术支持等多种场景下,实时获得另一部移动设备的屏幕画面,并对其进行操作,具有极高的应用价值,本项目旨在实现两台Android手... 目录一、项目概述二、相关知识2.1 MediaProjection API2.2 Socket 网络

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序

Android实现悬浮按钮功能

《Android实现悬浮按钮功能》在很多场景中,我们希望在应用或系统任意界面上都能看到一个小的“悬浮按钮”(FloatingButton),用来快速启动工具、展示未读信息或快捷操作,所以本文给大家介绍... 目录一、项目概述二、相关技术知识三、实现思路四、整合代码4.1 Java 代码(MainActivi

Android Mainline基础简介

《AndroidMainline基础简介》AndroidMainline是通过模块化更新Android核心组件的框架,可能提高安全性,本文给大家介绍AndroidMainline基础简介,感兴趣的朋... 目录关键要点什么是 android Mainline?Android Mainline 的工作原理关键

如何解决idea的Module:‘:app‘platform‘android-32‘not found.问题

《如何解决idea的Module:‘:app‘platform‘android-32‘notfound.问题》:本文主要介绍如何解决idea的Module:‘:app‘platform‘andr... 目录idea的Module:‘:app‘pwww.chinasem.cnlatform‘android-32

深入理解Apache Kafka(分布式流处理平台)

《深入理解ApacheKafka(分布式流处理平台)》ApacheKafka作为现代分布式系统中的核心中间件,为构建高吞吐量、低延迟的数据管道提供了强大支持,本文将深入探讨Kafka的核心概念、架构... 目录引言一、Apache Kafka概述1.1 什么是Kafka?1.2 Kafka的核心概念二、Ka