中斷管理
什么是中斷?簡單的解釋就是系統(tǒng)正在處理某一個(gè)正常事件,忽然被另一個(gè)需要馬上處理的緊急事件打斷,系統(tǒng)轉(zhuǎn)而處理這個(gè)緊急事件,待處理完畢,再恢復(fù)運(yùn)行剛才被打斷的事件。生活中,我們經(jīng)常會(huì)遇到這樣的場景:
當(dāng)你正在專心看書的時(shí)候,忽然來了一個(gè)電話,于是記下書的頁碼,去接電話,接完電話后接著剛才的頁碼繼續(xù)看書,這是一個(gè)典型的中斷的過程。
電話是老師打過來的,讓你趕快交作業(yè),你判斷交作業(yè)的優(yōu)先級(jí)比看書高,于是電話掛斷后先做作業(yè),等交完作業(yè)后再接著剛才的頁碼繼續(xù)看書,這是一個(gè)典型的在中斷中進(jìn)行任務(wù)調(diào)度的過程。
這些場景在嵌入式系統(tǒng)中也很常見,當(dāng)CPU正在處理內(nèi)部數(shù)據(jù)時(shí),外界發(fā)生了緊急情況,要求CPU暫停當(dāng)前的工作轉(zhuǎn)去處理這個(gè)異步事件。處理完畢后,再回到原來被中斷的地址,繼續(xù)原來的工作,這樣的過程稱為中斷。
實(shí)現(xiàn)這一功能的系統(tǒng)稱為中斷系統(tǒng),申請(qǐng)CPU中斷的請(qǐng)求源稱為中斷源。
中斷是一種異常,異常是導(dǎo)致處理器脫離正常運(yùn)行轉(zhuǎn)向指向特殊代碼的任何事件,如果不及時(shí)地進(jìn)行處理,輕則系統(tǒng)出錯(cuò),重則系統(tǒng)癱瘓。所以正確地處理異常,避免錯(cuò)誤的發(fā)生是提高軟件魯棒性的重要一環(huán)。
中斷處理與CPU架構(gòu)密切相關(guān),所以,本章會(huì)先介紹 ARM Cortex-M 的 CPU 架構(gòu),然后結(jié)合 Cortex-M CPU 架構(gòu)來介紹 RT-Thread 的中斷管理機(jī)制,讀完本章,大家將深入了解 RT-Thread 的中斷處理過程,如何添加中斷服務(wù)程序(ISR)以及相關(guān)的注意事項(xiàng)。
Cortex-M CPU架構(gòu)基礎(chǔ)
ARM Cortex-M 處理器有一個(gè)非常不同的架構(gòu),Cortex-M是一個(gè)家族系列,其中包括Cortex M0/M3/M4/M7多個(gè)不同型號(hào),每個(gè)型號(hào)之間會(huì)有些區(qū)別,比如說Cortex-M4 比 Cortex-M3 多了浮點(diǎn)計(jì)算功能等,但它們的編程模型基本是一致的。
寄存器簡介
Cortex-M系列CPU的寄存器組里有R0~R15共16個(gè)通用寄存器組和若干特殊功能寄存器,如下圖所示。
通用寄存器組里的R13作為堆棧指針寄存器(Stack Pointer,SP);R14作為連接寄存器(Link Register,LR),用于在調(diào)用子程序時(shí),存放返回地址;R15作為程序計(jì)數(shù)器(Program Counter,PC),其中堆棧指針寄存器可以是主堆棧指針(MSP),也可以是進(jìn)程堆棧指針(PSP)。
特殊功能寄存器包括程序狀態(tài)寄存器組(PSRs)、中斷屏蔽寄存器組(PRIMASK,F(xiàn)AULTMASK,BASEPRI)、控制寄存器(CONTROL),可以通過MSR/MRS指令訪問特殊功能寄存器。
MRS R0,CONTROL; 讀取CONTROL到R0中
MSR CONTROL,R0 寫入R0到CONTROL寄存器中
程序狀態(tài)字寄存器里保存算術(shù)與邏輯標(biāo)志,例如負(fù)數(shù)標(biāo)志,零結(jié)果標(biāo)志,溢出標(biāo)志等等。中斷屏蔽寄存器組控制Cortex-M的中斷除能。控制寄存器用來定義特權(quán)級(jí)別和當(dāng)前使用哪個(gè)堆棧指針。
操作模式和特權(quán)級(jí)別
Cortex-M引入了操作模式和特權(quán)級(jí)別的概念,分別為線程模式和處理模式,如果進(jìn)入異?;蛑袛嗵幚韯t進(jìn)入處理模式,其它情況則為線程模式。
Cortex-M有兩個(gè)運(yùn)行級(jí)別,分別為特權(quán)級(jí)和用戶級(jí),線程模式可以工作在特權(quán)級(jí)或者用戶級(jí),而處理模式總工作在特權(quán)級(jí),可通過CONTROL特殊寄存器控制。
Cortex-M的堆棧寄存器SP對(duì)應(yīng)兩個(gè)物理寄存器MSP和PSP,MSP為主堆棧,PSP為進(jìn)程堆棧,處理模式總是使用MSP作為堆棧,線程模式可以選擇使用MSP或PSP作為堆棧,同樣通過CONTROL特殊寄存器控制。
復(fù)位后,Cortex-M默認(rèn)進(jìn)入線程模式、特權(quán)級(jí)、使用MSP堆棧。
嵌套向量中斷控制器
Cortex-M中斷控制器名為NVIC(嵌套向量中斷控制器),支持中斷嵌套功能。
當(dāng)一個(gè)中斷觸發(fā)并且系統(tǒng)進(jìn)行響應(yīng)時(shí),處理器硬件會(huì)將當(dāng)前運(yùn)行位置的上下文寄存器自動(dòng)壓入中斷棧中,這部分的寄存器包括PSR、PC、LR、R12、R3~R0寄存器。
當(dāng)系統(tǒng)正在服務(wù)一個(gè)中斷時(shí),如果有一個(gè)更高優(yōu)先級(jí)的中斷觸發(fā),那么處理器同樣會(huì)打斷當(dāng)前運(yùn)行的中斷服務(wù)程序,然后把這個(gè)中斷服務(wù)程序的上下文寄存器自動(dòng)保存到中斷棧中。
PendSV系統(tǒng)調(diào)用
PendSV也稱為可懸起的系統(tǒng)調(diào)用,它是一種異常,可以像普通的中斷一樣被掛起,專門用于輔助操作系統(tǒng)進(jìn)行上下文切換。
PendSV異常會(huì)被初始化為最低優(yōu)先級(jí)的異常。
每次需要進(jìn)行上下文切換時(shí),會(huì)手動(dòng)觸發(fā)PendSV異常,在PendSV異常處理函數(shù)中進(jìn)行上下文切換。
中斷向量表
中斷向量表是所有中斷處理程序的入口。
把一個(gè)函數(shù)(用戶中斷服務(wù)程序)同一個(gè)虛擬中斷向量表的中斷向量聯(lián)系在一起。當(dāng)中斷向量對(duì)應(yīng)中斷發(fā)生的時(shí)候,被掛接的用戶中斷服務(wù)程序就會(huì)被調(diào)用執(zhí)行。
在Cortex-M內(nèi)核上,所有中斷都采用中斷向量表的方式進(jìn)行處理,即當(dāng)一個(gè)中斷觸發(fā)時(shí),處理器將直接判定是哪個(gè)中斷源,然后直接跳轉(zhuǎn)到相應(yīng)的固定位置進(jìn)行處理,每個(gè)中斷服務(wù)程序必須排列在一起放在統(tǒng)一的地址上(這個(gè)地址必須要射到NVIC的中斷向量偏移寄存器中)。中斷向量表一般由一個(gè)數(shù)組定義或在起始代碼中給出,默認(rèn)采用起始代碼給出:
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset 處理函數(shù)
DCD NMI_Handler ; NMI 處理函數(shù)
DCD HardFault_Handler ; Hard Fault 處理函數(shù)
DCD MemManage_Handler ; MPU Fault 處理函數(shù)
DCD BusFault_Handler ; Bus Fault 處理函數(shù)
DCD UsageFault_Handler ; Usage Fault 處理函數(shù)
DCD 0 ; 保留
DCD 0 ; 保留
DCD 0 ; 保留
DCD 0 ; 保留
DCD SVC_Handler ; SVCall 處理函數(shù)
DCD DebugMon_Handler ; Debug Monitor 處理函數(shù)
DCD 0 ; 保留
DCD PendSV_Handler ; PendSV 處理函數(shù)
DCD SysTick_Handler ; SysTick 處理函數(shù)
… …
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
… …
[WEAK]標(biāo)識(shí),是符號(hào)弱化標(biāo)識(shí),在[WEAK]前面的符號(hào)(如NMI_Handler、HardFault_Handler)將被執(zhí)行弱化處理,如果整個(gè)代碼在鏈接時(shí)遇到了名稱相同的符號(hào)(如與NMI_Handler相同名稱的函數(shù)),那么代碼將使用未被弱化定義的符號(hào)(與 NMI_Handler 相同名稱的函數(shù)),而與弱化符號(hào)相關(guān)的代碼將被自動(dòng)丟棄。
以SysTick中斷為例,在系統(tǒng)啟動(dòng)代碼上,需要填上SysTick_Handler中斷入口函數(shù),然后實(shí)現(xiàn)該函數(shù)即可對(duì) SysTick 中斷進(jìn)行響應(yīng),中斷處理函數(shù)示例程序如下所示:
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
rt_tick_increase();
/* leave interrupt */
rt_interrupt_leave();
}
中斷處理過程
RTT中斷管理中,將中斷處理程序分為中斷前導(dǎo)程序、用戶中斷服務(wù)程序、中斷后續(xù)程序三部分,如下圖:
中斷前導(dǎo)程序
- 保存CPU中斷現(xiàn)場,這部分跟CPU架構(gòu)相關(guān),不同CPU架構(gòu)的實(shí)現(xiàn)方式有差異。
對(duì)于Cortex-M來說,該工作由硬件自動(dòng)完成。當(dāng)一個(gè)中斷觸發(fā)并且系統(tǒng)進(jìn)行響應(yīng)時(shí),處理器硬件會(huì)將當(dāng)前運(yùn)行部分的上下文寄存器自動(dòng)壓入中斷棧中,這部分的寄存器包括PSR、PC、LR、R12、R3~R0寄存器。 - 通知內(nèi)核進(jìn)入中斷狀態(tài),調(diào)用rt_interrupt_enter()函數(shù),作用是把全局變量rt_interrupt_nest加1,用它來記錄中斷嵌套的層數(shù)。
void rt_interrupt_enter(void)
{
rt_base_t level;
RT_DEBUG_LOG(RT_DEBUG_IRQ, ("irq coming..., irq nest:%d\n",
rt_interrupt_nest));
level = rt_hw_interrupt_disable();
rt_interrupt_nest ++;
RT_OBJECT_HOOK_CALL(rt_interrupt_enter_hook,());
rt_hw_interrupt_enable(level);
}
RTM_EXPORT(rt_interrupt_enter);
用戶中斷服務(wù)程序
在用戶中斷服務(wù)程序(ISR)中,分為兩種情況,第一張情況是不進(jìn)行線程切換,這種情況下用戶中斷服務(wù)程序和中斷后續(xù)程序運(yùn)行完畢后退出中斷模式,返回被中斷的線程。
另一種情況是,在中斷處理過程中需要進(jìn)行線程切換,這種情況會(huì)調(diào)用**rt_hw_context_switch_interrupt()**函數(shù)進(jìn)行上下文切換,該函數(shù)跟CPU架構(gòu)相關(guān),不同CPU架構(gòu)的實(shí)現(xiàn)方式有差異。
在Cortex-M架構(gòu)中,rt_hw_context_switch_interrupt()的函數(shù)實(shí)現(xiàn)流程如圖。
它將設(shè)置需要切換的線程rt_interrupt_to_thread變量,然后觸發(fā)PendSV異常(PendSV異常是專門用來輔助上下文切換的,且被初始化為最低優(yōu)先級(jí)的異常)。
PendSV異常被觸發(fā)后,不會(huì)立即進(jìn)行PendSV異常中斷處理程序,因?yàn)榇藭r(shí)還在中斷處理中,只有當(dāng)中斷后續(xù)程序運(yùn)行完畢,真正退出中斷處理后,才進(jìn)入PendSV異常中斷處理程序。
中斷后續(xù)程序
- 通知內(nèi)核離開中斷狀態(tài),通過調(diào)用rt_interrupt_leave()函數(shù),將全局變量rt_interrupt_nest減1.
void rt_interrupt_leave(void)
{
rt_base_t level;
level = rt_hw_interrupt_disable();
rt_interrupt_nest --;
rt_hw_interrupt_enable(level);
}
- 恢復(fù)中斷前的CPU上下文,如果在中斷處理過程中未進(jìn)行線程切換,那么恢復(fù)from線程的CPU上下文,如果在中斷中進(jìn)行了線程切換,那么恢復(fù)to線程的CPU上下文。
中斷嵌套
在允許中斷嵌套的情況下,在執(zhí)行中斷服務(wù)程序的過程中,如果出現(xiàn)高優(yōu)先級(jí)的中斷,當(dāng)前中斷服務(wù)程序的執(zhí)行將被打斷,以執(zhí)行高優(yōu)先級(jí)中斷的中斷服務(wù)程序,當(dāng)高優(yōu)先級(jí)中斷的處理完成后,被打斷的中斷服務(wù)程序又繼續(xù)得到執(zhí)行,如果需要進(jìn)行線程調(diào)度,線程的上下文切換在所有中斷處理程序都運(yùn)行結(jié)束時(shí)才發(fā)生。
中斷棧
在系統(tǒng)響應(yīng)中斷前,處理器需要把當(dāng)前線程的上下文保存起來(通常保存在當(dāng)前線程的線程棧中),再調(diào)用中斷服務(wù)程序進(jìn)行中斷響應(yīng)、處理。
在中斷處理函數(shù)中很可能會(huì)有自己的局部變量,這些都需要相應(yīng)的棧空間來保存,所以中斷響應(yīng)仍然需要一個(gè)??臻g來作為上下文,運(yùn)行中斷處理函數(shù)。
中斷棧可以保存在被打斷線程的棧中,當(dāng)從中斷退出時(shí),返回相應(yīng)的線程繼續(xù)執(zhí)行。
中斷棧也可以與線程棧完全分離,每次進(jìn)入中斷時(shí),在保存完打斷線程上下文后,切換到新的中斷棧獨(dú)立運(yùn)行。
在中斷退出時(shí),再做相應(yīng)的上下文恢復(fù)。
使用獨(dú)立中斷棧相對(duì)來說更容易實(shí)現(xiàn),并且對(duì)于線程棧使用情況也比較容易了解和掌握(否則必須要為中斷預(yù)留空間,如果系統(tǒng)支持中斷嵌套,還需要考慮應(yīng)該為嵌套中斷預(yù)留多大的空間)。
RTT采用的方式是提供獨(dú)立的中斷棧,即中斷發(fā)生時(shí),中斷的前期處理程序會(huì)將用戶的棧指針更換到系統(tǒng)事先留出的中斷棧空間中,等中斷退出時(shí)再恢復(fù)用戶的棧指針。
**這樣中斷就不會(huì)占用線程的??臻g,**從而提高了內(nèi)存空間的利用率,且隨著線程的增加,這種減少內(nèi)存占用的效果也越明顯。
在Cortex-M處理器內(nèi)核里有兩個(gè)堆棧指針,一個(gè)是主堆棧指針(MSP),是默認(rèn)的堆棧指針,在運(yùn)行第一個(gè)線程之前和在中斷和異常服務(wù)程序里使用;另一個(gè)是線程堆棧指針(PSP),在線程里使用。文章來源:http://www.zghlxwxcb.cn/news/detail-690329.html
在中斷和異常服務(wù)程序退出時(shí),修改LR寄存器的第2位的值為1,線程的SP就由MSP切換到PSP。文章來源地址http://www.zghlxwxcb.cn/news/detail-690329.html
到了這里,關(guān)于RT-Thread 中斷管理學(xué)習(xí)(一)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!