Cortex-M3-中断/异常的响应序列
生活随笔
收集整理的這篇文章主要介紹了
Cortex-M3-中断/异常的响应序列
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Cortex-M3-中斷/異常的響應序列
當CM3開始響應一個中斷時,會在它看不見的體內奔涌起三股暗流:
- 入棧:?把8個寄存器的值壓入棧.
- 取向量:從向量表中找出對應的服務程序入口地址.
- 選擇堆棧指針MSP/PSP,更新堆棧指針SP,更新連接寄存器LR,更新程序計數器PC.
入棧
- 響應異常的第一個行動,就是自動保存現場的必要部分:依次把xPSR,?PC,?LR,?R12以及R3‐R0由硬件自動壓入適當的堆棧中:如果當響應異常時,當前的代碼正在使用PSP,則壓入PSP,即使用線程堆棧;否則壓入MSP,使用主堆棧。一旦進入了服務例程,就將一直使用主堆棧。
- 假設入棧開始時,SP的值為N,則在入棧后,堆棧內部的變化如表9.1表示。又因為AHB接口上的流水線操作本質,地址和數據都在經過一個流水線周期之后才進入。另外,這種入棧在機器的內部,并不是嚴格按堆棧操作的順序的——但是機器會保證:正確的寄存器將被保存到正確的位置. 如下圖和表的第三列所示.
- CM3在看不見的內部打亂了入棧的順序,這是有深層次的原因的。先把PC與xPSR的值保存,就可以更早地啟動服務例程指令的預取——因為這需要修改PC;同時,也做到了在早期就可以更新xPSR中IPSR位段的值。
- 細心的讀者一定在猜測:為啥袒護R0‐R3以及R12呢,R4‐R11就是下等公民?原來,在ARM上,有一套的C函數調用標準約定(《C/C++?Procedure?Call?Standard?for?the?ARM?Architecture》,AAPCS,?Ref5)。個中原因就在它上面:它使得中斷服務例程能用C語言編寫,編譯器優先使用被入棧的寄存器來保存中間結果(當然,如果程序過大也可能要用到R4‐R11,此時編譯器負責生成代碼來push它們。但是,ISR應該短小精悍,不要讓系統如此操心——譯者注)。
- 如果讀者再仔細看,會發現R0‐R3,?R12是最后被壓進去的。這里也有一番良苦用心:為的是可以更容易地使用SP基址來索引尋址,(以及為了LDM等多重加載指令,因為LDM必須加載地址連續的一串數據)。參數的傳遞也是受益者:使之可以方便地通過壓入棧的R0‐R3取出(主要為系統軟件所利用,多見于SVC與PendSV中的參數傳遞)。
取向量
- 當數據總線(系統總線)正在為入棧操作而忙得團團轉時,指令總線(I‐Code總線)可不是涼快地坐著看熱鬧——它正在為響應中斷緊張有序地執行另一項重要的任務:從向量表中找出正確的異常向量,然后在服務程序的入口處預取指。由此可以看到各自都有專用總線的好處:入棧與取指這兩個工作能同時進行。
更新寄存器
- 在入棧和取向量的工作都完畢之后,執行服務例程之前,還要更新一系列的寄存器:
- SP:在入棧中會把堆棧指針(PSP或MSP)更新到新的位置。在執行服務例程后,將由MSP負責對堆棧的訪問。
- PSR:IPSR位段(地處PSR的最低部分)會被更新為新響應的異常編號。
- PC:在向量取出完畢后,PC將指向服務例程的入口地址。
- LR:LR的用法將被重新解釋,其值也被更新成一種特殊的值,稱為“EXC_RETURN”,并且在異常返回時使用。EXC_RETURN的二進制值除了最低4位外全為1,而其最低4位則有另外的含義。(圖5 會有解釋EXC_RETURN)
- 以上是在響應異常時通用寄存器的變化。另一方面,在NVIC中,也伴隨著更新了與之相關的若干寄存器。例如,新響應異常的懸起位將被清除,同時其活動位將被置位。
異常返回
- 當異常服務例程執行完畢后,需要很正式地做一個“異常返回”動作序列,從而恢復先前的系統狀態,才能使被中斷的程序得以繼續執行。從形式上看,有3種途徑可以觸發異常返回序列,如下表所示;不管使用哪一種,都需要用到先前儲的LR的值。
?
- 有些處理器使用特殊的返回指令來標示中斷返回,例如8051就使用reti。但是在CM3中,是通過把EXC_RETURN往PC里寫來識別返回動作的。因此,可以使用上述的常規返回指令,從而為使用C語言編寫服務例程掃清了最后的障礙(無需特殊的編譯器命令,如__interrupt)。
在啟動了中斷返回序列后,下述的處理就將進行:
- 出棧:先前壓入棧中的寄存器在這里恢復。內部的出棧順序與入棧時的相對應,堆棧指針的值也改回去。
- 更新NVIC寄存器:伴隨著異常的返回,它的活動位也被硬件清除。對于外部中斷,倘若中斷輸入再次被置為有效,懸起位也將再次置位,新一次的中斷響應序列也可隨之再次開始。
嵌套的中斷
- 在CM3內核以及NVIC的深處,就已經內建了對中斷嵌套的全力支持,根本無需使用用匯編寫封皮代碼(wrapper?code)。事實上,我們要做的就只是為每個中斷適當地建立優先級,不用再操心別的。表現在:
- 第一、?NVIC和CM3處理器會為我們排出優先級解碼的順序。因此,在某個異常正在響應時,所有優先級不高于它的異常都不能搶占之,而且它自己也不能搶占自己。
- 第二、?有了自動入棧和出棧,就不用擔心在中斷發生嵌套時,會使寄存器的數據損毀,從而可以放心地執行服務例程。
- 然而,有一件事情卻必須更加一絲不茍地處理了,否則有功能紊亂甚至死機的危險,這就是計算主堆棧容量的最小安全值。我們已經知道,所有服務例程都只使用主堆棧。所以當中斷嵌套加深時,對主堆棧的壓力會增大:每嵌套一級,就至少再需要8個字,即32字節的堆棧空間——而且這還沒算上ISR對堆棧的額外需求,并且何時嵌套多少級也是不可預料的。如果主堆棧的容量本來就已經所剩無幾了,中斷嵌套又突然加深,則主堆棧有被用穿的兇險。這就好像已經表現出了高血壓危象的時候,情緒又一激動,就容易導致中風一般。在這里,堆棧溢出同樣是很致命的,它會使入棧數據與主堆棧前面的數據區發生混迭,使這些數據被破壞;若服務例程又更改了混迭區的數據,則堆棧內容被破壞。這么一來在執行中斷返回后,系統極可能功能紊亂,甚至當場被一擊必殺——程序跑飛/死機!
- 另一個要注意的,是相同的異常是不允許重入的。因為每個異常都有自己的優先級,并且在異常處理期間,同級或低優先級的異常是要阻塞的,因此對于同一個異常,只有在上次實例的服務例程執行完畢后,方可繼續響應新的請求。由此可知,在SVC服務例程中,就不得再使用SVC指令,否則將fault伺候。
咬尾中斷
- CM3為縮短中斷延遲做了很多努力,第一個要提的,就是新增的“咬尾中斷”(Tail‐Chaining)機制。當處理器在響應某異常時,如果又發生其它異常,但它們優先級不夠高,則被阻塞——這個我們已經知道。那么在當前的異常執行返回后,系統處理懸起的異常時,倘若還是先POP然后又把POP出來的內容PUSH回去,這不成了砸鍋煉鐵再鑄鍋,白白浪費CPU時間嗎,可知還有多少緊急的事件懸而未決呀!正因此,CM3不會傻乎乎地POP這些寄存器,而是繼續使用上一個異常已經PUSH好的成果,消滅了這種鋪張浪費。這么一來,看上去好像后一個異常把前一個的尾巴咬掉了,前前后后只執行了一次入棧/出棧操作。于是,這兩個異常之間的“時間溝”變窄了很多。如下兩圖:
晚到(的高優先級)異常
- CM3的中斷處理還有另一個機制,它強調了優先級的作用,這就是“晚到的異常處理”。當CM3對某異常的響應序列還處在早期:入棧的階段,尚未執行其服務例程時,如果此時收到了高優先級異常的請求,則本次入棧就成了為高優先級中斷所做的了——入棧后,將執行高優先級異常的服務例程。可見,它雖然來晚了,卻還是因優先級高而受到偏袒,低優先級的異常為它“火中取栗”。
- 比如,若在響應某低優先級異常#1的早期,檢測到了高優先級異常#2,則只要#2沒有太晚,就能以“晚到中斷”的方式處理——在入棧完畢后執行ISR?#2,如圖4所示。如果異常#2來得太晚,以至于已經執行了ISR?#1的指令,則按普通的搶占處理,這會需要更多的處理器時間和額外32字節的堆棧空間。在ISR?#2執行完畢后,則以剛剛講過的“咬尾中斷”方式,來啟動ISR?#1的執行。
異常返回值(EXC_RETURN)
- 前面已經講到,在進入異常服務程序后,LR的值被自動更新為特殊的EXC_RETURN,這是一個高28位全為1的值,只有[3:0]的值有特殊含義,如圖5所示。當異常服務例程把這個值送往PC時,就會啟動處理器的中斷返回序列。因為LR的值是由CM3自動設置的,所以只要沒有特殊需求,就不要改動它。
可以得出,合法的EXC_RETURN值共3個:
0xFFFF_FFF1? //返回handler模式 0xFFFF_FFF9? //返回線程模式,并使用主堆棧(SP=MSP) 0xFFFF_FFFD? //返回線程模式,并使用線程堆棧(SP=PSP)- 如果主程序在線程模式下運行,并且在使用MSP時被中斷,則在服務例程中LR=0xFFFF_FFF9(主程序被打斷前的LR已被自動入棧)。
- 如果主程序在線程模式下運行,并且在使用PSP時被中斷,則在服務例程中LR=0xFFFF_FFFD(主程序被打斷前的LR已被自動入棧)。
總結
以上是生活随笔為你收集整理的Cortex-M3-中断/异常的响应序列的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GCC 和 MDK (即 Keil) 手
- 下一篇: Cortex-M3-建立堆栈