从本文开始介绍,kvn的启动流程.
启动入口位于j2me_cldc/kvm/VmExtra/src/main.c的main方法.代码如下:
int main (int argc, char* argv[]) {
int result;
#if USE_JAM
char *jamInstalledAppsDir = "./instapps";
#endif
JamEnabled = FALSE;
JamRepeat = FALSE;
RequestedHeapSize = DEFAULTHEAPSIZE; // 256*1024
#if ENABLE_JAVA_DEBUGGER
suspend = TRUE;
#endif
// 处理参数
while (argc > 1) {
if (strcmp(argv[1], "-version") == 0) {
fprintf(stdout, "Version: %s\n", BUILD_VERSION);
exit(1);
} else if (strcmp(argv[1], "-help") == 0) {
printHelpText();
exit(0);
#if ENABLE_JAVA_DEBUGGER
} else if (strcmp(argv[1], "-debugger") == 0) {
debuggerActive = TRUE;
argv++; argc--;
} else if (strcmp(argv[1], "-suspend") == 0) {
suspend = TRUE;
argv++; argc--;
} else if (strcmp(argv[1], "-nosuspend") == 0) {
suspend = FALSE;
argv++; argc--;
} else if ((strcmp(argv[1], "-port") == 0) && (argc > 2)) {
debuggerPort = (short)atoi(argv[2]);
argv+=2; argc -=2;
#endif /* ENABLE_JAVA_DEBUGGER */
// 设置堆大小
} else if ((strcmp(argv[1], "-heapsize") == 0) && (argc > 2)) {
char *endArg;
long heapSize = strtol(argv[2], &endArg, 10);
switch (*endArg) {
case '\0': break;
case 'k': case 'K': heapSize <<= 10; break;
case 'm': case 'M': heapSize <<= 20; break;
default: printHelpText(); exit(1);
}
// 从经验上来看,KVM可以运行在只有几kb的堆内存上.将16k作为最小值.
// KVM可以收集的最大内存为64m。实际上,GC只针对小堆进行了优化,并且如果堆大于几兆字节的话,很可能有明显的GC暂停。
if (heapSize < 16 * 1024) {
fprintf(stderr, KVM_MSG_USES_16K_MINIMUM_MEMORY "\n");
heapSize = 16 * 1024;
}
if (heapSize > 64 * 1024 * 1024) {
fprintf(stderr, KVM_MSG_USES_64M_MAXIMUM_MEMORY "\n");
heapSize = 64 * 1024 * 1024;
}
/* 确保堆的大小是4字节对齐的 */
heapSize -= heapSize%CELL;
argv+=2; argc -=2;
RequestedHeapSize = heapSize;
// 设置classpath
} else if ((strcmp(argv[1], "-classpath") == 0) && argc > 2) {
if (JamEnabled) {
fprintf(stderr, KVM_MSG_CANT_COMBINE_CLASSPATH_OPTION_WITH_JAM_OPTION);
exit(1);
}
UserClassPath = argv[2];
argv+=2; argc -=2;
#if INCLUDEDEBUGCODE
#define CHECK_FOR_OPTION_IN_ARGV(varName, userName) \
} else if (strcmp(argv[1], userName) == 0) { \
varName = 1; \
argv++; argc--;
FOR_EACH_TRACE_FLAG(CHECK_FOR_OPTION_IN_ARGV)
} else if (strcmp(argv[1], "-traceall") == 0) {
# define TURN_ON_OPTION(varName, userName) varName = 1;
FOR_EACH_TRACE_FLAG(TURN_ON_OPTION)
argv++; argc--;
#endif /* INCLUDEDEBUGCODE */
#if USE_JAM
} else if (strcmp(argv[1], "-jam") == 0) {
JamEnabled = TRUE;
argv++; argc--;
} else if (JamEnabled && (strcmp(argv[1], "-repeat") == 0)) {
#if ENABLE_JAVA_DEBUGGER
if (debuggerActive) {
fprintf(stderr,
KVM_MSG_CANT_COMBINE_DEBUGGER_OPTION_WITH_REPEAT_OPTION);
exit(1);
}
#endif /* ENABLE_JAVA_DEBUGGER */
JamRepeat = TRUE;
argv++; argc--;
} else if (JamEnabled && (strcmp(argv[1], "-appsdir") == 0)
&& argc > 2) {
jamInstalledAppsDir = argv[2];
argv+=2; argc-=2;
#endif /* USE_JAM */
} else {
break;
}
}
/* Skip program name 跳过应用的名称*/
argc--;
argv++;
// 设置classpath
if (UserClassPath == NULL && !JamEnabled) {
UserClassPath = getenv("classpath");
if (UserClassPath == NULL) {
/* Just in case environment variable reading is case sensitive */
UserClassPath = getenv("CLASSPATH");
/* Use "." as the default classpath */
if (UserClassPath == NULL) {
UserClassPath = ".";
}
}
}
#if USE_JAM
if (JamEnabled) {
if (argc != 1 ) {
fprintf(stderr,
KVM_MSG_EXPECTING_HTTP_OR_FILE_WITH_JAM_OPTION);
exit(1);
}
JamInitialize(jamInstalledAppsDir);
do {
result = JamRunURL(argv[0], JamRepeat);
if (result == JAM_RETURN_ERR) {
break;
}
} while(JamRepeat);
JamFinalize();
} else
#endif /* USE_JAM */
{
/* 启动kvm虚拟机*/
result = StartJVM(argc, argv);
#if ENABLEPROFILING
/* By default, the VM prints out profiling information */
/* upon exiting if profiling is turned on. 打印性能参数*/
printProfileInfo();
#endif /* ENABLEPROFILING */
/* If no classfile was provided, print help text 如果没有类文件提供的话,打印帮助信息*/
if (result == -1) printHelpText();
}
return result;
}
这处的方法比较简单.步骤如下:
- 通过循环,依次处理参数.其中,对于-heapsize而言,其最小为16k,最大为64m.
- 设置classpath
- 调用StartJVM,启动kvm虚拟机.
- 如果没有类文件提供的话,打印帮助信息.
这里重要的是第3步.
StartJVM
此部分的代码如下:
int StartJVM(int argc, char* argv[])
{
volatile int returnValue = 0;
/* 1.必须提供要运行的类名,如果没提供的话,返回-1 */
if (argc <= 0 || argv[0] == NULL) {
AlertUser(KVM_MSG_MUST_PROVIDE_CLASS_NAME);
return -1;
}
// 2. 启动KVM
returnValue = KVM_Start(argc, argv);
// 3. 回收资源
KVM_Cleanup();
return returnValue;
}
KVM_Start
此处的代码如下(经过宏展开后的结果):
int KVM_Start(int argc, char* argv[])
{
ARRAY arguments;
INSTANCE_CLASS mainClass = NULL;
volatile int returnValue = 0; /* Needed to make compiler happy */
struct throwableScopeStruct __scope__;
int __state__;
jmp_buf __env__;
__scope__.outer = ThrowableScope;
ThrowableScope = &__scope__;
ThrowableScope->env = &__env__;
ThrowableScope->tmpRootsCount = TemporaryRootsLength;
ASSERT_NO_ALLOCATION_GUARD
TRACE_EXCEPTION("TRY")
if ((__state__ = setjmp(__env__)) == 0) {
jmp_buf __env__;
VMScope = &(__env__);
if (setjmp(__env__) == 0) {
/* 1. 创建ROM镜像*/
CreateROMImage();
/* 2. 初始化FPU*/
InitializeFloatingPoint();
#if ASYNCHRONOUS_NATIVE_FUNCTIONS
/* 3.初始化异步I/0系统*/
InitalizeAsynchronousIO();
#endif
/* 4. 初始化本地代码 */
InitializeNativeCode();
InitializeVM();// 5. 初始化vm
/* 6.初始化全局变量 */
InitializeGlobals();
/* 7. 初始化性能统计的变量 */
InitializeProfiling();
/* 8. 初始化内存系统 */
InitializeMemoryManagement();
/* 9. 初始化内部hash 表 */
InitializeHashtables();
/* 10.初始化inline cache */
InitializeInlineCaching();
/* 11. 初始化类加载接口*/
InitializeClassLoading();
/* 12. 初始化VM所需要的内部类*/
InitializeJavaSystemClasses();
/* 13. 初始化class文件验证器 */
InitializeVerifier();
/* 14. 初始化事件处理系统 */
InitializeEvents();
/* 15. 加载主类 */
mainClass = loadMainClass(argv[0]);
/* 16. 解析参数 */
arguments = readCommandLineArguments(argc - 1, argv + 1);
/* 17. 初始化多线程 */
InitializeThreading(mainClass, arguments);
#if ENABLE_JAVA_DEBUGGER
/* Prepare the VM for Java-level debugging */
if (debuggerActive) {
InitDebugger();
}
#endif
/*
* 18. 初始化系统类
*/
initializeClass(JavaLangOutOfMemoryError);
initializeClass(JavaLangSystem);
initializeClass(JavaLangString);
initializeClass(JavaLangThread);
initializeClass(JavaLangClass);
#if ENABLE_JAVA_DEBUGGER
/* Prepare the VM for Java-level debugging */
if (vmDebugReady) {
setEvent_VMInit();
if (CurrentThread == NIL) {
CurrentThread = removeFirstRunnableThread();
/* Make sure xp_globals are synched with thread data */
loadExecutionEnvironment(CurrentThread);
}
sendAllClassPrepares();
}
/* 19.解释执行 */
Interpret();
} else {
int value = VMExitCode;
returnValue = value;
}
}
TRACE_EXCEPTION("CATCH")
ThrowableScope = __scope__.outer;
TemporaryRootsLength = __scope__.tmpRootsCount;
if (__state__ != 0) {
START_TEMPORARY_ROOTS
DECLARE_TEMPORARY_ROOT(THROWABLE_INSTANCE,
e,__scope__.throwable);
/* Any uncaught C-level exception above will transfer control here 在之前c语言级别的异常没有捕获的,都在这里处理*/
if (mainClass == NULL) {
/* If main class was not found, print special error message 如果主类没有找到的话,打印特殊的异常 */
char buffer[STRINGBUFFERSIZE];
sprintf(buffer, "%s", getClassName((CLASS)e->ofClass));
if (e->message != NULL) {
sprintf(buffer + strlen(buffer),": %s", getStringContents(e->message));
}
sprintf(str_buffer, "%s", buffer);
AlertUser(str_buffer);
returnValue = 1;
} else {
Log->uncaughtException(e);
returnValue = UNCAUGHT_EXCEPTION_EXIT_CODE;
}
TemporaryRootsLength = _tmp_roots_;
}
return returnValue;
}
该方法的步骤如下:
- 创建ROM镜像,此处为宏,定义在j2me_cldc/kvm/VmCommon/h/garbage.h
- 初始化FPU
- 初始化异步I/0系统
- 初始化本地代码
- 初始化vm,此处为宏,定义在j2me_cldc/kvm/VmUnix/h/machine_md.h
- 初始化全局变量
- 初始化性能统计的变量,定义在 j2me_cldc/kvm/VmCommon/h/profiling.h
- 初始化内存系统
- 初始化内部hash 表
- 初始化inline cache
- 初始化类加载接口
- 初始化VM所需要的内部类
- 初始化class文件验证器
- 初始化事件处理系统
- 加载主类
- 解析参数
- 初始化多线程
- 初始化系统类
- 解释执行
初始化FPU
此处的代码如下:
void InitializeFloatingPoint() {
#if defined(LINUX) && PROCESSOR_ARCHITECTURE_X86
/* Set the precision FPU to double precision 设置FPU为双精度*/
// 0x037f & ~0x300 | 0x200
fpu_control_t cw = (_FPU_DEFAULT & ~_FPU_EXTENDED) | _FPU_DOUBLE;
_FPU_SETCW(cw); // fldcw cw --> 设置状态寄存器
#endif
}
此处是通过修改fpu 的状态寄存器将其修改为双精度.关于此处可以参考如下链接:
初始化异步I/0系统
此处的代码为:
int VersionOfTheWorld = 0;
void InitalizeAsynchronousIO(void) {
if (VersionOfTheWorld++ == 0) {
int i;
for (i = 0 ; i < ASYNC_IOCB_COUNT ; i++) { // ASYNC_IOCB_COUNT = 5
ASYNCIOCB *aiocb = &IocbRoots[i];
FreeAsyncIOCB(aiocb);
}
} else {
while (ActiveAsyncOperations() > 0) {
Yield_md();
}
}
}
由于VersionOfTheWorld = 0 ,因此此处会进行异步I/O的初始化.通过循环,调用FreeAsyncIOCB 进行初始化.
此处使用了ASYNCIOCB,其代码如下:
ASYNCIOCB IocbRoots[ASYNC_IOCB_COUNT];
ASYNCIOCB *IocbFreeList = 0;
typedef struct asynciocb {
struct asynciocb *nextFree;
THREAD thread;
INSTANCE instance;
BYTEARRAY array;
char *exception;
} ASYNCIOCB;
由于ASYNC_IOCB_COUNT默认为5,因此实例化了长度为5的ASYNCIOCB数组,其中ASYNCIOCB又通过nextFree想连,形成链表.如图所示:
这里使用了FreeAsyncIOCB,代码如下:
static void FreeAsyncIOCB(ASYNCIOCB *aiocb) {
aiocb->thread = 0;
aiocb->instance = 0;
aiocb->array = 0;
aiocb->exception = 0;
aiocb->nextFree = IocbFreeList;
IocbFreeList = aiocb;
}
注意,在该方法中使用了头插法.
初始化本地代码
此处使用的是InitializeNativeCode,代码如下:
void InitializeNativeCode() {
/*
* 用来处理dump stack
*/
signal(SIGILL, signal_handler); // 执行了非法指令, 通常是因为可执行文件本身出现错误,或者试图执行数据段, 堆栈溢出时也有可能产生这个信号。
signal(SIGABRT, signal_handler); // 异常终止条件,例如 abort() 所起始的
signal(SIGBUS, signal_handler); // 非法地址,包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数,但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或
signal(SIGSEGV, signal_handler); // 非法内存访问(段错误)
signal(SIGPIPE, SIG_IGN); // 管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止
}
初始化全局变量
此处的代码如下:
// This flag indicates whether class loading has been initiated from Class.forName() or from elsewhere in the virtual machine
此标志指示类加载是从class.forname()还是从虚拟机中的其他位置启动的
bool_t loadedReflectively;
void InitializeGlobals(void)
{
loadedReflectively = FALSE;
}
后续流程,下文讲述