本文介绍kvm中线程切换的实现.
通过kvm线程-001的介绍.可以得到如下事实:
-
对于通过new Thread() 创建的线程,其分配的时间片为:priority * 1000.其代码如下:
void Java_java_lang_Thread_setPriority0(void) { int priority = popStack(); THREAD VMthread; /* Ensure that the internal thread execution */ /* structure has been created */ START_TEMPORARY_ROOTS DECLARE_TEMPORARY_ROOT(JAVATHREAD, javaThread, popStackAsType(JAVATHREAD)); javaThread->priority = (priority > MAX_PRIORITY ? MAX_PRIORITY : (priority < MIN_PRIORITY ? MIN_PRIORITY : priority)); VMthread = getVMthread(&javaThread); /* The actual VM-level timeslice of the thread is calculated by * multiplying the given priority */ VMthread->timeslice = javaThread->priority * TIMESLICEFACTOR; END_TEMPORARY_ROOTS }
-
对于主线程,kvm为其分配的时间片为: 1000.代码如下:
newThread->timeslice = BASETIMESLICE; // 1000
-
分配的时间片的含有为: 每执行一次字节码,就会将时间片减一,若为0,则需要进行线程切换.关于第三点,在下文介绍.
在FastInterpret方法中,有如下代码:
#if RESCHEDULEATBRANCH
reschedulePoint:
RESCHEDULE
而每次字节码执行完毕后,都会有如下代码:
goto reschedulePoint;
因此实现了上文提到的 每执行一次字节码,就会将时间片减一,若为0,则需要进行线程切换. 这里的关键点是RESCHEDULE,宏展开后如下:
#define RESCHEDULE { \
INC_RESHED // 此处为宏,默认为空操作 \
checkRescheduleValid(); // 此处为宏,默认为空操作 \
if (isTimeToReschedule()) { \
VMSAVE \
reschedule(); \
VMRESTORE \
} \
}
此处使用了多个宏,宏展开的结果为:
if (Timeslice-- == 0) {
GlobalState.gs_ip = ip;
GlobalState.gs_fp = fp;
GlobalState.gs_sp = sp;
GlobalState.gs_lp = lp;
GlobalState.gs_cp = cp;
do {
ulong64 wakeupDelta;
if (AliveThreadCount <= 0) {
return; /* end of program */
}
checkTimerQueue(&wakeupDelta); // 这个在之前的文章有介绍
InterpreterHandleEvent(wakeupDelta); // 这个函数与线程切换关系不大
} while (!SwitchThread());
ip = GlobalState.gs_ip;
fp = GlobalState.gs_fp;
sp = GlobalState.gs_sp;
lp = GlobalState.gs_lp;
cp = GlobalState.gs_cp;
}
这里比较重要的是SwitchThread(),其代码如下:
bool_t SwitchThread(void)
{
THREAD threadToAdd = NIL;
if (CurrentThread != NIL) {
/* 1. 如果当前线程存在异常,则抛出 */
if (CurrentThread->pendingException != NIL) {
fatalError(KVM_MSG_BAD_PENDING_EXCEPTION);
}
if (CurrentThread->state == THREAD_ACTIVE) {
if (RunnableThreads == NULL) {
/* 如果只有一个线程,则不进行切换*/
Timeslice = CurrentThread->timeslice;
return TRUE;
} else {
/* 如果有其他线程,则需要进行切换 */
storeExecutionEnvironment(CurrentThread);
threadToAdd = CurrentThread;
CurrentThread = NIL;
}
} else {
// 如果线程是其他状态,则抛出异常
fatalError(KVM_MSG_ATTEMPTING_TO_SWITCH_TO_INACTIVE_THREAD);
}
}
/* 从RunnableThreads队列中删除队首,即获得一个等待运行的线程 RunnableThreads是循环队列,RunnableThreads指向队尾 */
CurrentThread = removeQueueStart(&RunnableThreads);
/* 将被切换的线程加入到队列中 */
if (threadToAdd != NIL) {
addThreadToQueue(&RunnableThreads, threadToAdd, AT_END);
}
/* 如果没有线程可运行,则返回false*/
if (CurrentThread == NIL) {
return FALSE;
}
#if ENABLEPROFILING
ThreadSwitchCounter++;
#endif
/* 加载寄存器*/
loadExecutionEnvironment(CurrentThread);
#if INCLUDEDEBUGCODE
if (tracethreading) {
/* Diagnostics */
TraceThread(CurrentThread, "Switching to this thread");
}
#endif
/* 分配时间片*/
Timeslice = CurrentThread->timeslice;
/* 如果当前线程有异常,则直接抛出 */
if (CurrentThread->pendingException != NIL) {
char* pending = CurrentThread->pendingException;
CurrentThread->pendingException = NIL;
raiseException(pending);
}
return TRUE;
}
这里需要强调的是: RunnableThreads是循环队列,RunnableThreads指向队尾.定义如下:
THREAD RunnableThreads;
typedef struct threadQueue* THREAD;
而关于threadQueue,在kvm线程-001 中有介绍.
而storeExecutionEnvironment(), loadExecutionEnvironment(CurrentThread) 代码分别如下:
void storeExecutionEnvironment(THREAD thisThread)
{
/* Save the current thread execution environment
* (virtual machine registers) to the thread structure.
*/
thisThread->fpStore = getFP();
thisThread->spStore = getSP();
thisThread->ipStore = getIP();
}
void loadExecutionEnvironment(THREAD thisThread)
{
/* Restore the thread execution environment */
/* (VM registers) from the thread structure */
setFP(thisThread->fpStore);
setLP(FRAMELOCALS(getFP()));
setCP(getFP()->thisMethod->ofClass->constPool);
setSP(thisThread->spStore);
setIP(thisThread->ipStore);
}
这两个方法也是在前文有介绍,这里就不在展开了.
在SwitchThread方法中,比较重要的是: removeQueueStart()方法和addThreadToQueue()方法.分别介绍如下:
-
removeQueueStart() --> 从RunnableThreads队列中删除队首,即获得一个等待运行的线程,使其运行.代码如下:
static THREAD removeQueueStart(THREAD *queue) { THREAD thisThread; START_CRITICAL_SECTION if (*queue == NULL) { thisThread = NULL; } else { thisThread = (*queue)->nextThread; if (thisThread == *queue) {// 如果当前只有一个元素的话,则队列移除后就为空了 *queue = NULL; } else { /* 从队列中删除*/ (*queue)->nextThread = thisThread->nextThread; } thisThread->nextThread = NIL; } END_CRITICAL_SECTION return thisThread; }
START_CRITICAL_SECTION, END_CRITICAL_SECTION为宏,是用来实现锁的.在kvm垃圾收集-002 中有介绍.
-
addThreadToQueue() --> 将被切换的线程,加入到RunnableThreads的队尾.代码如下:
static void addThreadToQueue(THREAD *queue, THREAD thisThread, queueWhere where) { START_CRITICAL_SECTION if (*queue == NIL) { // 如果RunnableThreads为null的话 *queue = thisThread; thisThread->nextThread = thisThread; } else { // 加入到队列中 thisThread->nextThread = (*queue)->nextThread; (*queue)->nextThread = thisThread; if (where == AT_START) { // 如果是加入到队首的话,则thisThread->nextThread = (*queue)->nextThread; (*queue)->nextThread = thisThread; 这两个操作已经完成了 ; } else { /* 如果是队尾的话,则需要将RunnableThreads执行新加入的线程*/ *queue = thisThread; } } END_CRITICAL_SECTION }
关于此处, RunnableThreads为循环队列,这需理解这一数据结构就不能理解.关于循环队列的讲解,这里就不展开了.可以参考如下链接: