第一章 深入解析Binder

面试题预览

  • 请介绍什么是Binder机制 ⭐⭐⭐⭐
  • 请介绍Binder机制流程 ⭐⭐⭐⭐
  • Binder有什么优势?(字节跳动)⭐⭐⭐⭐
  • Binder机制需要多少次内存拷贝 ⭐⭐⭐
  • Binder是如何做到一次拷贝?(腾讯) ⭐⭐⭐
  • Android有很多跨进程通信方法,为何选择Binder?⭐⭐⭐
  • MMAP的原理讲解(腾讯)⭐⭐
  • Binder机制是如何跨进程的(阿里)⭐⭐⭐
  • 描述AIDL生成的java类细节(字节跳动)⭐⭐⭐
  • 为什么Intent不能传递大数据(阿里)⭐⭐⭐

  1. Binder是什么

Binder是Android系统提供的一种IPC(进程间通信)机制,采用C/S(客户端与服务端结构)架构。Binder原理是掌握Android系统的基石,Binder就像Android中的血管,基本上贯穿了andorid框架层的全部。在Android中我们所使用的Activity,Service等组件都需要和AMS(system_server)通信,这种跨进程的通信都是通过Binder完成。

在基于Binder通信的C/S架构体系中,除了C/S架构所包括的Client端和Server端外,Android还有一个全局的ServiceManager端,它的作用是管理系统中的各种服务(Service)。Client、Server和ServiceManager这三者之间的交互关系如下图所示:

关键点:

  • 从进程间通信的角度看,Binder 是一种进程间通信的机制。
  • 从 Server 进程的角度看,Binder 指的是 Server 中的 Binder 实体对象(Binder类 IBinder)。
  • 从 Client 进程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个远程代理。
  • 从传输过程的角度看,Binder 是一个可以跨进程传输的对象;Binder 驱动会自动完成代理对象和本地对象之间的转换。
  • 从Android Framework角度看,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁 Binder跨进程通信机制:基于C/S架构,由Client、Server、ServerManager和Binder驱动组成。

进程空间分为用户空间和内核空间。用户空间不可以进行数据交互;内核空间可以进行数据交互,所有进程共用一个内核空间。Client、Server、ServiceManager均在用户空间中实现,而Binder驱动程序则是在内核空间中实现的。

1.1 Android中的跨进程通讯方法

  • 共享内存:通过共享缓冲区直接映射到各个进程的虚拟地址空间,速度快。但实现比较复杂,进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决。
  • 消息队列:提供了一种从一个进程向另一个进程发送一个数据块的方法,但需要进行2次数据拷贝,不适合频繁或者数据量大的情况
  • 管道:分为有名/无名管道,在创建时分配一个page大小的内存,缓存区大小比较有限。
  • 信号量:一般作为一种锁机制,用于进程/线程同步。
  • 信号:更适合作为进程中断控制,而非数据交换。
  • Socket/LocalSocket:允许位于同一主机(计算机)或使用网络连接起来的不同主机上的应用程序之间交换数据;
  • Binder: Binder是Android中的一种跨进程通信机制;
  • 匿名共享内存:在Android系统中,提供了独特的匿名共享内存子系统Ashmem(Anonymous Shared Memory),它利用了Linux的 tmpfs文件系统(一种可以基于RAM或是SWAP的高速文件系统)来实现不同进程间的内存共享。有两个特点:能够辅助内存管理系统来有效地管理不再使用的内存块;
    • 通过Binder进程间通信机制来实现进程间的内存共享;
  • File两个进程通过读写同一个文件来进行数据共享,共享的文件可以是文本、XML、JOSN。文件共享适用于对数据同步要求不高的进程间通信。通过文件进行多进程通信用法简单,但不适合高并发情况
  • ContentProvider: Android四大组件之一,常用于进程间数据共享,特别是一对多的情况,不过受限于AIDL;
  • BundleBundle实现了Parcelable接口,所以它可以方便的在不同的进程间传输。Acitivity、Service、Receiver都是在Intent中通过Bundle来进行数据传递。Bundle很常见也很常用,但只能传输Bundle支持的数据类型,同样不适合大量数据的传输;

面试官很喜欢问Android中有哪些跨进程方法,如果你可以答出这么多,一定可以加分

  1. 为何要使用Binder

众所周知,Android系统基于Linux系统,因此Linux系统使用的IPC方法:共享内存、消息队列、管道、信号量等,在Android系统上也都能使用。1.1小节就列出了10+种方法,那么为何还需要Binder?

使用Binder的理由可以归纳为:性能、安全、稳定、语言四个方面:

  • 性能:管道、消息队列、Socket都需要2次数据拷贝(即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区),而Binder只要1次(可见第3节分析),且Binder相对Socket方法也更加高效, Socket主要用于跨网络的进程间通讯,传输的效率比较低。当然共享内存1次数据拷都不需要,因此就性能而言,共享内存高于Binder;
  • 安全:Android系统为每个应用都分配各自的UID/PID来验证身份,保证了进程通信的安全性;
  • 稳定:共享内存1次数据拷贝都不需要,但实现的方法比较复杂,并且需要做好进务端同步,容易出现死锁或者资源竞争,稳定性差。而Binder基于C/S架构,客户端和服务端彼此独立,稳定性强。
  • 语言:Linux是基于C语言,C语言是面向过程的,Android应用层和Java Framework是基于Java语言,Java语言是面向对象的。Binder本身符合面向对象的思想,因此作为Androißd的通信机制更合适不过。

因此,在Android系统的IPC方式,一般情况下都推荐使用Binder。当然,在Android源码里,上述其他IPC方法也会使用到。接下来让我们先看看传统的Linux跨进程通讯原理。

2.1 传统的Linux跨进程通讯

2.1.1 背景知识

进程空间分为用户空间和内核空间。用户空间是用户程序代码运行的地方,内核空间是内核代码运行的地方。为了安全,它们是隔离的,这样即使用户程序奔溃了,内核也不受影响。

Android是基于Linux的,以32位系统来说,每个Linux进程都有3G虚拟内存空间,称之为用户空间,以及1G的内核空间。其中,不同进程的用户空间是相互隔离的,不可直接通讯;但是内核空间却是共享的。以讲程A发送数据到进程B为例,需要经历如下步骤:

  • A进程的内核空间开辟一块内核缓存区,通过copy_to_user()系统调用,将用户空间的数据拷贝到内核缓冲区;
  • 进程B的内核空间也开辟了一块内核缓存区,因为不同进程的内核空间共享的,此时进程B就可以通过copy_to_user()系统调用将内核缓存区的数据拷贝到自己的用户空间;

如此就完成了一次进程间通讯,上面说到的管道、消息队列都是基于这种方式。这种方式有两个缺点:

  • 需要经过2次数据拷贝,效率较低;
  • 进程B接收的内核缓存区无法确定开辟多大的空间合适,太大自然损耗空间,太小则可能出现拷贝不完整,因此要么开辟尽可能大的空间,要么先获取进程A发送的数据大小再开辟对应大小的空间。这两种做法要么浪费空间,要么浪费时间。
  1. Bind的通讯原理

拓展面试题:

  1. Binder Driver 如何在内核空间中做到一次拷贝的?(腾讯)
  2. Binder有什么优势?(字节跳动)
  3. Binder机制是如何跨进程的(阿里)

3.1 了解Binder通讯原理需要知道两个概念

  1. Binder驱动:Linux有一个叫“动态内核可加载模块”的机制,因此Android系统可以通过动态加载这个模块,让该模块链接到内核,作为内核的一部分,以实现用户进程通过这个内核模块作为通讯桥梁。这个模块就是“Binder驱动”。
  2. 内存映射:内存映射即经典的mmap()方法,该方法可以将进程的用户空间的一块用户内存区域A映射到内核空间B,映射后,用户内存区域A的修改都会直接反应到内核空间B,反之亦然。

3.2 Binder的通讯原理

知道这两个概念Binder驱动和内存映射的两个概念后,以进程A发送数据到进程B为例。那么Binder是如何做到一次拷贝的?可见下图解释。

Binder是基于内存映射来实现的,它使用内存映射是为了跨进程传递数据。

Binder其实相当于将发送方的内核缓冲区与接收方用户空间建立内存映射。发送方发送数据,也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信

3.3 Binder有什么优势

Binder与传统IPC对比

3.4 Binder机制是如何跨进程通讯的?

发送方进程1和接收方进程2不能直接进行通信,但是由于内核空间是共享的,所以发送方可以通过copy_from_user()把数据直接拷贝到内核空间,内核空间与接收方的用户空间同时映射到一块物理内存中,也就相当于直接进入了接收方的用户空间,所以整个通信过程只进行了一次内存拷贝,这个映射就是通过mmap实现的。

3.5 Binder传输数据的大小限制

  • 同一进程Binder所传输数据总量大小限制:1MB
    • 普通的由Zygote孵化而来的用户进程,所映射的Binder内存大小是不到1M的
  • Android 6和Android 7上面,又进行了更加严格的限制,Binder单次传输数据大小限制:200KB

下面是传输限制的详细介绍:

在开发APP的时候,Binder对程序员几乎不可见,但是作为Android的数据运输系统,Binder的影响是全面性的,所以有时候如果不了解Binder的一些限制,在出现问题的时候往往是没有任何头绪。比如在Activity之间传输BitMap的时候,如果Bitmap过大,就会引起问题,比如崩溃等,这其实就跟Binder传输数据大小的限制有关系,在上面的一次拷贝中分析过,mmap函数会为Binder数据传递映射一块连续的虚拟地址,这块虚拟内存空间其实是有大小限制的,不同的进程可能还不一样。

普通的由Zygote孵化而来的用户进程,所映射的Binder内存大小是不到1M的,准确说是 110241024) - (4096 *2) :这个限制定义在ProcessState类中,如果传输说句超过这个大小,系统就会报错,因为Binder本身就是为了进程间频繁而灵活的通信所设计的,并不是为了拷贝大数据而使用的:

#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2))

而在内核中,其实也有个限制,是4M,不过由于APP中已经限制了不到1M,这里的限制似乎也没多大用途:

static int binder_mmap(struct file *filp struct vm_area_struct *vma)
{
	int ret;
	struct vm_struct *area;
	struct binder_proc *proc = filp->private_data;
	const char *failure_string;
	struct binder_buffer *buffer;
    //限制不能超过4M
	if ((vma->vm_end - vma->vm_start) > SZ_4M)
		vma->vm_end = vma->vm_start + SZ_4M;
	。。。
	}

有个特殊的进程ServiceManager进程,它为自己申请的Binder内核空间是128K,这个同ServiceManager的用途是分不开的,ServcieManager主要面向系统Service,只是简单的提供一些addServcie,getService的功能,不涉及多大的数据传输,因此不需要申请多大的内存:

int main(int argc char **argv)
{
    struct binder_state *bs;
    void *svcmgr = BINDER_SERVICE_MANAGER;

		// 仅仅申请了128k
    bs = binder_open(128*1024);
 if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n" strerror(errno));
        return -1;
    }

    svcmgr_handle = svcmgr;
    binder_loop(bs svcmgr_handler);
    return 0;
}

  1. Binder通讯模型

4.1服务端、客户端、ServiceManager、Binder驱动

文章一开头就说过, Binder基于服务端/客户端架构,主要包括服务端、客户端、ServiceManager, Binder驱动这4个部分。以进程A与进程B通讯为例,进程B就是客户端,进程A为服务端。其中服务端、客户端、ServiceManager运行在用户空间,只有Binder驱动运行在内核空间。ServiceManager和Binder驱动是系统提供的,服务端和客户端则由应用程序来实现。

4.1.1 Binder驱动

Binder驱动在系统里对应/dev/binder文件,负责进程间Binder通讯的建立。是服务端、客户端、ServiceManager这3者之间的桥梁。Binder跨进程传递、Binder引用计数管理、数据包跨进程传递等一系列操作都需要Binder提供支持。

4.1.2 ServiceManager

ServiceManager作用很强大,在Binder通讯中,主要作用管理服务端和客户端。

Android系统启动过程中,SystemServer会向BinderDriver注册ServiceManager,BinderDriver自动为ServiceManager创建Binder实体。所有在这之后启动的应用进程都会持有这个Binder的句柄,为0号引用,即所有用户进程的0号引用都指向该Binder。

4.2 使用Binder进行数据传输的具体过程

以下图进程A和进程B的通讯过程举例:

4.2.1 服务端向ServiceManager注册

  • 服务进程向Binder进程发起服务注册
  • Binder驱动将注册请求转发给ServiceManager进程
  • ServiceManager进程添加这个服务进程

该案例中,进程B作为服务端,可以通过第0号引用获得ServiceManager的Binder引用,通过该Binder引用和ServiceManager通讯。进程B会先创建自己的Binder实体,并起个容易记得的名字,如"进程B",将这个Binder实体和名字一起打包,通过第0号引用通知ServiceManager注册一个名为"进程B"的Binder。此时,会由Binder驱动为进程B创建位于内核空间的Binder实体,以及ServiceManager对该Binder实体的引用,ServiceManager可以通过"讲程B"这个名字找到新创建的讲程B的Binder实体。

有一个叫法问题需要统一下,在进程A与进程B通讯的案例中,进程B作为服务端。但是在进程B通过第0号引用向ServiceManager注册时,进程B其实是作为“客户端",ServiceManager是"服务端"。也就是一个进程,比如进程B可能是提供服务的服务端,但至少对于ServiceManager这个进程来说,进程B仍然是一个"客户端"。

4.2.2 客户端获取服务端Binder引用

  • 用户进程向Binder驱动发起获取服务的请求,传递要获取的服务名称Binder驱动
  • 将该请求转发给ServiceManager进程
  • ServiceManager进程查到到用户进程需要的服务进程信息最后
  • 通过Binder驱动将上述服务信息返回个用户进程

作为服务端的进程B已经向ServiceManager注册好,此时作为客户端的进程A也可以通过第0号引用,向ServiceManager请求访问名字叫做"进程B"的Binder引用。

ServiceManager通过客户端发送过来的名字,查到对应的进程B的Binder引用回复给进程A。进程A就可以通过该Binder引用和进程B通讯了。到此为止,Binder驱动为作为服务端的进程B创建了位于内核空间的Binder实体,同时ServiceManager拥有该Binder实体的引用,作为客户端的进程A也拥有该Binder实体的引用。如果接下来有进程C也作为客户端请求进程B的Binder,则最后进程C也会拥有进程B的Binder实体的引用。

4.2.3 使用服务

  • Binder通过内存映射建立数据缓存区
  • 根据ServiceManager查到的服务的进程和数据缓存区 , 数据缓存区和client进程的内存缓存区建立映射
  • client调用copy_from_user数据到内存缓存区
  • 收到binder启动后服务进程根据用户进程要求调用目标方法
  • 服务进程将目标方法的结果返回给用户进程

具体过程整理如下表所示:

4.3 Binder通讯中的代理模式

上面说到作为客户端的进程A通过Binder驱动获取到了作为服务端的进程B的Binder引用,这个Binder引用更准确来说应该称为BinderProxy,也就是进程A获取到的是进程B的Binder代理对象,通过该Binder代理对象,进程A可以调用进程B提供的方法。当然,因为进程A和B毕竟存在进程边界。所以进程A获取到的进程B的Binder代理对象并非可以直接使用进程B提供的方法。进程A只是调用方法时,把请求参数打包成Parcel对象,通过Binder代理对象发给Binder驱动,Binder驱动看到这个Binder代理对象是进程B的Binder代理,就会通知进程B解包Parcel对象,使用传入的请求参数,调用对应的方法,最后把进程B执行完的结果返回给进程A。但对于进程A来说,就像是直接调用了进程B的方法。

具体代码层面:

服务端中的Service给客户端提供Binder对象

  1. 客户端通过AIDL接口中的asInterface()将这个Binder对象转换为代理Proxy并通过它发起RPC请求
  2. client进程的请求数据data通过代理binder对象的transact方法,发送到内核空间,当前线程被挂起
  3. server进程收到binder驱动通知, onTransact(在线程池中进行数据反序列化&调用目标方法)处理客户端请求,并将结果写入reply
  4. Binder驱动将server进程的目标方法执行结果,拷贝到client进程的内核空间
  5. Binder驱动通知client进程,之前挂起的线程被唤醒,并收到返回结果

4.4 好多Binder分不清?

在上面说的服务端、客户端、ServiceManager、Binder驱动这4个部分,他们的代表都是"Binder", Binder一词出现多次,可能有部分同学觉得有点乱,现在统一理一遍:

  • 通常意义:通常我们说"Binder",就是指Android里面的一种跨进程通讯机制;
  • 服务端进程:对服务端进程来说, "Binder"指服务端进程创建的自己的Binder本地对象
  • 客户单进程:对客户端进程来说, "Binder"指客户端进程从ServiceManager拿到的服务端进程里的Binder本地对象,在客户端进程的一个Binder代理对象;
  • 传输过程:在跨进程传输过程中, "Binder"是可以进行跨进程传递的对象; Binder驱动会对具有跨进程传递能力的对象做特殊处理,会自动完成代理对象和本地对象的转换。

4.5 Binder源码分析

本文不涉及源码分析,因为笔者也是还在学习源码中,不过推荐网上找到的一个比较好的系列:

Binder系列

4.6 参考文档

Binder系列

Android Binder原理

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值