Dalvik之非常简介

作者: LSteven | 来源:发表于2018-03-25 19:46 被阅读0次

    最近对应用层有些懈怠,决定深入一点从虚拟机研究入手,增加一些难度hhhh

    分析老罗的代码为主,建议大家都仔细学习一下,以学习记录思想。

    一张图可以说明大概了。

    image.png

    启动过程

    在Android系统中,应用程序进程都是由Zygote进程孵化出来的。Zygote进程在启动时会创建一个Dalvik虚拟机实例,每当它孵化一个新的应用程序进程时,都会将这个Dalvik虚拟机实例复制到新的应用程序进程里面去,从而使得每一个应用程序进程都有一个独立的Dalvik虚拟机实例。

    JavaVMExt

    虚拟机实例。拥有一个函数表FuncTable--gInvokeInterface,它是线程与虚拟机交互的接口。更具体的说,应该是整个进程与虚拟机交互的接口。例如:

    • AttachCurrentThread
    • DestroyJavaVM

    当前线程可以通过这个接口挂载到虚拟机中。

    JNIEnvExt

    JNIEnvExt通过调用函数dvmCreateJNIEnv来完成的。每一个Dalvik虚拟机实例会有一个JNI环境列表,用envList表示。首先说Zygote中的主线程,在创建虚拟机时,就会在虚拟机中创造一个JNI环境。每一个AttachDalvik虚拟机中去的线程都有一个对应的JNIEnvExtJNIEnvExt也有自己的函数表gNativeInterface,这是C++调用Java的本地接口表。例如FindClass,就是C++希望得到Java中的某个类。

    其实怎么说,我们首先要明确一个事实,Dalvik虚拟机实例是用来执行Java代码的,而JNI代码其实是由本地操作系统环境来执行的。所以当本地Native代码希望获取Java中的某些信息时,就需要先获取当前线程的JNI环境(JNIEnv),然后调用它的函数表接口来获取。

    注册核心方法

    启动时还需要预先注册一些Android核心类JNI方法JNI方法一般都定义在so中,所以系统会先加载so,并注册JNI方法。注册JNI方法是什么意思呢,可以先大致说一下整个过程,跟linux加载so的过程类似,首先dlopen加载so获取句柄,然后dlsym找到定义的JNI_Onload方法,最后通过JNIEnv进入dalvik虚拟机内部进行registerNativeMethod注册JNI方法。

    image.png

    每一个用来实现JNI方法的so文件都应该定义有一个名称为JNI_OnLoad的函数:

    jint JNI_OnLoad(JavaVM* vm, void* reserved);  
    

    注册方法时,通过
    (*env)->RegisterNatives进入虚拟机内部进行注册。我们首先会获得一个Method对象method,用来描述要注册的JNI方法所对应的Java类成员函数,当JNI方法未被注册时,method->nativeFunc指向dvmResolveNativeMethod。当真正注册时,会为方法选择一个合适的bridge函数。即设置method->nativeFunc = xxxbridge

    调用JNI方法

    当未来在Java层调用JNI方法,虚拟机会通过Bridge进行调用

    这些Bridge函数实际上仍然不是直接调用地调用JNI方法的,这是因为Dalvik虚拟机是可以运行在各种不同的平台之上,而每一种平台可能都定义有自己的一套函数调用规范,也就是所谓的ABI(Application Binary Interface),这是一个API(Application Programming Interface)不同的概念。ABI是在二进制级别上定义的一套函数调用规范,例如参数是通过寄存器来传递还是堆栈来传递,而API定义是一个应用程序编程接口规范。换句话说,API定义了源代码和库之间的接口,因此同样的代码可以在支持这个API的任何系统中编译 ,而ABI允许编译好的目标代码在使用兼容ABI的系统中无需改动就能运行。

    为了使得运行在不同平台上的Dalvik虚拟机能够以统一的方法来调用JNI方法,这些Bridge函数使用了一个libffi库,它的源代码位于external/libffi目录中。Libffi是一个开源项目,用于高级语言之间的相互调用的处理,它的实现机制可以进一步参考http://www.sourceware.org/libffi/

    总而言之,Bridge会使用函数dvmPlatformInvoke通过libffi库来调用对应的JNI方法,以屏蔽Dalvik虚拟机运行在不同目标平台的细节。

    执行过程

    虚拟机实例启动完毕后,肯定会有一个main函数来执行。因此zygote会通过JNIEnv.CallStaticVoidMethod来执行main函数。这里是不是跟我们前面说的相呼应

    以当本地native代码希望获取java中的某些信息时,就需要先获取当前线程的JNI环境(JNIEnv),然后调用它的函数表接口来获取。

    native方法

    如果方法是native方法,那么Method->nativeFunc就是方法的地址,直接执行就ok。走前面讲的JNI方法的Bridge执行过程。

    Java方法

    如果方法是Java方法,就通过dvmInterpret执行。真正发挥虚拟机的解析作用,首先,虚拟机实例有三种执行Java方法的模式:

    • Portable 可移植模式
    • Fast 快速模式
    • JIT JIT模式

    执行过程看起来挺简单,

    pc程序计数器指向的就是当前要执行的Java函数的方法区,在一个无限while循环中,通过FETCH宏依次获得当前程序计数器(pc)中的指令inst,并且通过宏INST_INST获得指令inst的类型,最后就switch到对应的分支去解释指令inst。

    以Zygote进程为例,Dalvik虚拟机解释器就是以com.android.internal.os.ZygoteInit类的静态成员函数main为入口点执行,然后在一个Socket上进行循环,用来等待和处理ActivityManagerService服务向它发送创建新应用程序进程的请求,直至系统退出为止。又以Android应用程序进程为例,Dalvik虚拟机解释器就是以android.app.ActivityThread类的静态成员函数main为入口点执行,然后在一消息队列上进行循环,用来等待和处理主线程的消息,直到应用程序退出为止。

    进程与线程

    最后说一下虚拟机中的进程与线程。Dalvik虚拟机进程实际上就是通常我们所说的Android应用程序进程。通过Zygotefork完成。一个Dalvik虚拟机进程实际上就是一个Linux进程.在Java代码中,我们可以通过java.lang.Thread类的成员函数start来创建一个Dalvik虚拟机线程,在Dalvik虚拟机中对应有一个Native层的Thread对象。它本质通过本地OS的函数pthread_create来创建一个线程,Dalvik虚拟机线程实际上就是本地操作系统线程。

    同时如果该线程是在Java层创建,或者在Native层创建但需要与Java层交互,就会与前面的主线程类似,通过dvmCreateJNIEnv来为新创建的Dalvik虚拟机线程创建一个JNI环境。同时,线程需要执行时,就会通过JNIEnv中的接口dvmCallMethod让虚拟机执行Java层中的Thread.run

    虚拟机线程会有三个重要的引用表:

    • JNI本地引用表。JNI方法引用Java对象时,会往当前Dalvik虚拟机线程的JNI方法本地引用表添加一个引用,以便它们不会被GC回收。
    • Dalvik虚拟机内部引用表。在Dalvik虚拟机内部为线程创建一些对象,这些对象需要添加到一个Dalvik虚拟机内部引用表中去,以便在线程退出时,可以对它们进行清理。
    • Monitor引用表。同步的一些东西

    相关文章

      网友评论

        本文标题:Dalvik之非常简介

        本文链接:https://www.haomeiwen.com/subject/vmfkcftx.html