本文介绍kvm启动过程中比较复杂的步骤,初始化多线程系统与初始化系统类
初始化多线程系统
此处的代码如下:
void InitializeThreading(INSTANCE_CLASS mainClass, ARRAY argumentsArg)
{
START_TEMPORARY_ROOTS
/*
* ARRAY arguments = (
arguments = argumentsArg,
TemporaryRoots[TemporaryRootsLength++].cellp = (cell *)&arguments,
arguments)
*/
DECLARE_TEMPORARY_ROOT(ARRAY, arguments, argumentsArg);
/*
* JAVATHREAD javaThread = ( \
javaThread = (JAVATHREAD)instantiate(JavaLangThread), \
TemporaryRoots[TemporaryRootsLength++].cellp = (cell *)&javaThread, \
javaThread);
*/
DECLARE_TEMPORARY_ROOT(JAVATHREAD, javaThread,
(JAVATHREAD)instantiate(JavaLangThread));
int unused; /* Needed for creating name char array */
makeGlobalRoot((cell **)&MainThread); // 将主线程加入到root中
MainThread = NULL;
MonitorCache = NULL;
makeGlobalRoot((cell **)&CurrentThread);
makeGlobalRoot((cell **)&RunnableThreads);
makeGlobalRoot((cell **)&TimerQueue);
/* Initialize the field of the Java-level thread structure 设置优先级为5*/
javaThread->priority = 5;
/* Initialize the name of the system thread (since CLDC 1.1) 设置线程名称为Thread-0*/
javaThread->name = createCharArray("Thread-0", 8, &unused, FALSE);
// 2. 构建Thread
MainThread = BuildThread(&javaThread);
/* AllThreads is initialized to NULL by the garbage collector.
*
* Ensure that the thread list is properly initialized
* and set mainThread as the active (CurrentThread) thread
* 将MainThread 设为活动线程
*/
MainThread->nextThread = NIL;
AliveThreadCount = 1;
Timeslice = BASETIMESLICE;
MainThread->state = THREAD_ACTIVE;
/* Initialize VM registers 初始化寄存器 sp,fp,ip */
CurrentThread = MainThread;
RunnableThreads = NULL;
TimerQueue = NULL;
setSP((MainThread->stack->cells - 1));
setFP(NULL);
setIP(KILLTHREAD);
/* We can't create a frame consisting of "method", since its class
* has not yet been initialized, (and this would mess up the
* garbage collector). So we set up a pseudo-frame, and arrange
* for the interpreter to do the work for us.
* 建立栈帧
*/
pushFrame(RunCustomCodeMethod);
// *(CustomCodeCallbackFunction *)(++(GlobalState.gs_sp)) = (initInitialThreadBehaviorFromThread);
pushStackAsType(CustomCodeCallbackFunction,
initInitialThreadBehaviorFromThread);
/* We want to push method, but that would confuse the GC, since
* method is a pointer into the middle of a heap object. Only
* heap objects, and things that are clearly not on the heap, can
* be pushed onto the stack.
*/
// *(INSTANCE_CLASS *)(++(GlobalState.gs_sp)) = (mainClass);
pushStackAsType(INSTANCE_CLASS, mainClass);
// *(ARRAY *)(++(GlobalState.gs_sp)) = (arguments);
pushStackAsType(ARRAY, arguments);
// 初始化class
initializeClass(mainClass);
END_TEMPORARY_ROOTS
}
- 实例化javaThread
- 构建Thread
- 将MainThread 设为活动线程
- 初始化寄存器 sp,fp,ip
- 建立栈帧
- 初始化class
这里比较重要的是第2,5,6步.
构建Thread
此处的代码如下:
static THREAD BuildThread(JAVATHREAD_HANDLE javaThreadH)
{
THREAD newThread;
JAVATHREAD javaThread;
START_TEMPORARY_ROOTS
/*
* THREAD newThreadX = (
newThreadX = (THREAD)callocObject(((sizeof(struct threadQueue) + 3) >> 2), GCT_THREAD),
TemporaryRoots[TemporaryRootsLength++].cellp = (cell *)&newThreadX,
newThreadX);
*/
// 1. 在内存中分配THREAD,stack
DECLARE_TEMPORARY_ROOT(THREAD, newThreadX,
(THREAD)callocObject(SIZEOF_THREAD, GCT_THREAD));
STACK newStack = (STACK)callocObject(sizeof(struct stackStruct)
/ CELL, GCT_EXECSTACK);
/* newStack->next = NULL; Already zero from calloc */
newStack->size = STACKCHUNKSIZE; // 128
newThreadX->stack = newStack;
#if INCLUDEDEBUGCODE
if (tracethreading) {
TraceThread(newThreadX, "Created");
}
if (tracestackchunks) {
fprintf(stdout,
"Created a new stack (thread: %lx, first chunk: %lx, chunk size: %ld\n",
(long)newThreadX, (long)newStack, (long)STACKCHUNKSIZE);
}
#endif /* INCLUDEDEBUGCODE */
newThread = newThreadX;
END_TEMPORARY_ROOTS
/* Time slice will be initialized to default value */
newThread->timeslice = BASETIMESLICE; // 1000
/* Link the THREAD to the JAVATHREAD 2. 设置THREAD,JAVATHREAD的关联关系 */
javaThread = unhand(javaThreadH);
newThread->javaThread = javaThread;
javaThread->VMthread = newThread;
/* Initialize the state 3.设置状态 */
newThread->state = THREAD_JUST_BORN;
#if ENABLE_JAVA_DEBUGGER
newThread->nextOpcode = NOP;
#endif
/* Add to the alive thread list 4.将vmthread添加到 AllThreads 中 */
newThread->nextAliveThread = AllThreads;
AllThreads = newThread;
return(newThread);
}
该方法中的步骤如下:
- 在内存中分配THREAD,stack
- 设置THREAD,JAVATHREAD的关联关系
- 设置状态
- 将vmthread添加到AllThreads中
此处的代码比较简单,相关方法在之前文章中都有介绍.但是此处有个BASETIMESLICE需要提一下,这个宏的值决定了虚拟机执行线程切换、事件通知和一些其他定期需要的操作的基本频率(作为执行的字节码数)。较小的数目可以减少事件处理和线程切换延迟,但会导致解释器运行较慢。
此时如图所示:
pushFrame
此处的代码如下:
void pushFrame(METHOD thisMethod)
{
int thisFrameSize = thisMethod->frameSize;
int thisArgCount = thisMethod->argCount;
int thisLocalCount = thisFrameSize - thisArgCount; // 计算局部变量的数量
STACK stack = getFP() ? getFP()->stack : CurrentThread->stack; // 获得栈
// 该方法所需要的大小(字节为单位)
int thisMethodHeight = thisLocalCount + thisMethod->u.java.maxStack + // 操作数栈的部分
SIZEOF_FRAME /* 存储frameStruct结构 */ + RESERVEDFORNATIVE; //RESERVEDFORNATIVE = 为了本地方法而保留的3个cell
FRAME newFrame;
int i;
cell* prev_sp = getSP() - thisArgCount; /* Very volatile! 获得之前的sp*/
/* 计算在目前的stack chunk 中新增一个frame 结构是否会不会超过该chunk的容量*/
if (getSP() - stack->cells + thisMethodHeight >= stack->size) {
/* 需要重新创建一个stack chunk*/
STACK newstack;
thisMethodHeight += thisArgCount;
/* 如果存在下一个stack但是stack的size不够用,则设置 stack->next = NULL
* 而之前的stack->next由于没有任何指针指向,因此会被下一次的gc所释放掉 */
if (stack->next && thisMethodHeight > stack->next->size) {
stack->next = NULL;
}
/* 创建stack */
if (stack->next == NULL) {
int size = thisMethodHeight > STACKCHUNKSIZE
? thisMethodHeight : STACKCHUNKSIZE;
int stacksize = sizeof(struct stackStruct) / CELL +
(size - STACKCHUNKSIZE);
START_TEMPORARY_ROOTS
/**
* STACK stackX = (
stackX = stack,
TemporaryRoots[TemporaryRootsLength++].cellp = (cell *)&stackX,
stackX);
*/
DECLARE_TEMPORARY_ROOT(STACK, stackX, stack);
newstack = (STACK)mallocHeapObject(stacksize, GCT_EXECSTACK);
stack = stackX;
prev_sp = getSP() - thisArgCount;
END_TEMPORARY_ROOTS
if (newstack == NULL) {
THROW(StackOverflowObject);
}
#if INCLUDEDEBUGCODE
/* In debug mode, initialize the new stack chunk to zeros */
memset(newstack, 0, stacksize << log2CELL);
#endif
newstack->next = NULL;
newstack->size = size;
stack->next = newstack;
#if INCLUDEDEBUGCODE
if (traceframes || tracestackchunks) {
fprintf(stdout,
"Created a new stack chunk (thread: %lx, new chunk: %lx, prev: %lx, stack depth: %ld)\n",
(long)CurrentThread, (long)newstack,
(long)stack, (long)frameDepth());
}
#endif
} else {
/* Can reuse an existing, unused stack chunk */
newstack = stack->next;
}
/* 参数复制 */
for (i = 0; i < thisArgCount; i++) {
newstack->cells[i] = prev_sp[i + 1];
}
setLP(newstack->cells);
newFrame = (FRAME)(getLP() + thisFrameSize);
newFrame->stack = newstack;
} else {
/* 所剩余的空间足够再存放一个新的frame结构 */
ASSERTING_NO_ALLOCATION
/* Set the local variable pointer to point to the start */
/* of the local variables in the execution stack */
setLP(prev_sp + 1);
newFrame = (FRAME)(getSP() + thisLocalCount + 1);
newFrame->stack = stack;
END_ASSERTING_NO_ALLOCATION
}
#if ENABLE_JAVA_DEBUGGER
/*
* Although the GC doesn't need to zero out the locations, the debugger
* code needs to have unallocated objects zeroed out on the stack, else
* it will try to dereference them when the debugger asks for the local
* variables.
*/
if (vmDebugReady)
memset(getLP() + thisArgCount, 0, thisLocalCount << log2CELL);
#endif
/* Fill out the remaining fields in the stack frame */
ASSERTING_NO_ALLOCATION
/* Initialize info needed for popping the stack frame later on */
newFrame->previousSp = prev_sp;
newFrame->previousIp = getIP();
newFrame->previousFp = getFP();
/* Initialize the frame to execute the given method */
newFrame->thisMethod = thisMethod;
newFrame->syncObject = NIL; /* Initialized later if necessary */
/* Change virtual machine registers to execute the new method */
setFP(newFrame);
// 由于newFrame 现在指向FRAME结构的低端,而且其类型为指向FRAME的指针,所以把newFrame + 1 会得到一个指向FRAME后的第一个内存指针的指针
// 然后,把方才得到的指针强转成cell的指针后,减一可以得到一个指向FRAME结构顶端的指针,并赋值为sp
setSP((cell*)(newFrame + 1) - 1);
setIP(thisMethod->u.java.code);
setCP(thisMethod->ofClass->constPool);
#if INCLUDEDEBUGCODE
if (tracemethodcalls || tracemethodcallsverbose) {
frameTracing(thisMethod, "=>", 0);
}
if (traceframes) {
fprintf(stdout,
"Pushed a stack frame (thread: %lx, fp: %lx, sp: %lx, depth: %ld, stack: %lx)\n",
(long)CurrentThread, (long)getFP(),
(long)getSP(), (long)frameDepth(), (long)stack);
}
#endif /* INCLUDEDEBUGCODE */
END_ASSERTING_NO_ALLOCATION
}
在kvm内部定义了如下几个变量:
#define ip_global GlobalState.gs_ip
#define sp_global GlobalState.gs_sp
#define lp_global GlobalState.gs_lp
#define fp_global GlobalState.gs_fp
#define cp_global GlobalState.gs_cp
struct GlobalStateStruct {
BYTE* gs_ip; /* 指向目前kvm正在执行method的Instruction*/
cell* gs_sp; /* 指向目前kvm正在执行method的 操作stack*/
cell* gs_lp; /* 指向目前kvm正在执行method的本地变量*/
FRAME gs_fp; /* 指向目前kvm正在执行method的 frame*/
CONSTANTPOOL gs_cp; /* 指向目前kvm正在执行method所属class的 运行时常量池*/
};
这部分的关系如图:
首先先看下面这段代码:
cell* prev_sp = getSP() - thisArgCount; /* Very volatile! 获得之前的sp*/
不管在kvm还是在jvm,其两个方法之间的栈帧有一部分是重叠的,其目标是减少参数的复制.以上代码的情况如下所示:
其他的结合代码注释和以上2图,可以很清楚的理解,这里就不做过多解释.
pushStackAsType
代码如下:
pushStackAsType(CustomCodeCallbackFunction,
initInitialThreadBehaviorFromThread);
#define pushStackAsType(_type_, data) *(_type_ *)(++getSP()) = (data)
通过看上图可以知道,sp是指向操作数栈的低部,那么此处通过push就需要增加sp指针.这很好理解.
initializeClass
初始化类.代码如下:
void initializeClass(INSTANCE_CLASS thisClass)
{
if (thisClass->status == CLASS_ERROR) {
raiseException(NoClassDefFoundError);
} else if (thisClass->status < CLASS_READY) {
if (thisClass->status < CLASS_VERIFIED) {
verifyClass(thisClass); // 验证class
}
/*
* VerifyError will have been thrown or status will be
* CLASS_VERIFIED. We can skip execution of <clinit> altogether if
* it does not exists AND the superclass is already initialised.
*/
if ((thisClass->superClass == NULL ||
thisClass->superClass->status == CLASS_READY) &&
getSpecialMethod(thisClass,clinitNameAndType) == NULL) {
setClassStatus(thisClass,CLASS_READY);
}
else {
TRY {
pushFrame(RunCustomCodeMethod);
pushStackAsType(CustomCodeCallbackFunction, &runClinit);
pushStackAsType(INSTANCE_CLASS, thisClass);
pushStackAsType(long, 1);
} CATCH (e) {
/* Stack overflow */
setClassStatus(thisClass, CLASS_ERROR);
THROW(e);
} END_CATCH
}
}
}
此处可以分为两部分.
- 验证class ,这部分可以参考 kvm 验证-模拟字节码执行,kvm类加载-007,StackMap属性解析
- 操作栈帧. 这部分上文有介绍
初始化系统类
此处的代码如下:
initializeClass(JavaLangOutOfMemoryError);
initializeClass(JavaLangSystem);
initializeClass(JavaLangString);
initializeClass(JavaLangThread);
initializeClass(JavaLangClass);
总结
通过本文的一系列的操作,则此时的栈帧如下:
可以看到其依次初始化class,thread,String,System,OutOfMemoryError,应用主类,然后执行主类的main方法.