本文介紹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爲循環隊列,這需理解這一數據結構就不能理解.關於循環隊列的講解,這裏就不展開了.可以參考如下鏈接: