cortex-M3 的SVC、PendSV异常,与操作系统(ucos实时系统)
SVC異常是??
PendSV異常是??
ucos 任務切換時機??
ucos 如何滿足實時性(實現)??
ucos中,systick的優先級?
SVC和PendSV
SVC(系統服務調用,亦簡稱系統調用)和PendSV(可懸起系統調用),它們多用于在操作系統之上的軟件開發中。
SVC:
SVC 用于產生系統函數的調用請求。?
例如,操作系統不讓用戶程序直接訪問硬件,而是通過提供一些系統服務函數,用戶程序使用SVC 發出對系統服務函數的呼叫請求,以這種方法調用它們來間接訪問硬件。?
因此,?
當用戶程序想要控制特定的硬件時,它就會產生一個SVC 異常,?
然后操作系統提供的SVC 異常服務例程得到執行,?
它再調用相關的操作系統函數,?
后者完成用戶程序請求的服務。?
這種“提出要求——得到滿足”的方式,很好、很強大、很方便、很靈活、很能可持續發展。?
首先,它使用戶程序從控制硬件的繁文縟節中解脫出來,而是由操作系統 負責控制具體的硬件。?
第二,操作系統的代碼可以經過充分的測試,從而能使系統更加健壯和可靠。?
第三,它使用戶程序無需在特權級下執行,用戶程序無需承擔因誤操作而癱瘓整個系統的風險。?
第四,通過SVC 的機制,還讓用戶程序變得與硬件無關,因此在開發應用程序時無需了解硬件的操作細節,從而簡化了開發的難度和繁瑣度,并且使應用程序跨硬件平臺移植成為可能。開發應用程序唯一需要知道的就是操作系統提供的應用編程接口(API),并且了解各個請求代號和參數表,然后就可以使用SVC 來提出要求了(事實上,為使用方便,操作系統往往會提供一層封皮,以使系統調用的形式看起來和普通的函數調用一致。各封皮函數會正確使用SVC指令來執行系統調用——譯者注)。?
其實,嚴格地講,操作硬件的工作是由設備驅動程序完成的,只是對應用程序來說,它們也是操作系統的一部分。如圖7.14 所示?
?
SVC 異常通過執行”SVC”指令來產生。該指令需要一個立即數,充當系統調用代號。SVC異常服務例程稍后會提取出此代號,從而解釋本次調用的具體要求,再調用相應的服務函數。例如,
1.SVC 0x3 ; 調用3 號系統服務
在SVC 服務例程執行后,上次執行的SVC 指令地址可以根據自動入棧的返回地址計算出。找到了SVC 指令后,就可以讀取該SVC 指令的機器碼,從機器碼中萃取出立即數,就獲知了請求執行的功能代號。如果用戶程序使用的是PSP,服務例程還需要先執行
1.MRS Rn,PSP
指令來獲取應用程序的堆棧指針。通過分析LR 的值,可以獲知在SVC 指令執行時,正在使用哪個堆棧。?
由CM3 的中斷優先級模型可知,你不能在SVC 服務例程中嵌套使用SVC 指令(事實上這樣做也沒意義),因為同優先級的異常不能搶占自身。這種作法會產生一個用法fault。同理,在NMI 服務例程中也不得使用SVC,否則將觸發硬fault。
PendSV:
另一個相關的異常是PendSV(可懸起的系統調用),它和SVC 協同使用。?
一方面,SVC異常是必須立即得到響應的(若因優先級不比當前正處理的高,或是其它原因使之無法立即響應,將上訪成硬fault——譯者注),應用程序執行SVC 時都是希望所需的請求立即得到響應。?
另一方面,PendSV 則不同,它是可以像普通的中斷一樣被搶占掛起的(不像SVC 那樣會上訪)。?
操作系統 可以利用它“緩期執行”一個異常——直到其它重要的任務完成后才執行動作。
PendSV是什么??
根據 權威指南。PendSV是為系統設備而設的“可懸掛請求”(pendable request)。
上下文切換 不能在中斷中進行,會導致中斷延期。為了解決這個問題,使用 PendSV。PendSV可以掛起,也就是等到別的 ISR結束后緩期執行。
為了實現緩期執行PendSV,PendSV一定要被設置為最低優先級的異常。
掛起PendSV 的方法是:軟件實現OSIntCtxSw()函數,向NVIC 的PendSV 懸起寄存器中寫1。
NVIC_INT_CTRL ? EQU ? ? 0xE000ED04 ? ; Interrupt control state register.
NVIC_PENDSVSET ?EQU ? ? 0x10000000 ? ; Value to trigger PendSV exception.
OSIntCtxSw
? ? LDR ? ? R0, =NVIC_INT_CTRL ? ? ? ; Trigger the PendSV exception (causes context switch)
? ? LDR ? ? R1, =NVIC_PENDSVSET
? ? STR ? ? R1, [R0]
? ? BX ? ? ?LR
掛起后,如果優先級不夠高,則將緩期等待執行。?
PendSV 的典型使用場合是在上下文切換時(在不同任務之間切換)。
操作系統,上下文切換 實例:
場景假設:一個系統(按時間片輪轉調度的系統)中有兩個就緒的任務(A任務、B任務),?
上下文切換被觸發的場合可以是:
執行一個系統調用
系統滴答定時器(SYSTICK)中斷,(輪轉調度中需要)
A、B兩個就緒任務,通過SysTick 異常啟動上下文切換。如圖7.15 所示。?
?
上圖是兩個任務輪轉調度的示意圖。?
但若在產生SysTick 異常時正在響應一個中斷,則SysTick 異常會搶占其ISR。?
在這種情況下,操作系統 不可以執行上下文切換,否則將使中斷請求被延遲,?
而且在真實系統中延遲時間還往往不可預知——任何有一丁點實時要求的系統都決不能容忍這種事。?
因此,在CM3 中也是嚴禁沒商量——如果操作系統 在某中斷活躍時嘗試切入線程模式,將觸犯用法fault 異常。?
?
為解決此問題,早期的操作系統 大多會在SysTick 異常中 檢測當前是否有中斷在活躍中,只有沒有任何中斷需要響應時,才執行上下文切換(切換期間無法響應中斷)。?
然而,這種方法的弊端在于,?
它可能把任務切換動作拖延很久(因為如果搶占了IRQ,則本次SysTick 在執行后不得作上下文切換,只能等待下一次SysTick 異常),尤其是當某中斷源的頻率和SysTick 異常的頻率比較接近時,會發生“共振”。?
現在好了,PendSV 來完美解決這個問題了(產生SysTick 異常時正在響應一個中斷,SysTick 異常會搶占其ISR。此時,操作系統 不可以執行上下文切換,否則將使中斷請求被延遲):?
把PendSV 編程為最低優先級的異常,PendSV 異常會自動延遲上下文切換的請求,直到其它的ISR 都完成了處理后才放行。?
如果操作系統 檢測到某IRQ 正在活動并且被SysTick 搶占,它將懸起一個PendSV 異常,以便緩期執行上下文切換。如圖7.17 所示?
流水賬記錄如下:?
1. 任務 A 呼叫SVC 來請求任務切換(例如,等待某些工作完成)?
2. OS 接收到請求,做好上下文切換的準備,并且pend 一個PendSV 異常。?
3. 當 CPU 退出SVC 后,它立即進入PendSV,從而執行上下文切換。?
4. 當 PendSV 執行完畢后,將返回到任務B,同時進入線程模式。?
5. 發生了一個中斷,并且中斷服務程序開始執行?
6. 在 ISR 執行過程中,發生SysTick 異常,并且搶占了該ISR。?
7. OS 執行必要的操作,然后pend 起PendSV 異常以作好上下文切換的準備。?
8. 當 SysTick 退出后,回到先前被搶占的ISR 中,ISR 繼續執行?
9. ISR 執行完畢并退出后,PendSV 服務例程開始執行,并且在里面執行上下文切換?
10. 當 PendSV 執行完畢后,回到任務A,同時系統再次進入線程模式。
其實,ucos中的實現,于此有些差異,但從結果上看,是一致的(如果systick搶占了其他ISRs,不會在其中執行上下文切換。會等到全部的ISRs執行完畢后(期間一定是無任務調度的),才執行pendsv異常,完成上下文的切換。==差別在于生成pendsv異常的時機。)
ucos 關于 PendSV 異常的應用(上下文切換時機、怎樣滿足實時性):
在systick異常中,執行必要的任務維護更新工作,在退出時,考慮生成pensv異常。?
當且僅當無中斷被搶占時,生成pensv異常,并于pendsv異常中,完成上下文切換工作;?
當每一個中斷/異常處理函數中,均在退出時考慮生成pensv異常,則最終無中斷可執行時,pensv異常一定會生成,并且期間無任務調度。
這可能與上邊描述的不一致,但結果上來看,是一致的。?
下邊以ucos系統的源碼,進行大概的講解:
中斷/異常處理通用模板:
? ? OS_CPU_SR ?cpu_sr;?
? ? OS_ENTER_CRITICAL(); /* Tell uC/OS-II that we are starting an ISR ? ? ? ? ?*/
? ? OSIntNesting++;
? ? OS_EXIT_CRITICAL();
? ? 用戶處理代碼;
? ? void ?OSIntExit (void);//OSIntNesting--;以及可能的調度
systick異常實現(ucos心臟):
void ?OS_CPU_SysTickHandler (void)
{
? ? OS_CPU_SR ?cpu_sr;
? ? OS_ENTER_CRITICAL(); /* Tell uC/OS-II that we are starting an ISR */
? ? OSIntNesting++;
? ? OS_EXIT_CRITICAL();
? ? OSTimeTick(); ?/* Call uC/OS-II's OSTimeTick() ? ? ? */
? ? OSIntExit(); ?/* Tell uC/OS-II that we are leaving the ISR */
}
void OSTimeTick (void)
void ?OSTimeTick (void)
{
更新系統時間,OSTime++;
遍歷OSTCBList 任務控制塊鏈表(已經建立的任務),
? ? 如果任務控制塊OSTCBDly非零,則減一;
? ? 如果等于零,更新OSTCBStat(任務狀態)、OSTCBStatPend(任務掛起狀態)成員;
? ? 如果OSTCBStat等于OS_STAT_RDY(就緒狀態),則將任務放入就緒表中。
}
OSIntExit
/*$PAGE*/
/*
*********************************************************************************************************
* ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? EXIT ISR
*
* Description: This function is used to notify uC/OS-II that you have completed serviving an ISR. ?When
* ? ? ? ? ? ? ?the last nested ISR has completed, uC/OS-II will call the scheduler to determine whether
* ? ? ? ? ? ? ?a new, high-priority task, is ready to run.
*
* Arguments ?: none
*
* Returns ? ?: none
*
* Notes ? ? ?: 1) You MUST invoke OSIntEnter() and OSIntExit() in pair. ?In other words, for every call
* ? ? ? ? ? ? ? ? to OSIntEnter() at the beginning of the ISR you MUST have a call to OSIntExit() at the
* ? ? ? ? ? ? ? ? end of the ISR.
* ? ? ? ? ? ? ?2) Rescheduling is prevented when the scheduler is locked (see OS_SchedLock())
*********************************************************************************************************
*/
void ?OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?/* Allocate storage for CPU status register */
? ? OS_CPU_SR ?cpu_sr = 0;
#endif
? ? if (OSRunning == OS_TRUE) {
? ? ? ? OS_ENTER_CRITICAL();
? ? ? ? if (OSIntNesting > 0) { ? ? ? ? ? ? ? ? ? ? ? ? ? ?/* Prevent OSIntNesting from wrapping ? ? ? */
? ? ? ? ? ? OSIntNesting--;
? ? ? ? }
? ? ? ? if (OSIntNesting == 0) { ? ? ? ? ? ? ? ? ? ? ? ? ? /* Reschedule only if all ISRs complete ... */
? ? ? ? ? ? if (OSLockNesting == 0) { ? ? ? ? ? ? ? ? ? ? ?/* ... and not locked. ? ? ? ? ? ? ? ? ? ? ?*/
? ? ? ? ? ? ? ? OS_SchedNew();
? ? ? ? ? ? ? ? OSTCBHighRdy ?= OSTCBPrioTbl[OSPrioHighRdy];
? ? ? ? ? ? ? ? if (OSPrioHighRdy != OSPrioCur) { ? ? ? ? ?/* No Ctx Sw if current task is highest rdy */
#if OS_TASK_PROFILE_EN > 0
? ? ? ? ? ? ? ? ? ? OSTCBHighRdy->OSTCBCtxSwCtr++; ? ? ? ? /* Inc. # of context switches to this task ?*/
#endif
? ? ? ? ? ? ? ? ? ? OSCtxSwCtr++; ? ? ? ? ? ? ? ? ? ? ? ? ?/* Keep track of the number of ctx switches */
? ? ? ? ? ? ? ? ? ? OSIntCtxSw(); ? ? ? ? ? ? ? ? ? ? ? ? ?/* Perform interrupt level ctx switch ? ? ? */
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? OS_EXIT_CRITICAL();
? ? }
}
我們在進入systick異常時,有執行OSIntNesting++;準備出來時,自然需要OSIntNesting–;?
如果OSIntNesting不等于零,退出systick異常。?
只有OSIntNesting等于零(無其他異常/中斷發生)并且OSLockNesting等于零(無任務調度鎖),才執行OS_SchedNew()查就任務緒表中最高優先級并返回,比較返回的優先級是否為當前運行任務的優先級,?
僅不相等時,執行OSIntCtxSw()函數,生成pendsv異常。OSIntCtxSw()函數的實現見上邊篇幅。?
pendsv異常OS_CPU_PendSVHandler,實現上下文的切換。這里不做解釋。
ucos中,systick的優先級?
PENDSV和SYSTICK屬于系統異常;?
定時器中斷,串口中斷這些屬于外部中斷。?
PENDSV和SYSTICK的中斷優先級可以編程,?
一般要把PENDSV的優先級設置成最低(沒什么好說的)。?
但SYSTICK異常的優先級:
一般無需設置(高于外部中斷的優先級),畢竟這是系統的時鐘源(ucos心臟);
當然,也可根據項目需要(有些外部中斷,項目上要求務必實時),將SYSTICK優先級設置與合適的位置。
確實存在的普遍現象是,很多項目對于實時沒有很高的要求,干脆將PENDSV和SYSTICK的優先級都設置成OxFF。
都是最低優先級,此時因為PENDSV在中斷向量表中排在SYSTICK前面,所以如果PENDSV,SYSTICK同時產生中斷,PENDSV優先中斷。
?
總結
以上是生活随笔為你收集整理的cortex-M3 的SVC、PendSV异常,与操作系统(ucos实时系统)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于stm32芯片解锁方法
- 下一篇: FreeRTOS 查询任务 剩余的栈空间