ART学习总结

作者: guoqifa | 来源:发表于2016-02-17 15:52 被阅读1418次

    一、概述

    ART是Android平台上的新一代运行时,用来代替dalvik。它主要采用了AOT的方法,在apk安装的时候将dalvikbytecode一次性编译成arm本地指令(但是这种AOT与c语言等还是有本质不同的,还是需要虚拟机的环境支持),这样在运行的时候就无需进行任何解释或编译便可直接执行,节省了运行时间,提高了效率,但是在一定程度上使得安装的时间变长,空间占用变大。(本文所有时序图均基于Android M)


    二、OAT文件

    简单的说,oat文件是嵌套在一个elf文件的格式中的。在elf文件的动态符号表中有三个重要的符号:oatdata、oatexec、oatlastword,分别表示oat的数据区,oat文件中的native code和结束位置。

    guoqifa@guoqifa:~$ readelf -s system@priv-app@Mms@Mms.apk@classes.dex
    Symbol table '.dynsym' contains 4 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000001000 0x95b000 OBJECT  GLOBAL DEFAULT    4 oatdata
         2: 000000000095c000 0xb3a4c0 OBJECT  GLOBAL DEFAULT    5 oatexec
         3: 00000000014964bc     4 OBJECT  GLOBAL DEFAULT    5 oatlastword
    

    这些关系结构在图中说明的很清楚,简单理解就是在oatdata中,保存有原来的dex文件内容,在头部还保留了寻址到dex文件内容的偏移地址和指向对应的oat class偏移,oat class中还保存了对应的native code的偏移地址,这样也就间接的完成了dexbytecode和native code的对应关系。具体请参考:Android ART Oat文件格式简析(上)Android ART Oat文件格式简析(下)Android运行时ART加载OAT文件的过程分析. 也可自行分析 art\runtime\Oat_file.cc文件中OatFile::Setup()函数来进一步学习OAT文件。(下图来源老罗博客)

    oat文件格式

    三、编译

    apk在安装时使用dex2oat来编译。大概流程如下图所示,有两种后端编译模式,可选的是所谓快模式(Quick)或优化模式(Optimizing)。如果没有特别指定的话,编译镜像image使用快模式,而编译一般的应用程序则使用优化模式。

    dex2oat compile

    什么是编译器的后端呢?其实这是LLVM引入的概念。所有的程序先用前端翻译成中间表示层,然后进行优化,最后用后端将优化过的中间表示层代码编译成平台相关的代码。Android虽然没有直接用LLVM编译器(以前也确实用过,但从6.0开始就废弃掉了),但是借鉴了这种编译器的设计结构。值得一提的是,如果使用优化模式,则一定是用PIC模式进行编译。

    更具体的compile时序图如下(该时序图只绘出quick编译模式),可自行分析该流程。


    Compile Image

    四、类加载

    ART类的加载通过ClassLinker::DefineClass()函数来完成。该函数会分别调用InsertClass()、LoadClass()、LinkClass()来执行类的加载。

    InsertClass()主要是把该类插入class_table_中,方便下次FindClass时直接从LookupClass()中返回结果。LoadClass()用来从指定的DEX文件中加载指定的类,并初始化一个Class对象,Class对象包含了一系列的ArtField对象和ArtMethod对象。LinkClass()用来动态绑定虚函数和接口函数。

    类加载完成后,得到的是一个Class对象。这个Class对象关联有一系列的ArtField对象和ArtMethod对象。其中,ArtField对象描述的是成员变量,而ArtMethod对象描述的是成员函数。对于每一个ArtMethod对象,它都有一个解释器入口点和一个本地机器指令入口点。这样,无论一个类方法是通过解释器执行,还是直接以本地机器指令执行,我们都可以以统一的方式来进行调用。

    关于类的加载可参考Android运行时ART加载类和方法的过程分析

    Load Class

    五、方法调用

    Zygote孵化应用进程后便会从入口AndroidRuntime.start()进入运行时,进入Java世界。

    CallStaticVoidMethod

    ART方法调用如上图所示,最终调用到ArtMethod::Invoke()。ArtMethod正是前面类加载章节中提到的ArtMethod对象,“它都有一个解释器入口点和一个本地机器指令入口点”,Invoke()函数正是通过art_quick_invoke_stub()或art_quick_invoke_static_stub()来调用oat文件中的native code的。art_quick_invoke_stub()是平台相关的汇编函数,比如我的机器该函数定义在art/runtime/arch/arm64/quick_entrypoints_arm64.S文件中。更多方法调用的分析请参考Android运行时ART执行类方法的过程分析.

    总结:通过查找相关的oat文件,得到所需要的类和方法,并将其对应的native code的位置放入ArtMethod结构,最后通过Invoke成员完成调用。


    六、JNI调用

    1. Java调用native方法

    ArtMethod对象与真实执行的代码链接的过程主要是通过LinkCode()函数执行的。

    void ClassLinker::LinkCode(ArtMethod* method, const OatFile::OatClass* oat_class,
                               uint32_t class_def_method_index) {
      ......
      if (method->IsNative()) {
        // Unregistering restores the dlsym lookup stub.
        method->UnregisterNative();
       ......
    }
    
    void ArtMethod::UnregisterNative() {
      CHECK(IsNative() && !IsFastNative()) << PrettyMethod(this);
      // restore stub to lookup native pointer via dlsym
      RegisterNative(GetJniDlsymLookupStub(), false);
    }
    

    GetJniDlsymLookupStub()函数返回平台相关的art_jni_dlsym_lookup_stub()汇编函数指针,RegisterNative()将该汇编函数指针注册到ArtMethod中,以上是链接过程。

    待JNI调用时便会执行到汇编art_jni_dlsym_lookup_stub()函数,该函数会继续调用artFindNativeMethod()函数。

    art_jni_dlsym_lookup_stub
    extern "C" void* artFindNativeMethod(Thread* self) {
      DCHECK_EQ(self, Thread::Current());
    #endif
      Locks::mutator_lock_->AssertNotHeld(self);  // We come here as Native.
      ScopedObjectAccess soa(self);
    
      ArtMethod* method = self->GetCurrentMethod(nullptr);
      DCHECK(method != nullptr);
    
      // Lookup symbol address for method, on failure we'll return null with an exception set,
      // otherwise we return the address of the method we found.
      void* native_code = soa.Vm()->FindCodeForNativeMethod(method);
      if (native_code == nullptr) {
        DCHECK(self->IsExceptionPending());
        return nullptr;
      } else {
        // Register so that future calls don't come here
        method->RegisterNative(native_code, false);
        return native_code;
      }
    }
    

    artFindNativeMethod()函数就是查找到相应方法的native code,然后再次注册到ArtMethod中,这样以后再执行的时候就直接跳到了native code执行了。

    2. native调用Java

    native调用Java是通过JNIEnv->FindClass()、JNIEnv->GetStaticMethodID()、JNIEnv->CallVoidMethod()来查找类,得到相应的方法的ID,然后通过此ID去调用。最终如上面第五章方法调用时序图所示,调用的是ArtMethod::Invoke()。即JNI的这些API其实还是做了一遍ART的类加载和初始化及调用的过程。


    七、参考文章

    1.也来看看Android的ART运行时
    2.Android运行时ART简要介绍和学习计划
    3.Android ART Oat文件格式简析

    相关文章

      网友评论

        本文标题:ART学习总结

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