Binder 概述

Android 应用程序中的很多功能都会用到进程间通信(IPC),如 WebView、音视频播放、大图浏览、推送都会创建一个单独的进程。而系统服务,诸如电话、闹钟、AMS 等也是由系统创建的多进程。

使用多进程有如下优点:

  • 可以开辟更多可用内存。一个进程分配一个 JVM,每个 JVM 的大小在同一个系统版本中都是固定的(可以通过 getprop dalvik.vm.heapsize 查看),创建多进程就会有多个 JVM 的内存可用
  • 风险隔离。将有风险的操作放在多进程中,即便出问题挂掉了也不会影响主进程

1、概述

1.1 Binder 是什么

最容易想到的就是,Binder 是 Android 系统采用的一种线程间通信机制,但除此之外,在不同的场景下,它还有不同的解释:

  1. 从机制角度,Binder 是一种进程间通信机制;
  2. 从通信模型角度,Binder 是一种虚拟的物理驱动设备;
  3. 从代码角度,在应用层,Binder 是 IBinder 接口的实现类;在 native 层有 Binder.cpp 和 binder.c;在 kernel 层也有一个 binder.c。

1.2 为什么是 Binder

为什么在 Linux 已经具备管道、信号量、Socket 和共享内存等 IPC 方式的情况下,还要再为 Android 定制一个 Binder 呢?需要先了解 Android 的进程空间划分。

1.2.1 进程空间划分 & IPC 原理


Android 进程只能运行在自己进程所拥有的虚拟地址空间,每个进程的空间可以分为用户空间与内核空间。在进程之间,用户空间的数据不可共享,而内核空间的数据可以共享。所以两个进程的用户空间不能直接传输数据,只能通过共享的内核空间进行通信。

内存被操作系统划分成两块:用户空间和内核空间,用户空间是用户程序代码运行的地方,内核空间是内核代码运行的地方。为了安全,它们是隔离的,即使用户的程序崩溃了,内核也不受影响。

用户空间和内核空间说的是虚拟内存,它们都需要映射到物理内存(内存条、磁盘)上。所有进程的内核空间映射的都是同一块物理内存,但是用户空间映射的物理内存是隔开的。对于 32 位系统来说,地址空间大小为 4G,用户空间会分配 3G,内核空间分配 1G(可通过参数调整);对于 64 位系统而言,低位的 0~47 位是有效的可变地址,寻址空间为 256T,一般用户空间高 16 位全补 0,内核空间高 16 位全补 1。

传统的 IPC 方式是由进程 A 发起通信,将数据从自己的用户空间先通过 copy_from_user 拷贝到内核空间,再由进程 B 去内核空间通过 copy_to_user 将数据拷贝到用户空间。整个数据传输过程要进行两次数据拷贝,效率比较低下。

1.2.2 Binder 通信原理

与传统 IPC 相比,Binder 通过 mmap() 进行内存映射,实现了内核空间与接收进程的内存共享,使得 IPC 过程仅需一次数据拷贝:

Binder通信原理
发送数据时仍然是从发送进程的用户空间通过 copy_from_user 拷贝到内核空间,但是接收进程的用户空间与内核空间的地址通过 mmap() 进行了映射,实现了物理地址的共享,这样接收进程不需要进行 copy_to_user 操作就可以在其用户空间内收到发送进程传来的数据。

最后来看下 Binder 与其它 IPC 方式的对比:

Binder与其它IPC对比

Binder 的性能更好更安全:

  • 性能:通信过程仅需一次数据拷贝,仅次于无需拷贝的共享内存,但是共享内存会面临多线程环境,需要同步机制,使用起来麻烦
  • 安全:Binder 由系统分配 UID,更安全,而其他 IPC 方式是通过判定 PID,并不靠谱。并且 Binder 同时支持实名服务(如系统服务,AMS、PMS 等)和匿名服务(用户自己创建的 Service 是匿名服务,其他进程拿不到)

2、Binder 通信模型

Binder通信模型

在 Binder 通信模型中有 4 个角色:

  1. Client 进程:发送数据的进程。
  2. Server 进程:接收数据的进程。
  3. ServiceManager:接受 Server 进程的注册,并保存 Server 信息。Client 要向特定的 Server 发起通信时,需要先去 ServiceManager 中获取 Server 实例。本身是一个特殊的 Binder。
  4. Binder 驱动:前三者互相通信的枢纽,类似一个中转站,完成数据在各个进程间的传递。

需要注意的点:

  1. 前三者都是在用户空间,而 Binder 驱动在内核空间。
  2. 看前面 ServiceManager 的描述,似乎它与 Client、Server 之间有直接联系,但实际上并没有,它们之间的联系都是通过与 Binder 驱动通信实现的。
  3. Client 与 Server 需要应用开发者自己实现,而 ServiceManager 和 Binder 驱动已经由系统实现。

我们再从 Android 系统层次结构的角度看一下 Binder 通信模型:

Framework 层我们能看到,Client 进程并不是直接操作的 Binder 对象,而是一个持有 Binder 引用的代理对象 BinderProxy,通过它去找到 Binder 本体再执行操作。

在 Native 层,BpBinder(客户端)和 BBinder(服务端)都是 IBinder 的派生类,BBinder 需要向 service_manager 先注册服务,而 BpBinder 需要通过 service_manager 获取服务,然后才能使用服务。它们之间的联系用虚线画出,表示它们之间并不是直接通信,而都是通过 ioctl 操作与 Binder 驱动通信,由 Binder 驱动将数据转发给接收对象。

Binder 源码的目录结构正是根据以上层次结构设计的:

/framework/base/core/java/               (Java)
/framework/base/core/jni/                (JNI)
/framework/native/libs/binder            (Native)
/framework/native/cmds/servicemanager/   (Native)
/kernel/drivers/staging/android          (Driver)

1、2属于 Java Framework 范畴,具体文件:

/framework/base/core/java/android/os/  
    - IInterface.java
    - IBinder.java
    - Parcel.java
    - IServiceManager.java
    - ServiceManager.java
    - ServiceManagerNative.java
    - Binder.java  


/framework/base/core/jni/    
    - android_os_Parcel.cpp
    - AndroidRuntime.cpp
    - android_util_Binder.cpp (核心类)

3、4属于 Native Framework:

/framework/native/libs/binder
    - IServiceManager.cpp
    - BpBinder.cpp
    - Binder.cpp
    - IPCThreadState.cpp (核心类)
    - ProcessState.cpp  (核心类)

/framework/native/include/binder/
    - IServiceManager.h
    - IInterface.h

/framework/native/cmds/servicemanager/
    - service_manager.c
    - binder.c

5 属于 Kernel:

/kernel/drivers/staging/android/
    - binder.c
    - uapi/binder.h

源码流程的话,先从 Native 层的 service_manager 看起,因为它是 BpBinder 和 BBinder 正常工作的前提。

3、mmap 是什么

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

mmap示意图

对文件进行 mmap,会在进程的虚拟内存分配地址空间,创建映射关系。实现这样的映射关系后,就可以采用指针的方式读写操作这一段内存,而系统会自动回写到对应的文件磁盘上。

mmap:用户空间与内核空间指的都是虚拟内存,它们都需要映射到真正的物理内存设备上。由于内核空间与物理内存本就存在映射关系,mmap 将接收进程的用户空间与内核空间进行映射,其实也是映射到了同一块物理内存上,这样内核空间的数据发生变化时,物理内存与用户空间映射的虚拟内存就都知道了数据的变化。mmap 能够让虚拟内存和指定的物理内存直接联系起来。对于 Binder 来说,接收进程与内核空间通过 mmap 共享同一块物理内存省去了一次拷贝

关于虚拟内存和物理内存的映射有一个很有趣的类比:物理内存就像地球,而虚拟内存则像地球仪,二者存在一个映射关系。应用只能操作虚拟地址,不能直接操作物理地址,但是数据最终还是存在物理地址上的;就像我们只能把玩地球仪,但把玩不了地球,可是我们的物品最终还是放在地球上,而不是放在地球仪上……

所有的系统资源管理都是在内核空间中完成的。比如读写磁盘文件,分配回收内存,从网络接口读写数据等等。用户空间通过系统调用(syscall)让内核空间完成这些功能。
写文件过程
对文件进行 mmap,会在进程的虚拟内存分配地址空间,创建映射关系。实现这样的映射关系后,就可以采用指针的方式读写操作这一段内存,而系统会自动回写到对应的文件磁盘上。比如说,用户空间是不可以直接操作文件的,因为用户空间用的是虚拟内存而不是物理内存,

写文件分三步:

  1. 调用 write,告诉内核需要写入数据的开始地址与长度
  2. 内核将数据拷贝到内核缓存
  3. 由操作系统调用,将数据拷贝到磁盘,完成写入

常规文件操作使用页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。

4、Binder JNI 启动流程

Android 系统开机过程中,init 进程通过解析 init.zygote.rc 文件创建 Zygote 进程,Zygote 进程对应的可执行程序为 app_process,对应的源文件为 app_main.cpp:

system\core\rootdir\init.zygote32.rc:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd

Zygote 启动时会进入其 main(),判断是否是 Zygote 命令:

frameworks\base\core\jni\AndroidRuntime.cpp:

int main(int argc, char* const argv[]) {
    // Parse runtime arguments.  Stop at first unrecognized option.
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    String8 niceName;
    String8 className;

    ++i;  // Skip unused "parent dir" argument.
    while (i < argc) {
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) {
            // 判断是不是 zygote,是的话就把 zygote 变量置为 true
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;
        } else if (strcmp(arg, "--application") == 0) {
            application = true;
        } else if (strncmp(arg, "--nice-name=", 12) == 0) {
            niceName.setTo(arg + 12);
        } else if (strncmp(arg, "--", 2) != 0) {
            className.setTo(arg);
            break;
        } else {
            --i;
            break;
        }
    }
    
    // zygote 为 true 就会启动 runtime 命令(runtime 是 AppRunTime,继承自
    // AndroidRuntime),调用 AndroidRuntime 的 start()
    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
        return 10;
    }
}

接下来会调用到 AndroidRuntime 的 start(),该函数仅需关注一个地方:

frameworks\base\core\jni\AndroidRuntime.cpp:

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    /*
     * Register android functions.
     */
    // 注册 JNI
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android natives\n");
        return;
    }
}

int AndroidRuntime::startReg(JNIEnv* env)
{
    /*
     * This hook causes all future threads created in this process to be
     * attached to the JavaVM.  (This needs to go away in favor of JNI
     * Attach calls.)
     */
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);

    ALOGV("--- registering native functions ---\n");

    /*
     * Every "register" function calls one or more things that return
     * a local reference (e.g. FindClass).  Because we haven't really
     * started the VM yet, they're all getting stored in the base frame
     * and never released.  Use Push/Pop to manage the storage.
     */
    env->PushLocalFrame(200);

    // 注册 JNI 这样 Java 才能调用 Native 方法
    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
        env->PopLocalFrame(NULL);
        return -1;
    }
    env->PopLocalFrame(NULL);

    //createJavaThread("fubar", quickTest, (void*) "hello");

    return 0;
}

// 
static int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env)
{
    for (size_t i = 0; i < count; i++) {
        if (array[i].mProc(env) < 0) {
#ifndef NDEBUG
            ALOGD("----------!!! %s failed to load\n", array[i].mName);
#endif
            return -1;
        }
    }
    return 0;
}

NDEBUG 是前面定义好的函数指针:

#ifdef NDEBUG
    #define REG_JNI(name)      { name }
    struct RegJNIRec {
        int (*mProc)(JNIEnv*);
    };
#else
    #define REG_JNI(name)      { name, #name }
    struct RegJNIRec {
        int (*mProc)(JNIEnv*);
        const char* mName;
    };
#endif

register_jni_procs() 参数 array[] 是一个常量:

static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_com_android_internal_os_RuntimeInit),
    REG_JNI(register_android_os_SystemClock),
    REG_JNI(register_android_util_EventLog),
    REG_JNI(register_android_util_Log),
    ...
    // Binder JNI 注册
    REG_JNI(register_android_os_Binder),
}

register_android_os_Binder() 是一个函数,在 android_util_Binder 文件中:

frameworks\base\core\jni\android_util_Binder.cpp:

int register_android_os_Binder(JNIEnv* env)
{
	// 注册 Binder
    if (int_register_android_os_Binder(env) < 0)
        return -1;
    // 注册 BinderInternal
    if (int_register_android_os_BinderInternal(env) < 0)
        return -1;
	// 注册 BinderProxy
    if (int_register_android_os_BinderProxy(env) < 0)
        return -1;

    jclass clazz = FindClassOrDie(env, "android/util/Log");
    gLogOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
    gLogOffsets.mLogE = GetStaticMethodIDOrDie(env, clazz, "e",
            "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I");

    clazz = FindClassOrDie(env, "android/os/ParcelFileDescriptor");
    gParcelFileDescriptorOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
    gParcelFileDescriptorOffsets.mConstructor = GetMethodIDOrDie(env, clazz, "<init>",
                                                                 "(Ljava/io/FileDescriptor;)V");

    clazz = FindClassOrDie(env, "android/os/StrictMode");
    gStrictModeCallbackOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
    gStrictModeCallbackOffsets.mCallback = GetStaticMethodIDOrDie(env, clazz,
            "onBinderStrictModePolicyChange", "(I)V");

    return 0;
}

三个注册的过程类似,以注册 Binder 为例:

// 为了让 Java 和 Native 可以互相调用
static int int_register_android_os_Binder(JNIEnv* env)
{
    jclass clazz = FindClassOrDie(env, kBinderPathName);

    // 让 Native 可以调用 Java 方法
    gBinderOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
    gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact", "(IJJI)Z");
    gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");

    // 让 Java 可以调用 Native,gBinderMethods 是方法映射关系。
    return RegisterMethodsOrDie(
        env, kBinderPathName,
        gBinderMethods, NELEM(gBinderMethods));
}

类似于反射,保存 Java 方法的 mClass、mExecTransact、mObject 信息,以便 Native 调用 Java 方法。最后调用的 RegisterMethodsOrDie() 中的参数 gBinderMethods 是 Java 层方法名与对应的 Native 层的方法名:

static const JNINativeMethod gBinderMethods[] = {
     /* name, signature, funcPtr */
    { "getCallingPid", "()I", (void*)android_os_Binder_getCallingPid },
    { "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid },
    { "clearCallingIdentity", "()J", (void*)android_os_Binder_clearCallingIdentity },
    { "restoreCallingIdentity", "(J)V", (void*)android_os_Binder_restoreCallingIdentity },
    { "setThreadStrictModePolicy", "(I)V", (void*)android_os_Binder_setThreadStrictModePolicy },
    { "getThreadStrictModePolicy", "()I", (void*)android_os_Binder_getThreadStrictModePolicy },
    { "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
    { "init", "()V", (void*)android_os_Binder_init },
    { "destroy", "()V", (void*)android_os_Binder_destroy },
    { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable }
};

Binder JNI 注册的目的就是让 Java 和 native 能够互相调用。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值