【Android面试-Android-V06】Binder

21 阅读7分钟

Binder

1. Linux下传统的进程间通信

Liunx 中跨进程通信涉及到的一些基本概念:

  • 进程隔离
  • 进程空间划分:用户空间(User Space)/内核空间(Kernel Space)
  • 系统调用:用户态/内核态

1. 1 进程隔离:操作系统中,进程间的内存不共享。

内核空间(Kernel)是系统内核运行的空间,用户空间(User Space)是用户程序运行的空间。为了保证安全性,它们之间是隔离的。

Linux 使用两级保护机制:0 级供系统内核使用,3 级供用户程序使用。

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

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

系统调用主要通过如下两个函数来实现:

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

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

1.2 linux 传统IPC方式

  • 管道:在创建时分配一个page大小的内存,缓存区大小比较有限;半双工,一般父子进程使用
  • 消息队列:信息复制两次,额外的CPU消耗;不合适频繁或信息量大的通信;
  • 共享内存:无须复制,共享缓冲区直接附加到进程虚拟地址空间,速度快;但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决;
  • Socket:作为更通用的接口,传输效率低,主要用于不同机器或跨网络的通信;全双工
  • Signal:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 不适用于信息交换,更适用于进程中断控制,比如非法内存访问,杀死某个进程等;kill -9 单向 不能带参数
传统IPC机制的通信原理(2次内存拷贝)
  1. 发送方进程通过系统调用(copy_from_user)将要发送的数据存拷贝到内核缓存区中。
  2. 接收方开辟一段内存空间,内核通过系统调用(copy_to_user)将内核缓存区中的数据拷贝到接收方的内存缓存区。

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

传统IPC机制存在2个问题:
  1. 需要进行2次数据拷贝,第1次是从发送方用户空间拷贝到内核缓存区,第2次是从内核缓存区拷贝到接收方用户空间。
  2. 接收方进程不知道事先要分配多大的空间来接收数据,可能存在空间上的浪费。

2.Binder

2.1为什么设计Binder?

  • 效率
  • 安全性
  • 稳定性
  1. 效率:传输效率主要影响因素是内存拷贝的次数,拷贝次数越少,传输速率越高。对于Binder来说,数据从发送方的缓存区拷贝到内核的缓存区,而接收方的缓存区与内核的缓存区是映射到同一块物理地址的,节省了一次数据拷贝的过程,如图:

共享内存不需要拷贝,Binder的性能仅次于共享内存。

  1. 稳定性:Binder基于C/S架构 ,Server端与Client端相对独立,稳定性较好。共享内存在处理并发同步问题时,容易出现死锁和资源竞争,稳定性较差。Socket虽然是基于C/S架构的,但是它主要是用于网络间的通信且传输效率较低。。
  2. 安全性:传统Linux IPC的接收方无法获得对方进程可靠的UID/PID,从而无法鉴别对方身份;而Binder机制为每个进程分配了UID/PID,且在Binder通信时会根据UID/PID进行有效性检测。

2.2 一次完整的 Binder IPC 通信过程:

  • 首先 Binder 驱动在内核空间创建一个数据接收缓存区。
  • 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系。
  • 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。

image.png

代码流程

image.png

从Client端开始

一个aidl生成java文件的transact方法,mRemote是一个IBinder对象

mRemote.transact(Stub.TRANSACTION_stopSmartLedFlashing, _data, _reply, 0);

BinderProxy实现IBinder,直接到native层

final class BinderProxy implements IBinder {
    public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
   
        return transactNative(code, data, reply, flags);
    }

frameworks/base/core/jni/android_util_Binder.cpp

static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
    jint code, jobject dataObj, jobject replyObj, jint flags)
{
    ...
    //将java Parcel转为c++ Parcel
    Parcel* data = parcelForJavaObject(env, dataObj);
    Parcel* reply = parcelForJavaObject(env, replyObj);
​
    //gBinderProxyOffsets.mObject中保存的是new BpBinder(handle)对象
    IBinder* target = (IBinder*) env->GetLongField(obj, gBinderProxyOffsets.mObject);
    ...
​
    //此处便是BpBinder::transact()【见小节2.7】
    status_t err = target->transact(code, *data, reply, flags);
    ...
​
    //最后根据transact执行具体情况,抛出相应的Exception
    signalExceptionForError(env, obj, err, true , data->dataSize());
    return JNI_FALSE;
}

2.3 Binder简述

Binder 是基于 C/S 架构 ,由Client、Server、ServiceManager、Binder Driver组成。其中 Client、Server、ServiceManager 运行在用户空间,Binder 驱动运行在内核空间。

Service Manager 和 Binder 驱动由系统提供,而 Client、Server 由应用程序来实现。Client、Server 和 ServiceManager 均是通过系统调用 open、mmap 和 ioctl 来访问设备文件 /dev/binder,从而实现与 Binder 驱动的交互来间接的实现跨进程通信。

  • 从进程间通信的角度看,Binder 是一种进程间通信的机制;
  • 从 Server 进程的角度看,Binder 指的是 Server 中的 Binder 实体对象;
  • 从 Client 进程的角度看,Binder 指的是 Binder 代理对象,是 Binder 实体对象的一个远程代理;
  • 从传输过程的角度看,Binder 是一个可以跨进程传输的对象;Binder 驱动会对这个跨越进程边界的对象对一点点特殊处理,自动完成代理对象和本地对象之间的转换。

image

  • Server&Client:服务器&客户端。在Binder驱动和Service Manager提供的基础设施上,进行Client-Server之间的通信。
  • ServiceManager(如同DNS域名服务器)服务的管理者,将Binder名字转换为Client中对该Binder的引用,使得Client可以通过Binder名字获得Server中Binder实体的引用。
  • Binder驱动(如同路由器):负责进程之间binder通信的建立,计数管理以及数据的传递交互等底层支持。

最后,结合Android跨进程通信:图文详解 Binder机制 的总结图来综合理解一下:

2.4 如何启动Binder机制

打开binder驱动

内存映射打开缓冲区

启动binder线程

frameworks/native/cmds/servicemanager/service_manager.c

int main(){
    bs = binder_open(128*1024);  
    if (binder_become_context_manager(bs)) {
        return -1;
    }
    binder_loop(bs, svcmgr_handler);
    return 0;
}

还不懂Binder的原理?你损失了一张腾讯offer