我們知道 Cortex-M3 系列單片機(jī)內(nèi)部有雙堆棧機(jī)制。即 Cortex‐M3 擁有兩個(gè)堆棧指針:主堆棧(MSP)和進(jìn)程堆棧(PSP)。任一時(shí)刻只能使用其中的一個(gè)。通過控制寄存器 CONTROL 中的選擇位進(jìn)行控制。
兩個(gè)堆棧指針如下:
- 主堆棧指針( MSP):復(fù)位后缺省使用的堆棧指針,用于操作系統(tǒng)內(nèi)核以及異常處理例程(包括中斷服務(wù)例程)
- 進(jìn)程堆棧指針( PSP):由用戶的應(yīng)用程序代碼使用。
將RTOS 移植到 Cortex-M3 系列單片機(jī)上后,任務(wù)堆棧用的是 PSP,然而任務(wù)切換是在中斷處理函數(shù) PendSV() 中完成的。
那么在任務(wù)切換期間,MCU 在執(zhí)行指令的過程中,是如何選擇堆棧指針呢?
下面逐步進(jìn)行分析。
堆棧的基本操作
堆棧操作就是對(duì)內(nèi)存的讀寫操作,其地址由 SP 給出。寄存器的數(shù)據(jù)通過 PUSH 操作存入堆棧,以后用 POP 操作從堆棧中取回。在 PUSH 與 POP 的操作中, SP 的值會(huì)按堆棧的使用法則自動(dòng)調(diào)整,以保證后續(xù)的 PUSH 不會(huì)破壞先前 PUSH 進(jìn)去的內(nèi)容。
堆棧的功能就是把寄存器的數(shù)據(jù)放入內(nèi)存,當(dāng)一個(gè)任務(wù)或一段子程序執(zhí)行完畢后,能夠恢復(fù)繼續(xù)執(zhí)行。正常情況下, PUSH 與 POP 必須成對(duì)使用,而且參與的寄存器,不論是身份還是先后順序都必須完全一致。當(dāng) PUSH/POP 指令執(zhí)行時(shí), SP 指針的值也根著自減/自增。
Cortex‐M3 使用的是“向下生長(zhǎng)的滿棧”模型。堆棧指針 SP 指向最后一個(gè)被壓入堆棧的 32位數(shù)值。在下一次壓棧時(shí), SP 先自減 4,再存入新的數(shù)值。
POP 操作剛好相反:先從 SP 指針處讀出上一次被壓入的值,再把 SP 指針自增 4 。
在進(jìn)入 ESR 時(shí), CM3 會(huì)自動(dòng)把一些寄存器壓棧,這里使用的是發(fā)生本異常的瞬間正在使用的 SP 指針(MSP 或者是 PSP)。離開 ESR 后,只要 ESR 沒有更改過 CONTROL[1],就依然使用發(fā)生本次異常的瞬間正在使用的 SP 指針來執(zhí)行出棧操作。
堆棧使用控制
已經(jīng)知道了 CM3 的堆棧是分為兩個(gè):主堆棧和進(jìn)程堆棧, 具體使用哪個(gè)堆棧(MSP 還是 PSP) 通過特殊寄存器 CONTROL[1] 來控制。
控制寄存器 CONTROL,有兩個(gè)用途:其一用于定義特權(quán)級(jí)別,其二用于選擇當(dāng)前使用哪個(gè)堆棧指針。
當(dāng) CONTROL[1]=0 時(shí),只使用 MSP,此時(shí)用戶程序和異常 handler 共享同一個(gè)堆棧。這也是復(fù)位后的缺省使用方式。
當(dāng) CONTROL[1]=1 時(shí),線程模式將不再使用 MSP,而改用 PSP(handler 模式永遠(yuǎn)使用 MSP)。這樣做的好處在哪里?原來,在使用 OS 的環(huán)境下,只要 OS 內(nèi)核僅在 handler 模式下執(zhí)行,用戶應(yīng)用程序僅在用戶模式下執(zhí)行,這種雙堆棧機(jī)制防止用戶程序的堆棧錯(cuò)誤破壞 OS 使用的堆棧。
再介紹一下兩個(gè)操作模式, Cortex-M3 支持 兩個(gè)模式和兩個(gè)特權(quán)等級(jí):
- 兩個(gè)模式:handler模式和線程模式
- 兩個(gè)特權(quán)等級(jí):特權(quán)級(jí)和用戶級(jí)
當(dāng)處理器處在線程狀態(tài)下時(shí),既可以使用特權(quán)級(jí),也可以使用用戶級(jí);另一方面, handler 模式總是特權(quán)級(jí)的。在復(fù)位后,處理器進(jìn)入線程模式+特權(quán)級(jí)。
在特權(quán)級(jí)下的代碼可以通過置位 CONTROL[0]來進(jìn)入用戶級(jí)。而不管是任何原因產(chǎn)生了任何異常,處理器都將以特權(quán)級(jí)來運(yùn)行其服務(wù)例程,異常返回后,系統(tǒng)將回到產(chǎn)生異常時(shí)所處的級(jí)別。
用戶級(jí)下的代碼不能再試圖修改 CONTROL[0]來回到特權(quán)級(jí)。它必須通過一個(gè)異常 handler,由那個(gè)異常handler 來修改 CONTROL[0],才能在返回到線程模式后拿到特權(quán)級(jí)。
運(yùn)行在線程模式的用戶代碼使用 PSP,而異常服務(wù)例程則使用 MSP。這兩個(gè)堆棧指針的切換是智能全自動(dòng)的,就在異常服務(wù)的始末由 CM3 硬件處理。
中斷處理過程
響應(yīng)異常的第一個(gè)行動(dòng),就是自動(dòng)保存現(xiàn)場(chǎng)的必要部分:依次把 xPSR, PC, LR, R12 以及 R3-R0 由硬件自動(dòng)壓入適當(dāng)?shù)亩褩V小?/p>
當(dāng)響應(yīng)異常時(shí),如果當(dāng)前的代碼正在使用 PSP,則壓入 PSP,也就是使用進(jìn)程堆棧;否則就壓入MSP,使用主堆棧。
一旦進(jìn)入了服務(wù)例程,就將一直使用主堆棧。
在進(jìn)入異常服務(wù)程序后,將自動(dòng)更新 LR(鏈接寄存器R14) 的值為特殊的 EXC_RETURN。這是一個(gè)高28位全為1的值,只有 [3:0] 的值有特殊含義,如下表所示。當(dāng)異常服務(wù)例程把這個(gè)值送往 PC 時(shí),就會(huì)啟動(dòng)處理器的中斷返回序列。因?yàn)長(zhǎng)R 的值是由 CM3 自動(dòng)設(shè)置的,所以只要沒有特殊需求,就不要改動(dòng)它。
總結(jié)一下,可以得出三個(gè)合法的 EXC_RETURN 值 :
如果主程序在線程模式下運(yùn)行,并且在使用MSP時(shí)被中斷,則在服務(wù)例程中 LR=0xFFFF_FFF9(主程序被打斷前的LR已被自動(dòng)入棧)。
如果主程序在線程模式下運(yùn)行,并且在使用 PSP 時(shí)被中斷,則在服務(wù)例程中 LR=0xFFFF_FFFD(主 程序被打斷前的LR已被自動(dòng)入棧)。
PendSV 中斷介紹
SVC(系統(tǒng)服務(wù)調(diào)用,亦簡(jiǎn)稱系統(tǒng)調(diào)用)和 PendSV(可懸起系統(tǒng)調(diào)用),它們多用于在操作系統(tǒng)之上的軟件開發(fā)中。 SVC 用于產(chǎn)生系統(tǒng)函數(shù)的調(diào)用請(qǐng)求。
SVC 異常通過執(zhí)行 ”SVC” 指令來產(chǎn)生。 該指令需要一個(gè)立即數(shù), 充當(dāng)系統(tǒng)調(diào)用代號(hào)。SVC異常服務(wù)例程稍后會(huì)提取出此代號(hào), 從而解釋本次調(diào)用的具體要求, 再調(diào)用相應(yīng)的服務(wù)函數(shù)。
例如,
SVC 0x3 ; 調(diào)用 3 號(hào)系統(tǒng)服務(wù)
在 SVC 服務(wù)例程執(zhí)行后,上次執(zhí)行的 SVC 指令地址可以根據(jù)自動(dòng)入棧的返回地址計(jì)算出。找到了 SVC 指令后, 就可以讀取該 SVC 指令的機(jī)器碼,從機(jī)器碼中萃取出立即數(shù),就獲知了請(qǐng)求執(zhí)行的功能代號(hào)。
如果用戶程序使用的是 PSP, 服務(wù)例程還需要先執(zhí)行 MRS Rn,PSP
指令來獲取應(yīng)用程序的堆棧指針 。通過分析 LR 的值,可以獲知在 SVC 指令執(zhí)行時(shí),正在使用哪個(gè)堆棧
PendSV(可懸起的系統(tǒng)調(diào)用)和 SVC 協(xié)同使用。
SVC 異常是必須立即得到響應(yīng)的(對(duì)于 SVC 異常來說,若因優(yōu)先級(jí)不比當(dāng)前正處理的高,或是其它原因使之無法立即響應(yīng),將造成成硬 fault ), 應(yīng)用程序執(zhí)行 SVC 時(shí)都是希望所需的請(qǐng)求立即得到響應(yīng)。
PendSV 則不同,它是可以像普通的中斷一樣被懸起的。 OS 可以利用它“緩期執(zhí)行” 一個(gè)異常——直到其它重要的任務(wù)完成后才執(zhí)行動(dòng)作。 懸起 PendSV 的方法是: 手動(dòng)往 NVIC 的 PendSV 懸起寄存器中寫 1。 懸起后, 如果優(yōu)先級(jí)不夠
高,則將緩期等待執(zhí)行。
PendSV 的典型使用場(chǎng)合是在上下文切換時(shí)(在不同任務(wù)之間切換)。 例如, 一個(gè)系統(tǒng)中 有兩個(gè)就緒的任務(wù),上下文切換被觸發(fā)的場(chǎng)合可以是:
PendSV 異常會(huì)自動(dòng)延遲上下文切換的請(qǐng)求,直到其它的 ISR 都完成了處理后才放行。為實(shí)現(xiàn)這個(gè)機(jī)制,需要把 PendSV 編程為最低優(yōu)先級(jí)的異常。
如果 OS 檢測(cè)到某 IRQ 正在活動(dòng)并且被 SysTick 搶占,它將懸起一個(gè) PendSV 異常,以便緩期執(zhí)行上下文切換。
- 任務(wù) A 呼叫 SVC 來請(qǐng)求任務(wù)切換(例如,等待某些工作完成)
- OS 接收到請(qǐng)求,做好上下文切換的準(zhǔn)備,并且 pend 一個(gè) PendSV 異常。
- 當(dāng) CPU 退出 SVC 后,它立即進(jìn)入 PendSV,從而執(zhí)行上下文切換。
- 當(dāng) PendSV 執(zhí)行完畢后,將返回到任務(wù) B,同時(shí)進(jìn)入線程模式。
- 發(fā)生了一個(gè)中斷,并且中斷服務(wù)程序開始執(zhí)行
- 在 ISR 執(zhí)行過程中,發(fā)生 SysTick 異常,并且搶占了該 ISR。
- OS 執(zhí)行必要的操作,然后 pend 起 PendSV 異常以作好上下文切換的準(zhǔn)備。
- 當(dāng) SysTick 退出后,回到先前被搶占的 ISR 中, ISR 繼續(xù)執(zhí)行
- ISR 執(zhí)行完畢并退出后, PendSV 服務(wù)例程開始執(zhí)行,并且在里面執(zhí)行上下文切換
- 當(dāng) PendSV 執(zhí)行完畢后,回到任務(wù) A,同時(shí)系統(tǒng)再次進(jìn)入線程模式
RTOS系統(tǒng)中雙堆棧操作
一個(gè)真正健壯的 CM3 軟件系統(tǒng)是要使用實(shí)時(shí)操作系統(tǒng)內(nèi)核的,通常會(huì)符合如下的要求:
- 服務(wù)例程使用 MSP
- 盡管異常服務(wù)例程使用 MSP,但是它們?cè)谛问缴戏祷睾螅瑑?nèi)容上卻可以依然繼續(xù)——而且此時(shí)還能使用 PSP,從而實(shí)現(xiàn)“可搶占的系統(tǒng)調(diào)用”,大幅提高實(shí)時(shí)性能
- 通過 SysTick,實(shí)時(shí)內(nèi)核的代碼每隔固定時(shí)間都被調(diào)用一次,運(yùn)行在特權(quán)級(jí)水平上,負(fù)責(zé)任務(wù)的調(diào)度、任務(wù)時(shí)間管理以及其它系統(tǒng)例行維護(hù)
- 用戶應(yīng)用程序以線程的形式運(yùn)行,使用 PSP,并且在用戶級(jí)下運(yùn)行
- 內(nèi)核在執(zhí)行關(guān)鍵部位的代碼時(shí),使用 MSP,并且在輔以 MPU 時(shí), MSP 對(duì)應(yīng)的堆棧只允許特權(quán)級(jí)訪問
在操作系統(tǒng)中,對(duì)于 EXC_RETURN 的修改,只是再尋常不過基本需求。在開始調(diào)度用戶程序后,一定還伴隨著 SysTick 異常,它周期性把執(zhí)行權(quán)轉(zhuǎn)入操作系統(tǒng),從而使例行的系統(tǒng)管理以及必要輪轉(zhuǎn)調(diào)度得以維持,任務(wù)切換過程如圖所示:
上圖為 SysTick 異常推動(dòng)時(shí)間片輪轉(zhuǎn)調(diào)度模式圖。在這里,使用 PendSV(一個(gè)優(yōu)先級(jí)最低的異常)來執(zhí)行上下文切換,從而消滅了在中斷服務(wù)例程中出現(xiàn)上下文切換的可能。
在 SysTick 中斷例程中執(zhí)行必要的操作,然后懸掛起 PendSV 異常以作好上下文切換的準(zhǔn)備。退出 SysTime 中斷處理函數(shù)后,PendSV 服務(wù)例程開始執(zhí)行,并且在里面執(zhí)行上下文切換。
在任務(wù)-1 和 任務(wù)2 程序執(zhí)行過程中使用的是 PSP(線程堆棧)。進(jìn)入中斷服務(wù)程序后(SysTime 和 PendSV)在內(nèi)部使用的是MSP(主堆棧)。
評(píng)論
查看更多