Skip to content

Dalvik虚拟机启动过程 #7

@NazcaLines

Description

@NazcaLines

虚拟机的启动

在AndroidRuntime.start()函数中,系统调用了startVm函数,在startVm函数中,系统设置了启动dalvik虚拟机的参数,然后调用Jni.cpp#JNI_CreateJavaVM正式开始创建工作。我们的分析就是从这里开始。

1.首先将 DvmGlobals 结构清零,这个结构是存储一个进程中的dalvik虚拟机的信息。

struct JavaVMExt {
    const struct JNIInvokeInterface* funcTable;     /* must be first */

    const struct JNIInvokeInterface* baseFuncTable;

    /* head of list of JNIEnvs associated with this VM */
    JNIEnvExt*      envList;
    pthread_mutex_t envListLock;
};
 JavaVMExt* pVM = (JavaVMExt*) calloc(1, sizeof(JavaVMExt)); //JavaVMExt
 pVM->funcTable = &gInvokeInterface;
 pVM->envList = NULL;
static const struct JNIInvokeInterface gInvokeInterface = {
    NULL,
    NULL,
    NULL,

    DestroyJavaVM,
    AttachCurrentThread,
    DetachCurrentThread,

    GetEnv,

    AttachCurrentThreadAsDaemon,
};

由代码可以知道,gInvokeInterface是一个函数跳转表,following:

struct JNIInvokeInterface {
    void*       reserved0;
    void*       reserved1;
    void*       reserved2;

    jint        (*DestroyJavaVM)(JavaVM*);
    jint        (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*);
    jint        (*DetachCurrentThread)(JavaVM*);
    jint        (*GetEnv)(JavaVM*, void**, jint);
    jint        (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);
};

每一个项都是一个指针。

2.接着开始检查每一个参数了。

3.实际上JavaVm结构就是一个跳转表JNIInvokeInterface,所以有

gDvmJni.jniVm = (JavaVM*) pVM; //JavaVMExt* pVM
struct _JavaVM {
    const struct JNIInvokeInterface* functions;
};

这里有一个强制类型转换。

为什么不直接让JNIEnvExt结构体从JNIEnv结构体继承下来呢?这样把一个JNIEnvExt结构体转换为一个JNIEnv结构体就是相当直观的。然而,Dalvik虚拟机的源代码并一定是要以C++语言的形式来编译的,它也可以以C语言的形式来编译的。由于C语言没有继承的概念,因此,为了使得Dalvik虚拟机的源代码能同时兼容C++和C,这里就使用了一个Trick:只要两个结构体的内存布局相同,它们就可以相互转换访问。当然,这并不要求两个结构体的内存布局完全相同,但是至少开始部分要求是相同的。在这种情况下,将一个结构体强制转换成另外一个结构体之外,只要不去访问内存布局不一致的地方,就没有问题。在Android系统的Native代码中,我们可以常常看到这种Trick。罗升阳博客

4.然后开始创建jni环境

 JNIEnvExt* pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL);

dvmCreateJNIEnv(Thread self)中,创建一个

 JNIEnvExt* newEnv; newEnv->funcTable = &gNativeInterface; 
struct JNIEnvExt {
    const struct JNINativeInterface* funcTable;     /* must be first */

    const struct JNINativeInterface* baseFuncTable;

    u4      envThreadId;
    Thread* self;

    /* if nonzero, we are in a "critical" JNI call */
    int     critical;

    struct JNIEnvExt* prev;
    struct JNIEnvExt* next;
};

这里会构成一个双向链表,将新创建的env插入到gvmjni的jniVm中的列表中。 这里是android 4.4与老罗博客的2.3不一样,区别在于gvmjni从gvm中拆分出来了。

  1. 函数返回后就开始执行
dvmStartup(int argc, const char* const argv[],bool ignoreUnrecognized, JNIEnv* pEnv)

这是关键函数

std::string dvmStartup(int argc, const char* const argv[],
        bool ignoreUnrecognized, JNIEnv* pEnv)
{

    setCommandLineDefaults(); //设置虚拟机默认参数

    int cc = processOptions(argc, argv, ignoreUnrecognized);//处理参数

    /*
     * Initialize components.
     */
    if (!dvmAllocTrackerStartup()) //对象分配记录子模块
    if (!dvmGcStartup())           //gc
    if (!dvmThreadStartup()) 
    if (!dvmInlineNativeStartup()) //dalvik内联函数子模块。会替换一些java方法,效率更高。
    if (!dvmRegisterMapStartup()) 
    if (!dvmInstanceofStartup()) 
    if (!dvmClassStartup())//来初始化启动类加载器(Bootstrap Class Loader),
                           //同时还会初始化java.lang.Class类。
                           //保证加载的Java核心类是合法的。


    if (!dvmFindRequiredClassesAndMembers())
//这里,会对gdvm结构进行很多的填充操作。下面dalvik取得的systemloader也是在这里初始化的。

    if (!dvmStringInternStartup())
    if (!dvmNativeStartup())      //初始化SO库加载表,即创建了一张存储so文件目录的hash表
         //并保存到gDvm.nativelibs中去

    if (!dvmInternalNativeStartup())//用来初始化一个内部Native函数表,并保存到gDvm.userDexFiles
     //所有需要直接访问Dalvik虚拟机内部函数或者数据结构的Native函数都定义在这张表中
    //同样用hash表存储

    if (!dvmJniStartup())//初始化全局引用表。
    //被Native Code引用的Java对象就会被记录在一个全局引用表中
    if (!dvmProfilingStartup())//Dalvik虚拟机的性能分析子模块


    if (!dvmCreateInlineSubsTable()) 

    if (!dvmValidateBoxClasses())//初始化装箱类

    if (!dvmPrepMainForJni(pEnv))

    if (!dvmInitClass(gDvm.classJavaLangClass)) 

    if (!registerSystemNatives(pEnv))//会调用 loadJniLibrary加载libcore和nativehelper。进而进入
    //dvmLoadNativeCode()
/*
1. 在gdvm的nativelibs(hash表)中查找so库是否已经被加载了,如果加载了就返回结果。
2. 没有被加载的情况下,会调用[addSharedLibEntry()](http://androidxref.com/4.4.2_r2/xref/dalvik/vm/Native.cpp#addSharedLibEntry)将so添加到gdvm.nativelibs。
3. 用dlopen打开需要被加载的库。如果该库没有JNI_ONLOAD,则直接返回成功。否则,由函数指针调用JNI_ONLOAD,最后会返回version,会判断version。
*/

    if (!dvmCreateStockExceptions())

    if (!dvmPrepMainThread()) 
/*
1. gDvm.classJavaLangClass || gDvm.classJavaLangThreadGroup) || gDvm.classJavaLangThread  ||
gDvm.classJavaLangVMThread这几项
2. 反射调用创建Thread类实例,并调用main方法
3. VMThread,同2
4.取得gdvm的 systemLoader。上文所说的初始化,所以这里拿到的就是java/lang中的getsystemloader()方法返回的loader。
*/

    if (dvmReferenceTableEntries(&dvmThreadSelf()->internalLocalRefTable) != 0)
    //确保主线程当前不引用有任何Java对象,

    if (!dvmDebuggerStartup())//用来初始化Dalvik虚拟机的调试环境

    if (!dvmGcStartupClasses()) 

    //会检查启动参数,判别是否是在zygote中启动的dalvik虚拟机
    //对应于第一次创建:zygote中启动
    //android app启动:dvmInitAfterZygote
    if (gDvm.zygote) {
        if (!initZygote()) {
            return "initZygote failed";
        }
    } else {
        if (!dvmInitAfterZygote()) {//在dvmInitAfterZygote()会开启JIT线程,如果选择JIT模式的话
            return "dvmInitAfterZygote failed";
        }
    }

    return "";
}

总结一下,dalvik虚拟机的启动过程,就是在对gdvm结构的填充过程。

相关函数链接:
dvmLoadNativeCode()

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions