dalvik学习随笔

153 阅读4分钟

                                                        dalvik创建

      所谓dalvik虚拟机就是在内存的一个单利对象gdvm 是一个DvmGlobals实例,用来收集当前进程所有虚拟机相关的信息,不过他掌管着所有对象的生成,线程管理,gc管理,资源的预加载。jni环境管理,异常管理,dex验证等等

DvmGlobals  gDvm.vmList管理进程的虚拟机是 JavaVMExt实例

    每一个Dalvik虚拟机实例还有一个JNI环境列表,保存在对应的JavaVMExt对象的成员变量envList中

JavaVMExt.envList 是一个JNIEnvExt双向,列表即JNI环境列表,其中,每一个Attach到Dalvik虚拟机中去的线程都有一个对应的JNIEnvExt,列表中的第一个JNIEnvExt对象描述的是主线程的JNI环境。

JNIEnvExt的funcTable 由全局变量gNativeInterface来描述

当我们需要在C/C++代码在中调用Java函数,就要用到这个本地接口表,例如:

  1. 调用函数FindClass可以找到指定的Java类;
  2. 调用函数GetMethodID可以获得一个Java类的成员函数,并且可以通过类似CallObjectMethod函数来间接调用它;
  3. 调用函数GetFieldID可以获得一个Java类的成员变量,并且可以通过类似SetIntField的函数来设置它的值;
  4. 调用函数RegisterNatives和UnregisterNatives可以注册和反注册JNI方法到一个Java类中去,以便可以在Java函数中调用;
  5. 调用函数GetJavaVM可以获得当前进程中的Dalvik虚拟机实例。

dalvik创建的时候会启动对象的生成记录,线程管理,gc管理,资源的预加载。jni环境管理,异常管理,dex验证等等

                                       dalvik运行

Dalvik虚拟机在Zygote进程中启动完成之后,就会获得一个JavaVM实例和一个JNIEnv实例。其中,获得的JavaVM实例就是用来描述Zygote进程的Dalvik虚拟机实例,而获得的JNIEnv实例描述的是Zygote进程的主线程的JNI环境。紧接着,Zygote进程就会通过前面获得的JNIEnv实例的成员函数CallStaticVoidMethod来调用com.android.internal.os.ZygoteInit类的静态成员函数main。这就相当于是将com.android.internal.os.ZygoteInit类的静态成员函数main作为Java代码的入口点。
最终会调到 dvmInterpretStd和dvmMterpStd这两函数可以理解为dalvik的解释器入口,解释器是虚拟机的核心模块,它的性能关乎到整个虚拟机的性能。Dalvik虚拟机的解释器开始的时候都是以C语言来实现的,后来为了提到性能,就改成以汇编语言来实现。注意,无论Dalvik虚拟机的解释器是以C语言来实现,还是以汇编语言来实现,它们的源代码都是以一种模块化方法来自动生成的,并且能够根据某一个特定的平台进行优化。

主要执行方法

GOTO_TARGET(invokeMethod, bool methodCallRange, const Method* _methodToCall,  
    u2 count, u2 regs)  
    {  
        STUB_HACK(vsrc1 = count; vdst = regs; methodToCall = _methodToCall;);  
        StackSaveArea* newSaveArea;   
        u4* newFp;   
        ......  
  
        newFp = (u4*) SAVEAREA_FROM_FP(fp) - methodToCall->registersSize;    
        newSaveArea = SAVEAREA_FROM_FP(newFp);  
        ......  
  
        newSaveArea->prevFrame = fp;  
        newSaveArea->savedPc = pc;  
        ......  
  
        if (!dvmIsNativeMethod(methodToCall)) {  
            /* 
             * "Call" interpreted code.  Reposition the PC, update the 
             * frame pointer and other local state, and continue. 
             */  
            curMethod = methodToCall;  
            methodClassDex = curMethod->clazz->pDvmDex;  
            pc = methodToCall->insns;  
            fp = self->curFrame = newFp;  
            ......  
              
            FINISH(0);                              // jump to method start  
        } else {  
            /* set this up for JNI locals, even if not a JNI native */  
            ......  
  
            self->curFrame = newFp;  
            ......  
              
            /* 
             * Jump through native call bridge.  Because we leave no 
             * space for locals on native calls, "newFp" points directly 
             * to the method arguments. 
             */  
            (*methodToCall->nativeFunc)(newFp, &retval, methodToCall, self);  
            ......  
               
            if (true /*invokeInstr >= OP_INVOKE_VIRTUAL && 
                invokeInstr <= OP_INVOKE_INTERFACE*/)  
            {  
                FINISH(3);  
            }   
  
            ......  
        }  
  
        ......  
    }  
GOTO_TARGET_END  

分支invokeMethod首先是为当前要解释的成员函数methodToCall创建一个新的栈帧newFp,接着再通过调用函数dvmIsNativeMethod来判断成员函数methodToCall是否是一个JNI方法。

在新创建的栈帧newFp中,分别在其成员变量prevFrame和savedPc中保存了当前栈帧fp和当前程序计数器pc的值,这样使得在调用完成员函数methodToCall之后,可以返回到当前正在执行的成员函数的下一条指令中去,以及恢复当前线程的栈帧。

如果成员函数methodToCall不是一个JNI方法,那么就说明接下来仍然是要通过Dalvik虚拟机解释器来执行它。不过这时候需要将当前线程激活的栈帧fp设置为newFp,以及将程序计数器pc指向成员函数methodToCall的方法区。最后通过宏FINISH(0)来跳出当前分支,实际上就是跳出前面Step 6的switch语句,然后重复执行while循环语句。此时传递给宏FINISH的参数为0,表示不需要调整程序计数器pc的值,因为前面已经调整过了

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