jamVM閱讀(一)——解析參數

學習源碼,是代碼技術提升的一個重要手段,在java相關的內容中,有各類框架的源碼、tomcat的源碼、android源碼、jdk標準庫的源碼和jvm的源碼等各種值得學習的內容。在這些內容當中,jvm的源碼是唯一一種純用c、c++完成的,因此對於很多寫習慣了java的程序員來說,閱讀jvm是一件不那麼容易的事情,而且目前大多數的jvm源碼相當冗長,不易於閱讀,所以爲了簡單的瞭解jvm的運行機制,我們選擇了“麻雀雖小,五臟俱全”的jamvm來進行閱讀。

首先,下載編譯jamvm自行百度去,這篇文章只對jamvm的代碼做簡單理解

由於對jamvm還不是特別瞭解,我們簡單地從主函數開始進行閱讀

int main(int argc, char *argv[]) {
    Class *array_class, *main_class;
    Object *system_loader, *array;
    MethodBlock *mb;
    InitArgs args;
    int class_arg;
    char *cpntr;
    int status;
    int i;

    setDefaultInitArgs(&args);

    // 解析命令參數
    class_arg = parseCommandLine(argc, argv, &args);

    // 設置類棧
    args.main_stack_base = &array_class;


    if (!initVM(&args)) {
        /**
         * TODO: Read
         * 啓動虛擬機, 失敗打印
         */
        printf("Could not initialise VM.  Aborting.\n");
        exit(1);
    }

    if ((system_loader = getSystemClassLoader()) == NULL)
        // TODO 設置主classLoader
        goto error;

    // TODO 給主線程設置classLoader
    mainThreadSetContextClassLoader(system_loader);

    for (cpntr = argv[class_arg]; *cpntr; cpntr++)
        /**
         * 把class的參數, 如‘jamVM HelloWorld’的‘HelloWorld’賦值給cpntr,對於路徑修改爲斜槓
         */
        if (*cpntr == '.')
            *cpntr = '/';


    // TODO 查詢主函數
    main_class = findClassFromClassLoader(argv[class_arg], system_loader);
    if (main_class != NULL)
        // TODO 初始化一個主函數
        initClass(main_class);

    if (exceptionOccurred())
        // TODO 大概是處理異常
        goto error;

    // TODO 下面一整段
    mb = lookupMethod(main_class, SYMBOL(main),
                      SYMBOL(_array_java_lang_String__V));

    if (mb == NULL || !(mb->access_flags & ACC_STATIC)) {
        signalException(java_lang_NoSuchMethodError, "main");
        goto error;
    }

    /* Create the String array holding the command line args */

    i = class_arg + 1;
    if ((array_class = findArrayClass(SYMBOL(array_java_lang_String))) &&
        (array = allocArray(array_class, argc - i, sizeof(Object *)))) {
        Object **args = ARRAY_DATA(array, Object*) - i;

        for (; i < argc; i++)
            if (!(args[i] = Cstr2String(argv[i])))
                break;

        /* Call the main method */
        if (i == argc)
            executeStaticMethod(main_class, mb, array);
    }

    error:
    /* ExceptionOccurred returns the exception or NULL, which is OK
       for normal conditionals, but not here... */
    if ((status = exceptionOccurred() ? 1 : 0))
        uncaughtException();

    /* Wait for all but daemon threads to die */
    mainThreadWaitToExitVM();
    // TODO 關閉虛擬機
    exitVM(status);

    /* Keep the compiler happy */
    return 0;
}

上面簡單地進行了註釋,我們根據這個流程做分析,首先這一篇分析解析參數。


// 解析命令參數
    class_arg = parseCommandLine(argc, argv, &args);
這裏傳入的參數有三個,前兩個是main函數的參數,代表了命令行的參數個數和參數數組,第三個是運行的初始化參數

typedef struct InitArgs {
    int asyncgc;
    int verbosegc;
    int verbosedll;
    int verboseclass;

    int compact_specified; /* Whether compaction has been given on the
                              command line, and the value if it has */
    int do_compact;

    int trace_jni_sigs;

    char *classpath; //classpath字面意思

    char *bootpath;
    char *bootpath_a;
    char *bootpath_p;
    char *bootpath_c;
    char *bootpath_v;

    int java_stack;
    unsigned long min_heap; // 最小堆棧值
    unsigned long max_heap; // 最大堆棧值

    Property *commandline_props;
    int props_count;

    void *main_stack_base; // 運行的類棧

    /* JNI invocation API hooks */
    
    int (*vfprintf)(FILE *stream, const char *fmt, va_list ap);
    void (*exit)(int status);
    void (*abort)(void);

#ifdef INLINING
    unsigned int codemem;
    int replication_threshold;
    int profile_threshold;
    int branch_patching_dup;
    int branch_patching;
    int print_codestats;
    int join_blocks;
    int profiling;
#endif

#ifdef HAVE_PROFILE_STUBS
    int dump_stubs_profiles;
#endif
} InitArgs;
這個是這個初始化參數的結構體,註釋瞭解析命令行參數需要用的參數的含義


然後看這個parseCommandLine函數

/**
 * 對jamVM命令的參數做出解析
 * @param argc 參數個數
 * @param argv 參數數組
 * @param args 初始化參數???
 * @return
 */
int parseCommandLine(int argc, char *argv[], InitArgs *args) {
    Property props[argc - 1];
    int is_jar = FALSE;
    int status = 0;
    int i;

    args->commandline_props = &props[0];

    for (i = 1; i < argc; i++) {
        /**
         * 遍歷
         */
        if (*argv[i] != '-') {
            /**
             * 如果不是-開頭,即不是操作位
             */
            if (args->min_heap > args->max_heap) {
                // 如果參數中最小堆棧值大於最大堆棧值就報錯
                printf("Minimum heap size greater than max!\n");
                status = 1;
                goto exit;
            }

            if (args->props_count) {
                args->commandline_props = sysMalloc(args->props_count *
                                                    sizeof(Property));
                memcpy(args->commandline_props, &props[0], args->props_count *
                                                           sizeof(Property));
            }

            if (is_jar) {
                /**
                 * 如果是jar就設置classpath
                 */
                args->classpath = argv[i];
                argv[i] = "jamvm/java/lang/JarLauncher";
            }

            // 返回運行class的參數所在位置
            return i;
        }

先看第一部分,對參數進行遍歷,這裏進行了判斷,根據參數是否以'-'開頭判斷參數是表示操作還是表示class類。

這一部分參數是不以'-'開頭的,即參數是class或者jar之類的可執行的java文件,然後如註釋所說,先判斷一下下文解析結果中的一些參數是否合法,如最小棧值大於最大棧值,然後對於jar文件進行解析,將classpath賦值。最後返回改參數的下標以供下文使用。




第二部分自然是對於實際操作的參數,我們可以執行java命令(如果編譯過jamVM的可以用jamvm命令),來查看下java的參數種類


shabi@shabi-C-H110M-K-PRO:~$ java
用法: java [-options] class [args...]
           (執行類)
   或  java [-options] -jar jarfile [args...]
           (執行 jar 文件)
其中選項包括:
    -d32	  使用 32 位數據模型 (如果可用)
    -d64	  使用 64 位數據模型 (如果可用)
    -server	  選擇 "server" VM
    -zero	  選擇 "zero" VM
    -dcevm	  選擇 "dcevm" VM
                  默認 VM 是 server,
                  因爲您是在服務器類計算機上運行。


    -cp <目錄和 zip/jar 文件的類搜索路徑>
    -classpath <目錄和 zip/jar 文件的類搜索路徑>
                  用 : 分隔的目錄, JAR 檔案
                  和 ZIP 檔案列表, 用於搜索類文件。
    -D<名稱>=<值>
                  設置系統屬性
    -verbose:[class|gc|jni]
                  啓用詳細輸出
    -version      輸出產品版本並退出
    -version:<值>
                  警告: 此功能已過時, 將在
                  未來發行版中刪除。
                  需要指定的版本才能運行
    -showversion  輸出產品版本並繼續
    -jre-restrict-search | -no-jre-restrict-search
                  警告: 此功能已過時, 將在
                  未來發行版中刪除。
                  在版本搜索中包括/排除用戶專用 JRE
    -? -help      輸出此幫助消息
    -X            輸出非標準選項的幫助
    -ea[:<packagename>...|:<classname>]
    -enableassertions[:<packagename>...|:<classname>]
                  按指定的粒度啓用斷言
    -da[:<packagename>...|:<classname>]
    -disableassertions[:<packagename>...|:<classname>]
                  禁用具有指定粒度的斷言
    -esa | -enablesystemassertions
                  啓用系統斷言
    -dsa | -disablesystemassertions
                  禁用系統斷言
    -agentlib:<libname>[=<選項>]
                  加載本機代理庫 <libname>, 例如 -agentlib:hprof
                  另請參閱 -agentlib:jdwp=help 和 -agentlib:hprof=help
    -agentpath:<pathname>[=<選項>]
                  按完整路徑名加載本機代理庫
    -javaagent:<jarpath>[=<選項>]
                  加載 Java 編程語言代理, 請參閱 java.lang.instrument
    -splash:<imagepath>
                  使用指定的圖像顯示啓動屏幕
有關詳細信息, 請參閱 http://www.oracle.com/technetwork/java/javase/documentation/index.html。
shabi@shabi-C-H110M-K-PRO:~$ java -X
    -Xmixed           混合模式執行 (默認)
    -Xint             僅解釋模式執行
    -Xbootclasspath:<用 : 分隔的目錄和 zip/jar 文件>
                      設置搜索路徑以引導類和資源
    -Xbootclasspath/a:<用 : 分隔的目錄和 zip/jar 文件>
                      附加在引導類路徑末尾
    -Xbootclasspath/p:<用 : 分隔的目錄和 zip/jar 文件>
                      置於引導類路徑之前
    -Xdiag            顯示附加診斷消息
    -Xnoclassgc       禁用類垃圾收集
    -Xincgc           啓用增量垃圾收集
    -Xloggc:<file>    將 GC 狀態記錄在文件中 (帶時間戳)
    -Xbatch           禁用後臺編譯
    -Xms<size>        設置初始 Java 堆大小
    -Xmx<size>        設置最大 Java 堆大小
    -Xss<size>        設置 Java 線程堆棧大小
    -Xprof            輸出 cpu 配置文件數據
    -Xfuture          啓用最嚴格的檢查, 預期將來的默認值
    -Xrs              減少 Java/VM 對操作系統信號的使用 (請參閱文檔)
    -Xcheck:jni       對 JNI 函數執行其他檢查
    -Xshare:off       不嘗試使用共享類數據
    -Xshare:auto      在可能的情況下使用共享類數據 (默認)
    -Xshare:on        要求使用共享類數據, 否則將失敗。
    -XshowSettings    顯示所有設置並繼續
    -XshowSettings:all
                      顯示所有設置並繼續
    -XshowSettings:vm 顯示所有與 vm 相關的設置並繼續
    -XshowSettings:properties
                      顯示所有屬性設置並繼續
    -XshowSettings:locale
                      顯示所有與區域設置相關的設置並繼續

-X 選項是非標準選項, 如有更改, 恕不另行通知。

然後對照着這些參數去看源代碼,先把源代碼全文貼一下:

switch (parseCommonOpts(argv[i], args, FALSE)) {
            /**
             * 如果是操作位,針對不同的操作做出處理
             */
            case OPT_OK:
                break;

            case OPT_ERROR:
                status = 1;
                goto exit;

            case OPT_UNREC:
            default:
                if (strcmp(argv[i], "-?") == 0 ||
                    strcmp(argv[i], "-help") == 0) {
                    goto usage;

                } else if (strcmp(argv[i], "-X") == 0) {
                    showNonStandardOptions();
                    goto exit;

                } else if (strcmp(argv[i], "-version") == 0) {
                    showVersionAndCopyright();
                    goto exit;

                } else if (strcmp(argv[i], "-showversion") == 0) {
                    showVersionAndCopyright();

                } else if (strcmp(argv[i], "-fullversion") == 0) {
                    showFullVersion();
                    goto exit;

                } else if (strncmp(argv[i], "-verbose", 8) == 0) {
                    char *type = &argv[i][8];

                    if (*type == '\0' || strcmp(type, ":class") == 0)
                        args->verboseclass = TRUE;

                    else if (strcmp(type, ":gc") == 0 || strcmp(type, "gc") == 0)
                        args->verbosegc = TRUE;

                    else if (strcmp(type, ":jni") == 0)
                        args->verbosedll = TRUE;

                } else if (strcmp(argv[i], "-jar") == 0) {
                    is_jar = TRUE;

                } else if (strcmp(argv[i], "-classpath") == 0 ||
                           strcmp(argv[i], "-cp") == 0) {

                    if (i == argc - 1) {
                        printf("%s : missing path list\n", argv[i]);
                        goto exit;
                    }
                    args->classpath = argv[++i];

                } else if (strncmp(argv[i], "-Xbootclasspath/c:", 18) == 0) {
                    args->bootpath_c = argv[i] + 18;

                } else if (strncmp(argv[i], "-Xbootclasspath/v:", 18) == 0) {
                    args->bootpath_v = argv[i] + 18;

                    /* Compatibility options */
                } else if (strcmp(argv[i], "-client") == 0 ||
                           strcmp(argv[i], "-server") == 0) {
                    /* Ignore */
                } else {
                    printf("Unrecognised command line option: %s\n", argv[i]);
                    status = 1;
                    goto usage;
                }
        }
    }

    usage:
    showUsage(argv[0]);

    exit:
    exit(status);
}

這是parseCommandLine函數的後半段,很顯然一開始執行了parseCommonOpts方法,並對返回的結果進行判斷,如果成功就break這個switch,如果失敗就跳轉到exit函數,這個exit函數顧名思義就是退出,一會再看,然後對於其他一些特殊的如-version這樣的參數就輸出指定的結果,然後執行showUsage函數,showUsage是輸出一個模板,即:

void showUsage(char *name) {
    printf("Usage: %s [-options] class [arg1 arg2 ...]\n", name);
    printf("                 (to run a class file)\n");
    printf("   or  %s [-options] -jar jarfile [arg1 arg2 ...]\n", name);
    printf("                 (to run a standalone jar file)\n");
    printf("\nwhere options include:\n");
    printf("  -client\t   compatibility (ignored)\n");
    printf("  -server\t   compatibility (ignored)\n\n");
    printf("  -cp\t\t   <jar/zip files and directories separated by :>\n");
    printf("  -classpath\t   <jar/zip files and directories separated by :>\n");
    printf("\t\t   locations where to find application classes\n");
    printf("  -D<name>=<value> set a system property\n");
    printf("  -verbose[:class|gc|jni]\n");
    printf("\t\t   :class print out information about class loading, etc.\n");
    printf("\t\t   :gc print out results of garbage collection\n");
    printf("\t\t   :jni print out native method dynamic resolution\n");
    printf("  -version\t   print out version number and copyright information\n");
    printf("  -showversion     show version number and copyright and continue\n");
    printf("  -fullversion     show jpackage-compatible version number and exit\n");
    printf("  -? -help\t   print out this message\n");
    printf("  -X\t\t   show help on non-standard options\n");
}

這段就不解釋了,我們來看看核心函數parseCommandLine:

/**
 * 解析操作位
 * @param string 操作位 如“-version”
 * @param args 初始化參數
 * @param is_jni 是否是jni的調用
 * @return
 */
int parseCommonOpts(char *string, InitArgs *args, int is_jni) {
    /**
     * 初始返回值
     */
    int status = OPT_OK;

    if(strcmp(string, "-Xasyncgc") == 0)
        args->asyncgc = TRUE;

    else if(strncmp(string, "-Xms", 4) == 0 ||
            (!is_jni && strncmp(string, "-ms", 3) == 0)) {

        /**
         * 將指針指向參數後的數字
         */
        char *value = string + (string[1] == 'm' ? 3 : 4);

        // 將初始化堆棧的大小賦值
        args->min_heap = parseMemValue(value);

        if(args->min_heap < MIN_HEAP) {
            // 如果數據不合法就返回錯誤
            optError(args, "Invalid minimum heap size: %s (min %dK)\n",
                     string, MIN_HEAP/KB);
            status = OPT_ERROR;
        }

    } else if(strncmp(string, "-Xmx", 4) == 0 ||
              (!is_jni && strncmp(string, "-mx", 3) == 0)) {

        char *value = string + (string[1] == 'm' ? 3 : 4);
        args->max_heap = parseMemValue(value);

        if(args->max_heap < MIN_HEAP) {
            optError(args, "Invalid maximum heap size: %s (min is %dK)\n",
                     string, MIN_HEAP/KB);
            status = OPT_ERROR;
        }

    } else if(strncmp(string, "-Xss", 4) == 0 ||
              (!is_jni && strncmp(string, "-ss", 3) == 0)) {

        char *value = string + (string[1] == 'm' ? 3 : 4);
        args->java_stack = parseMemValue(value);

        if(args->java_stack < MIN_STACK) {
            optError(args, "Invalid Java stack size: %s (min is %dK)\n",
                     string, MIN_STACK/KB);
            status = OPT_ERROR;
        }

    } else if(strncmp(string, "-D", 2) == 0) {
        char *key = strcpy(sysMalloc(strlen(string + 2) + 1), string + 2);
        char *pntr;

        for(pntr = key; *pntr && (*pntr != '='); pntr++);
        if(*pntr)
            *pntr++ = '\0';
        args->commandline_props[args->props_count].key = key;
        args->commandline_props[args->props_count++].value = pntr;

    } else if(strncmp(string, "-Xbootclasspath:", 16) == 0) {
        args->bootpath = string + 16;
        args->bootpath_p = args->bootpath_a = NULL;

    } else if(strncmp(string, "-Xbootclasspath/a:", 18) == 0) {
        args->bootpath_a = string + 18;

    } else if(strncmp(string, "-Xbootclasspath/p:", 18) == 0) {
        args->bootpath_p = string + 18;

    } else if(strcmp(string, "-Xnocompact") == 0) {
        args->compact_specified = TRUE;
        args->do_compact = FALSE;

    } else if(strcmp(string, "-Xcompactalways") == 0) {
        args->compact_specified = args->do_compact = TRUE;

    } else if(strcmp(string, "-Xtracejnisigs") == 0) {
        args->trace_jni_sigs = TRUE;
#ifdef INLINING
    } else if(strcmp(string, "-Xnoinlining") == 0) {
        /* Turning inlining off is equivalent to setting
           code memory to zero */
        args->codemem = 0;

    } else if(strcmp(string, "-Xnoprofiling") == 0) {
        args->profiling = FALSE;

    } else if(strcmp(string, "-Xnopatching") == 0) {
        args->branch_patching = FALSE;

    } else if(strcmp(string, "-Xnopatchingdup") == 0) {
        args->branch_patching_dup = FALSE;

    } else if(strcmp(string, "-Xnojoinblocks") == 0) {
        args->join_blocks = FALSE;

    } else if(strcmp(string, "-Xcodestats") == 0) {
        args->print_codestats = TRUE;

    } else if(strncmp(string, "-Xprofiling:", 12) == 0) {
        args->profile_threshold = strtol(string + 12, NULL, 0);

    } else if(strncmp(string, "-Xreplication:", 14) == 0) {
        char *pntr = string + 14;

        if(strcmp(pntr, "none") == 0)
            args->replication_threshold = INT_MAX;
        else
            if(strcmp(pntr, "always") == 0)
                args->replication_threshold = 0;
            else
                args->replication_threshold = strtol(pntr, NULL, 0);

    } else if(strncmp(string, "-Xcodemem:", 10) == 0) {
        char *pntr = string + 10;

        args->codemem = strncmp(pntr, "unlimited", 10) == 0 ?
            INT_MAX : parseMemValue(pntr);

    } else if(strcmp(string, "-Xshowreloc") == 0) {
        showRelocatability();
#endif

#ifdef HAVE_PROFILE_STUBS
    } else if(strcmp(string, "-Xdumpstubsprofiles") == 0) {
        args->dump_stubs_profiles = TRUE;
#endif
    /* Compatibility options */
    } else {
        int i;

        for(i = 0; compat[i].option != NULL; i++) {
            int len = strlen(compat[i].option);

            if(strncmp(string, compat[i].option, len) == 0 &&
               (((compat[i].flags & OPT_ARG) && string[len] == ':') ||
                ((compat[i].flags & OPT_NOARG) && string[len] == '\0')))
                break;
        }

        if(compat[i].option == NULL)
            status = OPT_UNREC;
    }

    return status;
}

代碼相當長,但其實只是對於不同的命令做分別的處理,我們簡單看一種命令,看一下上面代碼註釋了的那一段:

    else if(strncmp(string, "-Xms", 4) == 0 ||
            (!is_jni && strncmp(string, "-ms", 3) == 0)) {

        /**
         * 將指針指向參數後的數字
         */
        char *value = string + (string[1] == 'm' ? 3 : 4);

        // 將初始化堆棧的大小賦值
        args->min_heap = parseMemValue(value);

        if(args->min_heap < MIN_HEAP) {
            // 如果數據不合法就返回錯誤
            optError(args, "Invalid minimum heap size: %s (min %dK)\n",
                     string, MIN_HEAP/KB);
            status = OPT_ERROR;
        }

查看命令輸出我們可以看到-Xms是設置初始化堆棧大小的,首先將指針移到命令後面 即 ‘-Xms 100M’移到 '100M',然後調parseMemValue函數,將返回結果賦值給args,並且判斷是否合法


/**
 * 解析參數後的那個數
 * @param str
 * @return
 */
unsigned long parseMemValue(char *str) {
    char *end;
    /**
     * 將參數轉化爲number,其中end儲存第一個不能被轉換的字符指針,第三個參數爲0表示10進制
     */
    unsigned long n = strtol(str, &end, 0);

    switch(end[0]) {
        case '\0':
            break;

        case 'G': case 'g':
            n *= KB;

        case 'M': case 'm':
            n *= KB;

        case 'K': case 'k':
            n *= KB;

            if(end[1] == '\0')
                break;

        default:
             n = 0;
    }

    return n;
}

這個函數很好理解,註釋都有就不解釋了


這就是jamVM解析命令行參數的全部流程了,這裏只是將命令行的參數解析賦值到了args變量中,而args變量相當於執行過程的全局變量,而真正的執行過程在後面的文章中解析。








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章