Android中的Binder

server/2024/9/21 9:12:57/

binder是Android平台的一种跨进程通信(IPC)机制,从应用层角度来说,binder是客户端和服务端进行通信的媒介。

ipc原理

ipc通信指的是两个进程之间交换数据,如图中的client进程和server进程。

Android为每个进程提供了虚拟内存空间,而每个Android进程只能运行在自己进程所拥有的虚拟内存空间。

内存空间又分为用户空间和内核空间,前者的数据不能进程间共享,但后者可以。图中的Client进程和Server进程就是利用了进程间可以共享各自内核空间的数据,来完成底层通信的工作。

Android的C/S通信机制

C/S通信指的就是Client和Server两个进程的通信,但实际通信时除了包含这两个进程,还有一个Service Manager,它用于管理各种服务。

这些服务通常是Android系统的核心功能模块,例如传感器管理、电源管理、WIFI管理、闹钟服务等等,与Android四大组件中的服务不同。

当一个Server(服务端)想要提供一种服务,首先需要在Service Manager注册该服务;

而当Client(客户端)想要使用Server中的服务时,不能直接访问,而是要从Service Manager获取该服务,才能使用Server所提供的服务,来与Server进行通信。

Binder通信模型

在引入binder机制后,客户端、服务端和Service Manager之间不能通过api直接互相访问,而是与内核空间的binder驱动通过ioctl方式来完成进程间的数据交换。

关键概念

  • Binder实体对象:Binder服务的提供者,类型是BBinder,位于服务端
  • Binder引用对象:Binder实体对象在客户端进程的代表,类型是BpBinder,位于客户端
  • IBinder对象:Binder实体对象和引用对象的统称,也是他们的父类
  • Binder代理对象:又称接口对象,为客户端的上层应用提供接口服务,类型是IInterface

Binder引用对象和代理对象都是服务端进程中的,把它们分离的好处是一个代理对象可以有多个引用对象,方便上层应用使用。

通信过程

注册服务

  1. server进程向binder驱动申请创建服务的binder实体 
  2. binder驱动为这个服务创建位于内核的binder实体和binder引用
  3. 创建完成后,服务端通过binder驱动将binder引用发送给service manager
  4. service manager收到数据后,取出被创建服务的名字和引用,填入一张查找表

通过以上步骤,server进程通过binder驱动完成了在service manager的服务注册。

在注册服务的过程中,server进程是客户端,而service manager是服务端。

获取服务

  1. Client进程利用handle值为0的引用找到service manager
  2. Client进程向service manager发送xxxservice的访问申请
  3. service manager从请求表中获取xxxservice的名字,在查找表中找到对应的条目,取出对应的binder引用
  4. service manager把xxxservice的binder引用传给Client进程

使用服务

在使用服务时,Client和Server进程都是发送方和接收方。 

这是因为Client在发送服务请求时,Server是接收方;当Server返回数据给Client时,Client变成了接收方。

不论发送方是谁,都会通过自身的Binder实体,把数据发送给接收方的Binder引用。binder驱动回处理发送请求,利用内核空间进程共享机制如下:

  1. 把发送方的数据存入写缓存(binder_write_read.write_buffer)(对于接收方这是读缓存)
  2. 接收方一直处于阻塞状态,当写缓存有数据,会读取数据执行命令操作
  3. 接收方执行操作后,会把结果返回,同样放在写缓存区(对于发送方这是读缓存)

Android中的Binder

 activity、service等组件都需要与ams(system_server)通信,这种跨进程的通信是由binder完成的。从不同角度分析binder如下:

  • 机制:binder是一种进程间通信机制
  • 驱动:binder是一个虚拟物理设备驱动
  • 应用层:binder是一个能发起通信的java类:在java中,如果想要进程通信,就要继承binder

为什么要使用多进程进行开发?

虚拟机分配给各个进程的运行内存是有限制的,lmk也会优先回收占用系统资源大的进程。

对于多进程开发的优势一般有以下几点:

  • 突破进程内存限制,为占用内存大的单独开辟一个进程
  • 功能稳定性:如为通信线程保持长连接的稳定性
  • 防止内存泄漏:如为容易内存泄漏的webview单独开辟一个进程
  • 隔离风险:对于不稳定的进程放在独立进程,避免主进程崩溃

Binder有什么优势 

首先回顾linux进程间的通信机制:管道、信号量、共享内存、socket。

从性能出发,共享内存 > binder > 其他ipc。

但共享内存的缺点也十分明显。与线程之间共享同一块内存相同,共享内存的进程也很容易出现死锁、数据不同步等问题,操作不方便。同时,socket作为一款通用接口,开销过大。

最重要的一点是安全性。传统ipc模式普遍存在的问题是依赖上层协议和访问接入点是开放的。

以创建服务为例,系统需要知道创建人的身份,但在传统ipc机制中,这个身份的获取是从上层协议获取的,即app将id传给系统,而app传回的内容可以是不真实的。对比之下,服务在被创建时,binder就会为创建人分配唯一的uid(用户身份)。

第二点以服务器为例,如果ip是开放的,服务器很容易就会被攻击。同样的,对于传统ipc,如果接入点被知晓,所有人都可以访问。对比之下,binder同时支持实名和匿名。实名与传统ipc相同,是开放的;匿名指的是如果有人需要获取服务,需要先获取到binder内部的引用,才能进行访问。通常的,系统服务是实名的,个人服务是匿名的。直接在service manager中注册的服务是实名的。

Binder是如何做到一次拷贝的

在回答问题之前详细解释一下ipc和虚拟内存的概念。

进程间通信和线程间不同的原因是两者的内存机制不同。对于线程而言,它们的内存是共享的,但进程之间的内存是相互隔离的。

在ipc原理中我们看到进程内部分为用户空间和内核空间,出于安全两者之间是隔离的,app和系统分别处理用户空间和内核空间。

设想如果进程中没有对这两部份进行隔离,app就可以任意访问系统才能访问的数据,而正常来说这样的访问是需要权限的。但这不意味着两者之间完全隔离。系统为两者通信提供了api(copy_from_user & copy_to_user),使得两者间可以互相通信。

对于我们编程而言,需要系统分配虚拟内存,这是因为物理内存不一定是一整块内存,而这种整块内存恰恰是我们在编程中常常需要的。

虚拟内存是通过MMU内存管理单元来映射到物理内存的。在设计的时候,所有进程的内核空间都被映射到了同一块物理内存。这么做的好处就是实现了内存共享,一个进程可以很方便的去获取物理空间中其他进程的内核空间。

 以上就是传统的ipc通信方式。一个进程把自身用户空间的数据通过copy_from_user拷贝到内核空间,而另一个进程通过copy_to_user第二次拷贝从内核空间获取数据到自己的用户空间,这就拷贝了两次。

binder机制下通信时,内核空间和数据接收方的用户空间映射了同一块物理内存。

简而言之,获取数据的进程的用户空间和内核空间都分配了一小块内存空间,指向同一块物理内存。而这就意味着当被获取数据的进程,从用户空间复制数据到内核空间后,如果内核空间把这个数据存储到这一小块内存空间,另一个进程就能够直接获取,不需要再做一次复制。

MMAP的原理

Linux将一个虚拟内存区域,与磁盘上的物理内存区域关联起来,以这种方式初始化这个虚拟内存区域的内容。这个过程称为内存映射(memory mapping)。

用户空间是不能直接访问磁盘上的内容的,如需访问要通过内核空间,这是肯定很慢的。 首先需要调用write方法从用户空间复制到内核空间,再把数据复制到磁盘,完成写入。

因此当我们使用mmap时关联了虚拟内存和物理内存,当我们在虚拟内存做操作时,物理内存就会直接被修改。

 


http://www.ppmy.cn/server/100145.html

相关文章

从C向C++26——C++11(3)

一.可调用对象 1.可调用对象 可调用对象分为四类&#xff1a; 是一个函数指针 int print(int a, double b) {cout << a << b << endl;return 0; } // 定义函数指针 int (*func)(int, double) print; //使用using起别名来定义函数指针&#xff0c;注意把us…

基于商业化glm大模型接口的pdf目录提取实验

鬼知道这个实验是怎么变成今天这个样子的。希望大家踊跃参加比赛&#xff0c;这样我的压力会更小。不然就要自己充钱了。这是参赛地址&#xff1a;智谱AI开放平台 世界上有很多PDF文档。这些PDF中有些非常标准化&#xff0c;可以从源码中直接解析出章节信息&#xff1b;而有些…

HAProxy 负载均衡原理深度解析

目录 一、引言 二、HAProxy 简介 三、工作模式 3.1、四层负载均衡&#xff08;Layer 4&#xff09; 3.2、七层负载均衡&#xff08;Layer 7&#xff09; 3.3、区别和选择 四、 负载均衡算法 4.1、静态负载均衡算法 4.2、动态负载均衡算法 4.3、其他算法 4.4、选择算…

springboot使用WebSocket

1、、创建springboot项目&#xff0c;勾选Spring web&#xff0c;并导包 当前springboot选择的是2.6.13版本&#xff0c;jdk1.8尽量选2.几的springboot <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-we…

Video视频抽帧和WebCodecs API视频抽帧介绍

目录 mp4Box抽帧 ffmpeg抽帧 video元素抽帧 WebCodecs 核心API 视频文件是一个容器&#xff0c;里面有很多不同的轨道信息。如&#xff1a;图像、声音、字幕等。而视频图像信息又是由一系列图片序列帧的集合。如10秒时长的视频&#xff0c;假设每秒30帧。那大概有300条图像…

relativePath

Maven中的parent.relativePath元素用于指定父POM的相对路径。当你看到这样的错误信息时&#xff0c;它意味着Maven期望在某个位置找到一个父POM&#xff0c;但实际上找到的是另一个POM。 在你的情况下&#xff0c;错误信息表明com.cubemall:cubemall-product:0.0.1-SNAPSHOT项…

网络如何发送一个数据包

网络如何发送一个数据包 网络消息发送就是点一点屏幕。 骚瑞&#xff0c;这一点都不好笑。&#xff08;小品就是我的本质惹&#xff09; 之前我就是会被这个问题搞的不安宁。是怎么知道对方的IP地址的呢&#xff1f;怎么知道对方的MAC呢&#xff1f;世界上计算机有那么多&…

Element UI左侧导航栏写法(递归组件实现)

1、左侧导航栏组件使用的是Element Ui的导航栏组件&#xff0c;思路&#xff1a;首先判断导航栏数据是否存在children&#xff0c;以此来实现一级菜单与多级菜单的渲染&#xff0c;然后使用递归组件实现多级菜单的子菜单渲染&#xff0c;注意使用递归组件需将菜单栏数据在父组件…