kvm線程-004-線程切換

本文介紹kvm中線程切換的實現.

通過kvm線程-001的介紹.可以得到如下事實:

  1. 對於通過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
    }
    
  2. 對於主線程,kvm爲其分配的時間片爲: 1000.代碼如下:

    newThread->timeslice = BASETIMESLICE; // 1000
    
  3. 分配的時間片的含有爲: 每執行一次字節碼,就會將時間片減一,若爲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()方法.分別介紹如下:

  1. 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 中有介紹.

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

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