FreeRTOS 任務(wù)調(diào)度 任務(wù)切換

@(嵌入式)

Freertos

FreeRtos

簡(jiǎn)述

前面文章 < FreeRTOS 任務(wù)調(diào)度 任務(wù)創(chuàng)建 > 介紹了 FreeRTOS 中如何創(chuàng)建任務(wù)以及其具體實(shí)現(xiàn)。
一般來(lái)說(shuō), 我們會(huì)在程序開(kāi)始先創(chuàng)建若干個(gè)任務(wù), 而此時(shí)任務(wù)調(diào)度器還沒(méi)又開(kāi)始運(yùn)行,因此每一次任務(wù)創(chuàng)建后都會(huì)依據(jù)其優(yōu)先級(jí)插入到就緒鏈表,同時(shí)保證全局變量 pxCurrentTCB 指向當(dāng)前創(chuàng)建的所有任務(wù)中優(yōu)先級(jí)最高的一個(gè),但是任務(wù)還沒(méi)開(kāi)始運(yùn)行。
當(dāng)初始化完畢后,調(diào)用函數(shù) vTaskStartScheduler啟動(dòng)任務(wù)調(diào)度器開(kāi)始開(kāi)始調(diào)度,此時(shí),pxCurrentTCB所指的任務(wù)才開(kāi)始運(yùn)行。
所以, 本章,介紹任務(wù)調(diào)度器啟動(dòng)以及如何進(jìn)行任務(wù)切換。

調(diào)度器涉及平臺(tái)底層硬件操作,本文以Cotex-M3 架構(gòu)為例, 具體可以參考 《Cortex-M3權(quán)威指南》(文末附)

分析的源碼版本是 v9.0.0
(為了方便查看,github 上保留了一份源碼Source目錄下的拷貝)

啟動(dòng)調(diào)度器

創(chuàng)建任務(wù)后,系統(tǒng)不會(huì)自動(dòng)啟動(dòng)任務(wù)調(diào)度器,需要用戶調(diào)用函數(shù) vTaskStartScheduler 啟動(dòng)調(diào)度器。 該函數(shù)被調(diào)用后,會(huì)先創(chuàng)建系統(tǒng)自己需要用到的任務(wù),比如空閑任務(wù) prvIdleTask,定時(shí)器管理的任務(wù)等。 之后, 調(diào)用移植層提供的函數(shù) xPortStartScheduler
代碼解析如下,

void vTaskStartScheduler( void )
{
    BaseType_t xReturn;
    #if( configSUPPORT_STATIC_ALLOCATION == 1 )
    {
        // 采用靜態(tài)內(nèi)存創(chuàng)建空閑任務(wù)
        StaticTask_t *pxIdleTaskTCBBuffer = NULL;
        StackType_t *pxIdleTaskStackBuffer = NULL;
        uint32_t ulIdleTaskStackSize;
        // 獲取靜態(tài)內(nèi)存地址/參數(shù)
        vApplicationGetIdleTaskMemory(
            &pxIdleTaskTCBBuffer, 
            &pxIdleTaskStackBuffer, 
            &ulIdleTaskStackSize );
        // 創(chuàng)建任務(wù)
        // 空閑任務(wù)優(yōu)先級(jí)為 0, 也就是其優(yōu)先級(jí)最低
        // !! 但是, 設(shè)置了特權(quán)位, 所以其運(yùn)行在 特權(quán)模式
        xIdleTaskHandle = xTaskCreateStatic(prvIdleTask, "IDLE", 
            ulIdleTaskStackSize, (void *) NULL, 
            (tskIDLE_PRIORITY | portPRIVILEGE_BIT), 
            pxIdleTaskStackBuffer,
            pxIdleTaskTCBBuffer); 
        
        if( xIdleTaskHandle != NULL )
        {
            xReturn = pdPASS;
        }
        else
        {
            xReturn = pdFAIL;
        }
    }
    #else
    {
        // 動(dòng)態(tài)申請(qǐng)內(nèi)存創(chuàng)建任務(wù)
        xReturn = xTaskCreate(prvIdleTask,
            "IDLE", configMINIMAL_STACK_SIZE,
            (void *)NULL,
            (tskIDLE_PRIORITY | portPRIVILEGE_BIT),
            &xIdleTaskHandle );     
    }
    #endif
    
    // 如果工程使用了軟件定時(shí)器, 需要?jiǎng)?chuàng)建定時(shí)器任務(wù)進(jìn)行管理
    #if ( configUSE_TIMERS == 1 )
    {
        if( xReturn == pdPASS )
        {
            xReturn = xTimerCreateTimerTask();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    #endif
    
    if( xReturn == pdPASS )
    {
        
        // 關(guān)閉中斷, 避免調(diào)度器運(yùn)行前節(jié)拍定時(shí)器產(chǎn)生中斷
        // 中斷在第一個(gè)任務(wù)啟動(dòng)時(shí)恢復(fù)
        portDISABLE_INTERRUPTS();
        
        #if ( configUSE_NEWLIB_REENTRANT == 1 )
        {
            // 如果使用了這個(gè)庫(kù)
            // 更新第一個(gè)任務(wù)的的指針到全局變量
            _impure_ptr = &( pxCurrentTCB->xNewLib_reent );
        }
        #endif
        
        // 初始化變量
        xNextTaskUnblockTime = portMAX_DELAY;
        xSchedulerRunning = pdTRUE;
        xTickCount = ( TickType_t ) 0U;
        
        // 如果啟動(dòng)統(tǒng)計(jì)任務(wù)運(yùn)行時(shí)間, 宏 configGENERATE_RUN_TIME_STATS = 1
        // 需要定義以下宏, 初始化一個(gè)定時(shí)器用于該功能 
        portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

        // 設(shè)置系統(tǒng)節(jié)拍計(jì)數(shù)器, 啟動(dòng)任務(wù)
        // 硬件相關(guān), 由系統(tǒng)移植層提供, 下面介紹
        if( xPortStartScheduler() != pdFALSE )
        {
            // 不會(huì)運(yùn)行到這里, 如果調(diào)度器運(yùn)行正常
        }
        else
        {
            // 當(dāng)調(diào)用 xTaskEndScheduler()才會(huì)來(lái)到這里
        }
    }
    else
    {
        // 內(nèi)存不足,創(chuàng)建空閑任務(wù)/定時(shí)任務(wù)失敗, 調(diào)度器啟動(dòng)失敗
        configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
    }

    // 預(yù)防編譯器警告
    ( void ) xIdleTaskHandle;
}

移植層調(diào)度器

上面提到, 創(chuàng)建系統(tǒng)所需任務(wù)和初始化相關(guān)靜態(tài)變量后, 系統(tǒng)調(diào)用了 xPortStartScheduler設(shè)置節(jié)拍定時(shí)器和啟動(dòng)第一個(gè)任務(wù),開(kāi)始系統(tǒng)正常運(yùn)行調(diào)度。 而對(duì)于不同架構(gòu)平臺(tái),該函數(shù)的實(shí)現(xiàn)可能存在不同,以下, 拿比較常用的 Cotex-M3 架構(gòu)舉例。
對(duì)于 M3, 可以在源碼目錄下 /Source/portable/GCC/ARM_CM3/port.c 看到該函數(shù)的實(shí)現(xiàn)。

與 FreeRTOS 任務(wù)優(yōu)先級(jí)相反, Cotex-M3 優(yōu)先級(jí)值越小, 優(yōu)先級(jí)越高。 Cotex-M3的優(yōu)先級(jí)配置寄存器考慮器件移植而向高位對(duì)齊,實(shí)際可用的 CPU 會(huì)裁掉表達(dá)優(yōu)先級(jí)低端的有效位,以減少優(yōu)先級(jí)數(shù)。 舉例子說(shuō), 加入平臺(tái)支持3bit 表示優(yōu)先級(jí),則其優(yōu)先級(jí)配置寄存器的高三位可以編程寫入,其他位被屏蔽,不管寫入何值,重新讀回都是0。
另外提供搶占優(yōu)先級(jí)和子優(yōu)先級(jí)分段配置相關(guān),詳細(xì)閱讀 《Cortex-M3權(quán)威指南》

在系統(tǒng)調(diào)度過(guò)程中,主要涉及到的三個(gè)異常:

  • SVC 系統(tǒng)服務(wù)調(diào)用
    操作系統(tǒng)通常不讓用戶程序直接訪問(wèn)硬件,而是通過(guò)提供一些系統(tǒng)服務(wù)函數(shù)。 這里主要觸發(fā)后,在異常服務(wù)中啟動(dòng)第一個(gè)任務(wù)
  • PendSV 可懸起系統(tǒng)調(diào)用
    相比 SVC, PenndSV 異常后可能不會(huì)馬上響應(yīng), 等到其他高優(yōu)先級(jí)中斷處理后才響應(yīng)。 用于上下文切換,同時(shí)保證其他中斷可以被及時(shí)響應(yīng)處理。
  • SysTick 節(jié)拍定時(shí)器
    在沒(méi)有高優(yōu)先級(jí)任務(wù)強(qiáng)制下,同優(yōu)先級(jí)任務(wù)按時(shí)間片輪流執(zhí)行,每次SysTick中斷,下一個(gè)任務(wù)將獲得一個(gè)時(shí)間片。
BaseType_t xPortStartScheduler( void )
{
    configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );
    #if( configASSERT_DEFINED == 1 )
    {
        volatile uint32_t ulOriginalPriority;
        // 取出中斷優(yōu)先級(jí)寄存器
        volatile uint8_t * const pucFirstUserPriorityRegister = 
            (volatile uint8_t * const) (portNVIC_IP_REGISTERS_OFFSET_16 +
                        portFIRST_USER_INTERRUPT_NUMBER);
        volatile uint8_t ucMaxPriorityValue;

        // 保存原有優(yōu)先級(jí)寄存器值
        ulOriginalPriority = *pucFirstUserPriorityRegister;

        // 判斷平臺(tái)支持優(yōu)先級(jí)位數(shù)
        // 先全寫 1
        *pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
        // 重新讀回, 不能設(shè)置的位依然是 0
        ucMaxPriorityValue = *pucFirstUserPriorityRegister;
        // 確保用戶設(shè)置優(yōu)先級(jí)不會(huì)超出范圍
        ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;
        
        // 判斷有幾個(gè)1, 得到對(duì)應(yīng)優(yōu)先級(jí)數(shù)最大值
        ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
        while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
        {
            ulMaxPRIGROUPValue--;
            ucMaxPriorityValue <<= ( uint8_t ) 0x01;
        }
        ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
        ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
        
        // 恢復(fù)優(yōu)先級(jí)配置寄存器值
        *pucFirstUserPriorityRegister = ulOriginalPriority;
    }
    #endif /* conifgASSERT_DEFINED */

    // 設(shè)置 PendSV 和 SysTIck 異常優(yōu)先級(jí)最低
    // 保證系統(tǒng)會(huì)話切換不會(huì)阻塞系統(tǒng)其他中斷的響應(yīng)
    portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;
    portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;

    // 初始化系統(tǒng)節(jié)拍定時(shí)器
    vPortSetupTimerInterrupt();
    // 初始化邊界嵌套計(jì)數(shù)器
    uxCriticalNesting = 0;

    // 觸發(fā) svc 異常 啟動(dòng)第一個(gè)任務(wù)
    prvPortStartFirstTask();

    /* Should not get here! */
    prvTaskExitError();
    return 0;
}

啟動(dòng)第一個(gè)任務(wù)

函數(shù)中調(diào)用了 prvPortStartFirstTask 來(lái)啟動(dòng)第一個(gè)任務(wù), 該函數(shù)重新初始化了系統(tǒng)的棧指針,表示 FreeRtos 開(kāi)始接手平臺(tái)的控制, 同時(shí)通過(guò)觸發(fā) SVC 系統(tǒng)調(diào)用,運(yùn)行第一個(gè)任務(wù)。具體實(shí)現(xiàn)如下

static void prvPortStartFirstTask( void )
{
    __asm volatile(
    " ldr r0, =0xE000ED08   \n" /*向量表偏移寄存器地址 CotexM3*/
    " ldr r0, [r0]          \n" /*取向量表地址*/
    " ldr r0, [r0]          \n" /*取 MSP 初始值*/
    /*重置msp指針 宣示 系統(tǒng)接管*/
    " msr msp, r0           \n"
    " cpsie i               \n" /*開(kāi)中斷*/
    " cpsie f               \n" /*開(kāi)異常*/
    /*流水線相關(guān)*/
    " dsb                   \n" /*數(shù)據(jù)同步隔離*/
    " isb                   \n" /*指令同步隔離*/
    /*觸發(fā)異常 啟動(dòng)第一個(gè)任務(wù)*/
    " svc 0                 \n"
    " nop                   \n"
    );
}

前面創(chuàng)建任務(wù)的文章介紹過(guò), 任務(wù)創(chuàng)建后, 對(duì)其棧進(jìn)行了初始化,使其看起來(lái)和任務(wù)運(yùn)行過(guò)后被系統(tǒng)中斷切換了一樣。 所以,為了啟動(dòng)第一個(gè)任務(wù),觸發(fā) SVC 異常后,異常處理函數(shù)中直接執(zhí)行現(xiàn)場(chǎng)恢復(fù), 把 pxCurrentTCB "恢復(fù)"到運(yùn)行狀態(tài)。

(另外,Cotex-M3 具有三級(jí)流水線,所以切換任務(wù)的時(shí)候需要清除預(yù)取的指令,避免錯(cuò)誤。)

對(duì)于 Cotex-M3 , 其代碼實(shí)現(xiàn)如下,

void vPortSVCHandler( void )
{
    __asm volatile (
    /*取 pxCurrentTCB 的地址*/
    "ldr r3, pxCurrentTCBConst2      \n"
    /*取出 pxCurrentTCB 的值 : TCB 地址*/
    "ldr r1, [r3]                    \n" 
    /*取出 TCB 第一項(xiàng) : 任務(wù)的棧頂 */
    "ldr r0, [r1]                   \n"
    /*恢復(fù)寄存器數(shù)據(jù)*/
    "ldmia r0!, {r4-r11}            \n" 
    /*設(shè)置線程指針: 任務(wù)的棧指針*/
    "msr psp, r0                    \n" 
    /*流水線清洗*/
    "isb                            \n"
    "mov r0, #0                     \n"
    "msr    basepri, r0             \n"
    /*設(shè)置返回后進(jìn)入線程模式*/
    "orr r14, #0xd                  \n"
    "bx r14                         \n"
    "                               \n"
    ".align 4               \n"
    "pxCurrentTCBConst2: .word pxCurrentTCB     \n"
    );
}

異常返回后, 系統(tǒng)進(jìn)入線程模式, 自動(dòng)從堆?;謴?fù)PC等寄存器,而由于此時(shí)棧指針已經(jīng)更新指向?qū)?yīng)準(zhǔn)備運(yùn)行任務(wù)的棧,所以,程序會(huì)從該任務(wù)入口函數(shù)開(kāi)始執(zhí)行。
到此, 第一個(gè)任務(wù)啟動(dòng)。

前面提到, 第一個(gè)任務(wù)啟動(dòng)通過(guò) SVC 異常, 而后續(xù)的任務(wù)切換, 使用的是 PendSV 異常, 而其對(duì)應(yīng)的服務(wù)函數(shù)是 xPortPendSVHandler。 后續(xù)介紹任務(wù)切換再分析。

任務(wù)切換

FreeRTOS 支持時(shí)間片輪序和優(yōu)先級(jí)搶占。系統(tǒng)調(diào)度器通過(guò)調(diào)度算法確定當(dāng)前需要獲得CPU 使用權(quán)的任務(wù)并讓其處于運(yùn)行狀態(tài)。對(duì)于嵌入式系統(tǒng),某些任務(wù)需要獲得快速的響應(yīng),如果使用時(shí)間片,該任務(wù)可能無(wú)法及時(shí)被運(yùn)行,因此搶占調(diào)度是必須的,高優(yōu)先級(jí)的任務(wù)一旦就緒就能及時(shí)運(yùn)行;而對(duì)于同優(yōu)先級(jí)任務(wù),系統(tǒng)根據(jù)時(shí)間片調(diào)度,給予每個(gè)任務(wù)相同的運(yùn)行時(shí)間片,保證每個(gè)任務(wù)都能獲得CPU 。

  1. 最高優(yōu)先級(jí)任務(wù) Task 1 運(yùn)行,直到其被阻塞或者掛起釋放CPU
  2. 就緒鏈表中最高優(yōu)先級(jí)任務(wù)Task 2 開(kāi)始運(yùn)行, 直到...
    1. 調(diào)用接口進(jìn)入阻塞或者掛起狀態(tài)
    2. 任務(wù) Task 1 恢復(fù)并搶占 CPU 使用權(quán)
    3. 同優(yōu)先級(jí)任務(wù)TASK 3 就緒,時(shí)間片調(diào)度
  3. 沒(méi)有用戶任務(wù)執(zhí)行,運(yùn)行系統(tǒng)空閑任務(wù)。

FreeRTOS 在兩種情況下執(zhí)行任務(wù)切換:

  1. 同等級(jí)任務(wù)時(shí)間片用完,提前掛起觸發(fā)切換
    在 SysTick 節(jié)拍計(jì)數(shù)器中斷中觸發(fā)異常
  2. 高優(yōu)先任務(wù)恢復(fù)就緒(如信號(hào)量,隊(duì)列等阻塞、掛起狀態(tài)下退出)時(shí)搶占
    最終都是通過(guò)調(diào)用移植層提供的 portYIELD() 宏懸起 PendSV 異常

但是無(wú)論何種情況下,都是通過(guò)觸發(fā)系統(tǒng) PendSV 異常,在該服務(wù)程序中完成切換。
使用該異常切換上下文的原因是保證切換不會(huì)影響到其他中斷的及時(shí)響應(yīng)(切換上下文搶占了 ISR 的執(zhí)行,延時(shí)時(shí)間不可預(yù)知,對(duì)于實(shí)時(shí)系統(tǒng)是無(wú)法容忍的),在SysTick 中或其他需要進(jìn)行任務(wù)切換的地方懸起一個(gè) PendSV 異常,系統(tǒng)會(huì)直到其他所有 ISR 都完成處理后才執(zhí)行該異常的服務(wù)程序,進(jìn)行上下文切換。

系統(tǒng)響應(yīng) PendSV 異常,在該中斷服務(wù)程序中,保存當(dāng)前任務(wù)現(xiàn)場(chǎng), 選擇切換的下一個(gè)任務(wù),進(jìn)行任務(wù)切換,退出異常恢復(fù)線程模式運(yùn)行新任務(wù),完成任務(wù)切換。

以下是 Cotex-M3 的服務(wù)程序,
首先先要明確的是,系統(tǒng)進(jìn)入異常處理程序的時(shí)候,使用的是主堆棧指針 MSP, 而一般情況下運(yùn)行任務(wù)使用的線程模式使用的是進(jìn)程堆棧指針 PSP。后者使用是系統(tǒng)設(shè)置的,前者是硬件強(qiáng)制設(shè)置的。
對(duì)應(yīng)這兩個(gè)指針,系統(tǒng)有兩種堆棧,系統(tǒng)內(nèi)核和異常程序處理使用的是主堆棧,MSP 指向其棧頂。而對(duì)應(yīng)而不同任務(wù),我們?cè)趧?chuàng)建時(shí)為其分配了空間,作為該任務(wù)的堆棧,在該任務(wù)運(yùn)行時(shí),由系統(tǒng)設(shè)置進(jìn)程堆棧 PSP 指向該棧頂。
如下分析該服務(wù)函數(shù)的執(zhí)行:

void xPortPendSVHandler( void )
{
    /* This is a naked function. */
    __asm volatile
    (
    /*取出當(dāng)前任務(wù)的棧頂指針 也就是 psp -> R0*/
    "   mrs r0, psp                         \n"
    "   isb                                 \n"
    "                                       \n"
    /*取出當(dāng)前任務(wù)控制塊指針 -> R2*/
    "   ldr r3, pxCurrentTCBConst           \n"
    "   ldr r2, [r3]                        \n"
    "                                       \n"
    /*R4-R11 這些系統(tǒng)不會(huì)自動(dòng)入棧,需要手動(dòng)推到當(dāng)前任務(wù)的堆棧*/
    "   stmdb r0!, {r4-r11}                 \n"
    /*最后,保存當(dāng)前的棧頂指針 
    R0 保存當(dāng)前任務(wù)棧頂?shù)刂?    [R2] 是 TCB 首地址,也就是 pxTopOfStack
    下次,任務(wù)激活可以重新取出恢復(fù)棧頂,并取出其他數(shù)據(jù)
    */
    "   str r0, [r2]                        \n"
    "                                       \n"
    /*保護(hù)現(xiàn)場(chǎng),調(diào)用函數(shù)更新下一個(gè)準(zhǔn)備運(yùn)行的新任務(wù)*/
    "   stmdb sp!, {r3, r14}                \n"
    /*設(shè)置優(yōu)先級(jí) 第一個(gè)參數(shù),
    即:configMAX_SYSCALL_INTERRUPT_PRIORITY
    進(jìn)入臨界區(qū)*/
    "   mov r0, %0                          \n"
    "   msr basepri, r0                     \n"
    "   bl vTaskSwitchContext               \n"
    "   mov r0, #0                          \n"
    "   msr basepri, r0                     \n"
    "   ldmia sp!, {r3, r14}                \n"
    "                                       \n"
    /*函數(shù)返回 退出臨界區(qū)
    pxCurrentTCB 指向新任務(wù)
    取出新的 pxCurrentTCB 保存到 R1
    */
    "   ldr r1, [r3]                        \n"
    /*取出新任務(wù)的棧頂*/
    "   ldr r0, [r1]                        \n"
    /*恢復(fù)手動(dòng)保存的寄存器*/
    "   ldmia r0!, {r4-r11}                 \n"
    /*設(shè)置線程指針 psp 指向新任務(wù)棧頂*/
    "   msr psp, r0                         \n"
    "   isb                                 \n"
    /*返回, 硬件執(zhí)行現(xiàn)場(chǎng)恢復(fù)
    開(kāi)始執(zhí)行任務(wù)
    */
    "   bx r14                              \n"
    "                                       \n"
    "   .align 4                            \n"
    "pxCurrentTCBConst: .word pxCurrentTCB  \n"
    ::"i"(configMAX_SYSCALL_INTERRUPT_PRIORITY)
    );
}

在服務(wù)程序中,調(diào)用了函數(shù) vTaskSwitchContext 獲取新的運(yùn)行任務(wù), 該函數(shù)會(huì)更新當(dāng)前任務(wù)運(yùn)行時(shí)間,檢查任務(wù)堆棧使用是是否溢出,然后調(diào)用宏 taskSELECT_HIGHEST_PRIORITY_TASK()設(shè)置新的任務(wù)。該宏實(shí)現(xiàn)分兩種情況,普通情況下使用的定義如下

UBaseType_t uxTopPriority = uxTopReadyPriority;
while(listLIST_IS_EMPTY(&(pxReadyTasksLists[uxTopPriority])))
{
    --uxTopPriority;
}
    
listGET_OWNER_OF_NEXT_ENTRY(pxCurrentTCB, 
    &(pxReadyTasksLists[ uxTopPriority]));

uxTopReadyPriority = uxTopPriority;

通過(guò) while 查找當(dāng)前存在就緒任務(wù)的最高優(yōu)先級(jí)鏈表,獲取鏈表項(xiàng)設(shè)置任務(wù)指針。(通一個(gè)鏈表內(nèi)多個(gè)項(xiàng)目通過(guò)指針循環(huán),實(shí)現(xiàn)同優(yōu)先級(jí)任務(wù)獲得相同時(shí)間片執(zhí)行)。

而另外一種方式,需要平臺(tái)支持,主要差別是查找最高任務(wù)優(yōu)先級(jí),平臺(tái)支持利用平臺(tái)特性,效率會(huì)更高,但是移植性就不好說(shuō)了。

發(fā)生異常跳轉(zhuǎn)到異常處理服務(wù)前,自動(dòng)執(zhí)行的現(xiàn)場(chǎng)保護(hù)會(huì)保留返回模式(線程模式),使用堆棧指針等信息,所以,結(jié)束任務(wù)切換, 通過(guò)執(zhí)行 bx r14返回,系統(tǒng)會(huì)自動(dòng)恢復(fù)現(xiàn)場(chǎng)(From stack),開(kāi)始運(yùn)行任務(wù)。

至此,任務(wù)切換完成。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 178,672評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,965評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,690評(píng)論 6 413
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 56,019評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 43,188評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,438評(píng)論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,667評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評(píng)論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,845評(píng)論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 35,252評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 36,590評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,384評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,635評(píng)論 2 380

推薦閱讀更多精彩內(nèi)容

  • 又來(lái)到了一個(gè)老生常談的問(wèn)題,應(yīng)用層軟件開(kāi)發(fā)的程序員要不要了解和深入學(xué)習(xí)操作系統(tǒng)呢? 今天就這個(gè)問(wèn)題開(kāi)始,來(lái)談?wù)劜?..
    tangsl閱讀 4,158評(píng)論 0 23
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,832評(píng)論 18 139
  • 一、溫故而知新 1. 內(nèi)存不夠怎么辦 內(nèi)存簡(jiǎn)單分配策略的問(wèn)題地址空間不隔離內(nèi)存使用效率低程序運(yùn)行的地址不確定 關(guān)于...
    SeanCST閱讀 7,865評(píng)論 0 27
  • 一、你的痛點(diǎn) 日常生活中,大家都有這樣的痛點(diǎn):別人說(shuō)什么,都略懂一點(diǎn)。但無(wú)法系統(tǒng)地整理出來(lái),怎么辦?是因?yàn)闆](méi)...
    算法成癮者閱讀 250評(píng)論 1 2
  • 忘了有多久沒(méi)有這樣靜心思考的時(shí)光了。有時(shí),不是為了寫好什么東西,只是覺(jué)得,記錄我的思考,哪怕只是想象。書寫我的心聲...
    鑒空閱讀 121評(píng)論 0 0