Binder相关面试总结(三):Binder机制是如何跨进程的

Book.aidl

package com.wu.test.aidl;

parcelable Book;

Book.java

public class Book implements Parcelable {

//实体字段、实现序列化方法

}

  • 定义接口

interface BookController {

List getBookList();

void addBookInOut(inout Book book);

}

创建或修改过AIDL文件后需要clean下工程,使系统及时生成我们需要的文件

  • 创建Service供客户端调用

public class AIDLService extends Service {

private final BookController.Stub stub = new BookController.Stub() {

@Override

public List getBookList() throws RemoteException {

return bookList;

}

@Override

public void addBookInOut(Book book) throws RemoteException {

if (book != null) {

book.setName(“服务器改了新书的名字 InOut”);

bookList.add(book);

} else {

Log.e(TAG, “接收到了一个空对象 InOut”);

}

}

};

@Override

public IBinder onBind(Intent intent) {

return stub; //返回binder对象Stub

}

}

客户端

  • 把服务端的AIDL文件以及Book类复制过来,将 aidl 文件夹整个复制到和Java文件夹同个层级下,不需要改动任何代码。创建和服务端Book类所在的相同包名来存放 Book类。

  • 绑定服务,获取服务端的binder接口对象。

private ServiceConnection serviceConnection = new ServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

bookController = BookController.Stub.asInterface(service); //绑定服务后,拿到接口对象

connected = true;

}

@Override

public void onServiceDisconnected(ComponentName name) {

connected = false;

}

};

//绑定服务

private void bindService() {

Intent intent = new Intent();

intent.setPackage(“com.wu.test”);

intent.setAction(“com.wu.test.action”);

bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);

}

aidl文件只是用来定义C/S交互的接口,Android在编译时会自动生成相应的Java类,生成的类中包含了Stub和Proxy静态内部类,用来封装数据转换的过程,实际使用时只关心具体的Java接口类即可。为什么Stub和Proxy是静态内部类呢?这其实只是为了将三个类放在一个文件中,提高代码的聚合性。

AIDL的使用

为何选择Binder方式

在讨论这个问题之前,我们知道Android也是基于Linux内核,Linux现有的进程通信手段有以下几种:

  • 管道:在创建时分配一个page大小的内存,缓存区大小比较有限;

  • 消息队列:信息复制两次,额外的CPU消耗;不合适频繁或信息量大的通信;

  • 共享内存:无须复制,共享缓冲区直接附加到进程虚拟地址空间,速度快;但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决;

  • 套接字:作为更通用的接口,传输效率低,主要用于不同机器或跨网络的通信;

  • 信号量:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 不适用于信息交换,更适用于进程中断控制,比如非法内存访问,杀死某个进程等;

既然有现有的IPC方式,为什么重新设计一套Binder机制呢。主要是出于三个方面的考量:

  • 效率:传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。从Android进程架构角度分析:对于消息队列、Socket和管道来说,数据先从发送方的缓存区拷贝到内核开辟的缓存区中,再从内核缓存区拷贝到接收方的缓存区,一共两次拷贝。

对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,节省了一次数据拷贝的过程,如图:

  • 稳定性:共享内存不需要拷贝,Binder的性能仅次于共享内存。共享内存的性能优于Binder,那为什么不采用共享内存呢,因为共享内存需要处理并发同步问题,容易出现死锁和资源竞争,稳定性较差。Socket虽然是基于C/S架构的,但是它主要是用于网络间的通信且传输效率较低。Binder基于C/S架构 ,Server端与Client端相对独立,稳定性较好。

  • 安全性:传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制为每个进程分配了UID/PID,且在Binder通信时会根据UID/PID进行有效性检测。

Binder机制的作用和原理

Linux系统将一个进程分为用户空间和内核空间。对于进程之间来说,用户空间的数据不可共享,内核空间的数据可共享,为了保证安全性和独立性,一个进程不能直接操作或者访问另一个进程,即Android的进程是相互独立、隔离的,这就需要跨进程之间的数据通信方式。普通的跨进程通信方式一般需要2次内存拷贝,如下图所示

在这里插入图片描述

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

  • 首先 Binder 驱动在内核空间创建一个数据接收缓存区。

  • 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系。(Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。)

  • 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

在这里插入图片描述

数据接收缓冲区映射如何实现?

Binder驱动实现了mmap()系统调用,这对字符设备是比较特殊的,因为mmap()通常用在有物理存储介质的文件系统上,而象Binder这样没有物理介质,纯粹用来通信的字符设备没必要支持mmap()。Binder驱动当然不是为了在物理介质和用户空间做映射,而是用来创建数据接收的缓存空间。先看mmap()是如何使用的:

fd = open(“/dev/binder”, O_RDWR);

mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);

这样Binder的接收方就有了一片大小为MAP_SIZE的接收缓存区。mmap()的返回值是内存映射在用户空间的地址,不过这段空间是由驱动管理,用户不必也不能直接访问(映射类型为PROT_READ,只读映射)。

mmap()分配的内存除了映射进了接收方进程里,还映射进了内核空间。所以调用copy_from_user()将数据拷贝进内核空间也相当于拷贝进了接收方的用户空间,这就是Binder只需一次拷贝的‘秘密’。

Binder框架中ServiceManager的作用

Binder框架 是基于 C/S 架构的。由一系列的组件组成,包括 Client、Server、ServiceManager、Binder驱动,其中 Client、Server、Service Manager 运行在用户空间,Binder 驱动运行在内核空间。如下图所示:

在这里插入图片描述

  • Server&Client:服务器&客户端。在Binder驱动和Service Manager提供的基础设施上,进行Client-Server之间的通信。

  • ServiceManager(如同DNS域名服务器)服务的管理者,将Binder名字转换为Client中对该Binder的引用,使得Client可以通过Binder名字获得Server中Binder实体的引用。

  • Binder驱动(如同路由器):负责进程之间binder通信的建立,计数管理以及数据的传递交互等底层支持。

Service Manager是Binder进程间通信的核心之一,它扮演着Binder进程间通信机制上下文管理者的角色,同时负责管理系统的Service组件,并且向Client组件提供获取Service代理对象的服务。

Service Manager运行在一个独立进程中,因此和Client和Service通信也是运用Binder进程间通信机制来交互的。ServiceManager 和其他进程同样采用 Bidner 通信,ServiceManager 是 Server 端,有自己的 Binder 实体,其他进程都是 Client,需要通过这个 Binder 的引用来实现 Binder 的注册,查询和获取。ServiceManager 提供的 Binder 比较特殊,它没有名字也不需要注册。当一个进程使用 BINDERSETCONTEXT_MGR 命令将自己注册成 ServiceManager 时 Binder 驱动会自动为它创建 Binder 实体(这就是那只预先造好的那只鸡)。其次这个 Binder 实体的引用在所有 Client 中都固定为 0 而无需通过其它手段获得。也就是说,一个 Server 想要向 ServiceManager 注册自己的 Binder 就必须通过这个 0 号引用和 ServiceManager 的 Binder 通信。

Service Manager主要是负责如下:

  1. 打开和映射Binder设备文件;

  2. 注册为Binder上下文管理者;

  3. 循环等待Client进程请求;

  4. 管理ServiceManager代理对象;

  5. Service组件创建,同时需要在注册ServiceManager;

  6. 管理Service代理对象;

总结图

在这里插入图片描述

Binder进程间通信模型

在这里插入图片描述

Client进程组件、Service进程组件和ServiceManager运行在用户空间,而Binder驱动程序运行在Linux系统内核空间,其中Client进程组件、Service进程组件和Service Manager均是通过调用Open、mmap和ioctl来访问虚拟设备文件/dev/binder,从而实现与Binder驱动程序的交互,进而能够间接地执行进程通信

Binder就是一种把这四个组件粘合在一起的粘结剂了,其中,核心组件便是Binder驱动程序了,Service Manager提供了辅助管理的功能,Client和Server正是在Binder驱动和Service Manager提供的基础设施上,进行Client-Server之间的通信。

Binder具体是如何通信的?

在这里插入图片描述

Binder实体对象、Binder引用对象、Binder本地对象和Binder代理对象交互过程:

  1. 运行在Client中Binder代理对象通过Binder驱动程序向运行在Server中的Binder本地对象发出一个进程间通信请求,Binder驱动程序接着根据Client传递过来的Binder代理对象的句柄值来找到对应的Binder引用对象。

  2. Binder驱动程序根据前面找到的Binder引用对象找到对应的Binder实体对象,并且创建一个事务来描述该次进程间通信过程。

  3. Binder驱动程序根据前面找到的Binder实体对象来找到运行在server中的Binder本地对象,并且将Client传递过来的通信数据发送给它处理。

  4. Binder本地对象处理完成Client进程的通信请求之后,就将通信结果返回给Binder驱动程序,Binder驱动程序接着找到前面创建的一个事务。

  5. Binder驱动程序根据前面找到的事务的相关属性来找到发出通信请求的Client,并且通知Client将通信结果返回给对应的Binder代理对象处理。

Binder 通信中的代理模式

跨进程通信的过程都有 Binder 驱动的参与,因此在数据流经 Binder 驱动的时候驱动会对数据做一层转换。当 A 进程想要获取 B 进程中的 object 时,驱动并不会真的把 object 返回给 A,而是返回了一个跟 object 看起来一模一样的代理对象 objectProxy,这个 objectProxy 具有和 object 一摸一样的方法,但是这些方法并没有 B 进程中 object 对象那些方法的能力,这些方法只需要把把请求参数交给驱动即可。对于 A 进程来说和直接调用 object 中的方法是一样的。

当 Binder 驱动接收到 A 进程的消息后,发现这是个 objectProxy 就去查询自己维护的表单,一查发现这是 B 进程 object 的代理对象。于是就会去通知 B 进程调用 object 的方法,并要求 B 进程把返回结果发给自己。当驱动拿到 B 进程的返回结果后就会转发给 A 进程,一次通信就完成了。

在这里插入图片描述

Binder 的完整定义

  • 从进程间通信的角度看,Binder 是一种进程间通信的机制;

最后

针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!

  • Android前沿技术大纲

  • 全套体系化高级架构视频

Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、混合式开发(ReactNative+Weex)全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
ctNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!

  • Android前沿技术大纲

    [外链图片转存中…(img-0t44CZ5A-1714934542553)]

  • 全套体系化高级架构视频

    [外链图片转存中…(img-1MkBCWXM-1714934542554)]

Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、混合式开发(ReactNative+Weex)全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 29
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值