UCOSIII(1)——SVC與PenSV實現任務切換

C~Tian 發表於 2020-10-17

本文基於STM32F407ZGT6
——————————————

SVC異常:

SVC(系統服務呼叫,亦簡稱系統呼叫)用於產生系統函式的呼叫請求。
SVC 異常是必須立即得到響應的應用程式執行 SVC 時都是希望所需的請求立即得到響應。
在這裡插入圖片描述
在 UCOS 中並未使用 SVC 這個功能,瞭解一下即可。
在 UCOS 中並未使用 SVC 這個功能,瞭解一下即可。
在 UCOS 中並未使用 SVC 這個功能,瞭解一下即可。

PendSv異常:

  • 由於SVC異常是必須立即得到響應的(若因優先順序不比當前正處理的高,或是其它原因使之無法立即響應,將上訪成硬 fault),應用程式執行SVC 時都是希望所需的請求立即得到響應。
  • PendSV 則不同,它是可以像普通的中斷一樣被懸起的(不像 SVC 那樣會上訪)。OS
    可以利用它“緩期執行”一個異常——直到其它重要的任務完成後才執行動作。

1、懸 起 PendSV 的方法是:手工往 NVIC 的 PendSV 懸起暫存器中寫 1。懸起後,如果優先順序不夠高,則將緩期等待執行。
2、PendSV 的典型使用場合是在上下文切換時(在不同任務之間切換)。異常會自動延遲上下文切換的請求,直到其它的 ISR 都完成了處理後才放行。為實現這個機制,需要把 PendSV 程式設計為最低優先順序的異常。如果 OS 檢測到某 IRQ 正在活動並且被 SysTick 搶佔,它將懸起一個 PendSV異常,以便緩期執行上下文切換。
在這裡插入圖片描述

可以看到uCOS作業系統裡(其實實時作業系統都一樣)各類異常/中斷的優先順序關係:
SYSTICK異常>中斷>PendSv異常
PendSV 異常會自動延遲上下文切換的請求,直到其它的 ISR 都完成了處理後才放行。這樣保證了中斷的快速響應性又保證了作業系統的正常輪轉。

一、將PendSV 異常設定為最低優先順序(這兩段程式碼在os_cpu_a.asm中定義)。

NVIC_INT_CTRL   EQU     0xE000ED04    ; 中斷控制暫存器   ; Interrupt control state register.
NVIC_SYSPRI14   EQU     0xE000ED22    ; 系統優先順序暫存器(2)  ; System priority register (priority 14).
NVIC_PENDSV_PRI EQU         0xFFFF    ; PendSV 中斷優先順序為最低  ; PendSV priority value (lowest).
NVIC_PENDSVSET  EQU     0x10000000    ; 觸發軟體中斷的值   ; Value to trigger PendSV exception.

NVIC_PENDSV_PRI EQU 0xFFFF ;這個語句把PendSV 中斷優先順序為了最低0xFFFF 。

OSStartHighRdy彙編函式:

OSStartHighRdy
    LDR     R0, =NVIC_SYSPRI14   ; 設定 PendSV 的優先順序為最低    ; Set the PendSV exception priority
    LDR     R1, =NVIC_PENDSV_PRI
    STRB    R1, [R0]

    MOVS    R0, #0               ; Set the PSP to 0 for initial context switch call
    MSR     PSP, R0

    LDR     R0, =OS_CPU_ExceptStkBase    ; Initialize the MSP to the OS_CPU_ExceptStkBase
    LDR     R1, [R0]
    MSR     MSP, R1    

    LDR     R0, =NVIC_INT_CTRL    ; Trigger the PendSV exception (causes context switch)
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]              ;觸發PenSv中斷

    CPSIE   I                     ;開中斷   ; Enable interrupts at processor level

OSStartHang
    B       OSStartHang          ;死迴圈,應該不會到這裡的    ; Should never get here
  • LDR R1, =NVIC_PENDSV_PRI;這個語句把PendSV 中斷優先順序設定位0xFFFF,也就是最低。
  • LDR R1, =NVIC_PENDSVSET
    STR R1, [R0]
    ;這兩個語句實現了觸發PendSv異常,也就是說進入OSStartHighRdy函式會觸發PendSv異常,然後會進入PendSv異常服務函式進行任務切換。

OSStartHighRdy 是由 OSStart()呼叫,OSStart()用來開啟多工的,如果多工開啟正常則進入OSStartHighRdy 函式觸發PendSv異常並馬上進行任務切換;如果多工開啟失敗的話就會進 入 OSStartHang函式(這裡沒有貼出來)。

二、OSStart()函式:

OSStart()函式在我們的main函式裡建立第一個任務的時候發生呼叫。(好像也只是呼叫一次,用於啟動系統)

void  OSStart (OS_ERR  *p_err)
{
#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }
#endif

    if (OSRunning == OS_STATE_OS_STOPPED) {
        OSPrioHighRdy   = OS_PrioGetHighest();    //找出目前最高優先順序的函式    /* Find the highest priority                              */
        OSPrioCur       = OSPrioHighRdy;
        OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
        OSTCBCurPtr     = OSTCBHighRdyPtr;
        OSRunning       = OS_STATE_OS_RUNNING;    /*OSRunning變為1所以退出OSStart函式以後,系統開始跑*/
        OSStartHighRdy();                         //呼叫 OSStartHighRdy彙編函式  /* Execute target specific code to start task             */
       *p_err           = OS_ERR_FATAL_RETURN;   /* OSStart() is not supposed to return                    */
    } else {
       *p_err           = OS_ERR_OS_RUNNING;     /* OS is already running                                  */
    }
}

呼叫這個函式一般是在建立第一個任務時,那時系統還沒開始跑;在程式裡面OSRunning = OS_STATE_OS_RUNNING;把系統開始的標誌位OSRunning設定為1。這個函式正常執行結束以後代表著作業系統真正開始跑起來了。
三、PendSV 異常服務函式:
PendSV 異常服務要完成兩個工作:1、儲存上文;2、切換下文
任務之間的切換就是發生在PendSV 異常服務函式裡面。
可以理解為——觸發了PendSV 異常->函式自動跳轉到PendSV 異常服務函式->執行異常服務函式->實現任務的切換。
觸發PendSV 異常的函式:(在os_cpu_a.asm中定義)

  • OSStartHighRdy;用得很少,只是在任務開啟時在OSStart()函式裡呼叫
  • OSCtxSw :實現任務級的任務切換
  • OSIntCtxSw :實現中斷級的任務切換
PendSV_Handler
    CPSID   I        ;關中斷    ; Prevent interruption during context switch
    MRS     R0, PSP   ; 將 psp 的值載入到 R0    ; PSP is process stack pointer
	
	 ; CBZ判0轉移,判斷 R0如果值為 0 ,則跳轉到 OS_CPU_PendSVHandler_nosave
	 ; 進行第一次任務切換的時候,R0 肯定為 0
    CBZ     R0, PendSVHandler_nosave    ; Skip register save the first time

    ;判讀是否使用FPU
	;Is the task using the FPU context? If so, push high vfp registers.
	TST		R14, #0X10
	IT		EQ
	VSTMDBEQ R0!,{S16-S31}
	
    SUBS    R0, R0, #0x20                                       ; Save remaining regs r4-11 on process stack
  ;————————————儲存上下文————————————————————
  ; 手動儲存 CPU 暫存器 R4-R11 的值到當前任務的堆疊
	STM     R0, {R4-R11}

 ; 載入 OSTCBCurPtr 指標的地址到 R1
    LDR     R1, =OSTCBCurPtr                                    ; OSTCBCurPtr->OSTCBStkPtr = SP;
    LDR     R1, [R1]
    STR     R0, [R1]                                            ; R0 is SP of process being switched out

 ;—————————切換上下文—————————————
 ; 實現 OSTCBCurPtr = OSTCBHighRdyPtr
 ; 把下一個要執行的任務的堆疊OSPrioHighRdy載入到 CPU 暫存器中                                                               ; At this point, entire context of process has been saved
PendSVHandler_nosave
    PUSH    {R14}                  ; Save LR exc_return value
    LDR     R0, =OSTaskSwHook      ; OSTaskSwHook();
    BLX     R0
    POP     {R14}

    LDR     R0, =OSPrioCur         ; OSPrioCur   = OSPrioHighRdy;
    LDR     R1, =OSPrioHighRdy
    LDRB    R2, [R1]
    STRB    R2, [R0]

    LDR     R0, =OSTCBCurPtr     ; OSTCBCurPtr = OSTCBHighRdyPtr;
    LDR     R1, =OSTCBHighRdyPtr
    LDR     R2, [R1]
    STR     R2, [R0]
                                ; R0 is new process SP; SP = OSTCBHighRdyPtr->StkPtr;
    LDM     R0, {R4-R11}        ; Restore r4-11 from new process stack
    ADDS    R0, R0, #0x20
   
   ;Is the task using the FPU context? If so, push high vfp registers.
	TST 	R14, #0x10
	IT 		EQ
	VLDMIAEQ R0!, {S16-S31} 
	
	MSR     PSP, R0            ; Load PSP with new process SP
    ORR     LR, LR, #0x04      ; Ensure exception return uses process stack
    CPSIE   I
    BX      LR                 ; Exception return will restore remaining context
    END