只要正常的程序流被暫時中止,處理器就進入異常模式。例如響應一個來自外設的中斷。在處理異常之前,ARM內核保存當前的處理器狀態,這樣當處理程序結束是可以恢復執行原來的程序。
注意:如果同時發生兩個或更多異常,那么將按照固定的順序來處理異常 。
ARM支持的異常種類:
一、異常的進入與退出
當一種異常發生時,硬件就會自動執行如下動作:
(1)將CPSR保存到相應異常模式下的SPSR中
(2)把PC寄存器保存到相應異常模式下的LR中
(3)將CPSR設置成相應的異常模式
(4)設置PC寄存器的值為相應處理程序的入口地址
可以總結如下圖:
呵呵,在這里我們需要重點研究的是,異常產生后,后PC會指到哪里去呢?這個事情實際不需要我們操心,ARM核在設計的時候就已經確定好了,也就是經常我們所說的異常向量表。異常向量表:
在ARM7,ARM9/10等處理器,異常向量表可以存放在以 0x00000000或0xffff00000其始的地址。默認是以零地址開始存放的。可能有些同學還是有些暈,我們來舉個例子說明一下。
例如:ARM處理器正在執行指令,此時外部硬件產生了一個中斷。此時將產生IRQ異常,然后ARM核就會自動完成我們上面說的4步。完成前3步后,ARM核會強制將pc的值修改為0x18。修改完成后,處理器就開始從0x18這個地址取指令執行。
從上圖可以知道,不同的異常產生時,ARM核修改的PC值不一樣。例如:如果是swi指令引起的異常,ARM核后就會修改pc的值為0x08。
是不是異常向量表,一定要放在0x00000000或0xffff00000其始的地址呢?答案:不是,現在cortex-A系列的處理器可以將異常向量表放在任何位置,拿ARM核收到異常后,它怎么知道應該將pc的值修改為多少呢?這就需要我們通過協處理指令告訴它了。
例如:在cortex-A8上,我們可以操作如下協處理指令,來告訴ARM核異常向量表的位置。
cortex-A8官方手冊:3.2.68節有詳細說明
如將告訴ARM核,異常向量表存放在0x20008000
ldr r0,=0x20008000
mcr p15,0,r0,c12,c0,0
好了,到這里大家已經知道了異常是什么,當異常產生的時候,ARM核都會自動做那些事情。當然,當異常產生的時候,我們應該對異常做出處理,處理完之后,要返回異常產生之前的場景繼續運行。就像,有些時候,我們在做事情的時候,生病了,我們就需要到醫生那里去治療一下,等治療完成之后,就必須把生病前的事情接著后面干。
有些人,肯定忍不住了,我知道了異常,也知道異常產生后,pc會指向異常向量表,那異常向量表中,到底放什么東西呀,我該怎么處理我的異常呢?
首先,回答第一個問題,異常向量表中存放的就是去醫生的火箭。坐上火箭就可以到醫生哪了,這叫一個字"快"。醫生,火箭.....
呵呵,別折磨大家了,我們公布答案吧!看下面
從上面我們可以知道,異常向量表里面存放的都是跳轉指令。有些時直接通過b指令實現的,有些時通過修改pc值實現的。這也就是剛剛我說到的"火箭"。跳轉的目的是跳到一個地方對異常進行處理。也就是我說到的到醫生那里去"治療"。
我們以irq異常為例子,來說明我們需要干的事情
irq :
SUB LR,LR,#4 ;計算返回地址
STMFD SP!,{R0-R3,LR} ;保存使用到的寄存器
干里你想干的事情
.....
LDMFD SP!,{R0-R3,PC}^ ;中斷返回
注意:當異常結束時,異常處理程序必須:
1.將LR中的值減去偏移量后存入PC,偏移量根據異常的類型而有所不同;
2.將SPSR的值復制回CPSR;
3.清零中斷禁止標志。
注:恢復CPSR的動作會將T、F和I位自動恢復為異常發生前的值。
哎,終于說完了異常。下面我們用一句話總結一下:異常產生需要保存現場(ARM核已經自動為我們做了),異常返回的時候需要恢復現場(需要程序員自動完成)。
下面我們來詳細說明異常產生的原因,以及異常返回時,異常模式的lr應該保存的值是多少。后我們會以軟中斷實驗,來給大家強化對異常的理解。
重要基礎知識:R15(PC)總是指向“正在取指”的指令,而不是指向“正在執行”的指令或正在“譯碼”的指令。一般來說,人們習慣性約定將“正在執行的指令作為參考點”,稱之為當前第一條指令,因此PC總是指向第三條指令。當ARM狀態時,每條指令為4字節長,所以PC始終指向該指令地址加8字節的地址,即:PC值=當前程序執行位置+8;
注意:不管是幾級流水線都統一按照三級流水線來分析,這一點已經向ARM官方求證過。
(1)快速中斷異常
快速中斷請求(FIQ)適用于對一個突發事件的響應,這得益于在ARM狀態中,快速中斷模式有8個專用的寄存器可用來滿足寄存器保護的需要(這可以加速上下文切換的速度)。
不管異常入口是來自ARM狀態還是Thumb狀態,FIQ處理程序都會通過下面的指令從中斷返回:
SUBS pc,R14_fiq,#4
在一個特權模式中,可以通過置位CPSR中的F位來禁止FIQ異常。
(2)中斷請求異常
中斷請求(IRQ)異常是一個由nIRQ輸入端的低電平所產生的正常中斷(在具體的芯片中,nIRQ由片內外設拉低,nIRQ是內核的一個信號,對用戶不可見)。IRQ的優先級低于FIQ。對于FIQ序列它是被屏蔽的。任何時候在一個特權模式下,都可通過置位CPSR中的I 位來禁止IRQ。
不管異常入口是來自ARM狀態還是Thumb狀態,FIQ處理程序都會通過執行下面的指令從中斷返回:
SUBS PC,R14_fiq,#4
分析IRQ 和 FIQ異常中斷處理的返回:
指令地址 對應于PC
A PC-8 執行此指令完成后(!)查詢IRQ及FIQ,如果有中斷請求則產生中斷.
A+4 PC-4
A+8 PC ;lr!
(此時PC的值已經更新,指向A+12.將當前PC-4(即A+8)
保存到LR.返回時,要接著執行A+4(LR-4)處的指令,所以返回指令為
SUBS PC, LR,#4(PC=A+4=LR-4)
白話解釋:對于普中斷和快中斷異常:
中斷必須在一條指令執行完以后被檢測到,如正在執行指令甲時發生了中斷,不等指令甲執行完是不
會處理該中斷的,發生異常時pc已經更新(A+12);
lr = pc – 4(這時處理器決定的,無法更改!)即A+8
返回后,應執行被中斷而沒有執行的指令(上面的A+4),所以返回時,pc = lr-4
(3)中止
中止發生在對存儲器的訪問不能完成時,中止包含兩種類型:
預取中止 : 發生在指令預取過程中
數據中止 : 發生在對數據訪問時
A.預取中止
當發生預取中止時,ARM核將預取的指令標記為無效,但在指令達到流水線的執行階段時才進入異常。如果指令在流水線中因為發生分支而沒有被執行,中止將不會發生。
在處理中止的原因之后,不管處于哪種處理器操作狀態,處理程序都會執行下面的指令恢復PC和CPSR并重試被中止的指令:
SUBS PC,R14_abt,#4
分析指令預取中止異常中斷處理的返回:
指令地址
A PC-8 執行本指令時發生異常,
A+4 PC-4 處理器將A+4(PC-4)保存到LR. ;lr!
A+8 PC
返回時,發生指令預取中止的指令A(PC-8)處重新執行,所以返回指令為
SUBS PC, LR,#4(PC=A=LR-4)
白話解釋:對于預取指令中止異常:
發生預取指令異常時,是在執行時發生的異常,pc未更新,即pc = A+8
lr = pc – 4(這時處理器決定的,無法更改!)即A+4
由于這類異常返回后應重新執行異常的那個指令(A),所以返回時,pc = lr-4
B.數據中止
指令地址
A PC-8 本指令訪問有問題的數據,產生中斷時,PC的值已經更新
A+4 PC-4 中斷發生時PC=A+12,處理器將A+8(PC-4)保存到LR.
A+8 PC ;lr!
返回時,要返回到A處繼續執行,所以指令為SUBS PC, LR,#8.(PC=A=LR-8)
白話解釋:對于數據訪問中止異常:
發生數據訪問中止異常時,是在執行時訪問數據錯誤導致的異常,pc已經更新,即pc = A+12
lr = pc – 4(這時處理器決定的,無法更改!)即A+8
由于這類異常返回后應重新執行異常的那個指令(A),所以返回時,pc = lr-8
(4)軟件中斷指令
所有的任務都是運行在用戶模式下的,因此任務只能讀CPSR而不能寫SPSR。任務切換到特權模式下唯一的途徑就是使用一個SWI指令調用,SWI指令強迫處理器從用戶模式切換到SVC管理模式,并且IRQ自動關閉,所以軟件中斷方式常被用于系統調用。
(5)未定義指令異常
(1)當ARM在對一條未定義指令進行譯碼時,發現這是一條自己和系統內任何協處理器都無法執行的指令時,就會發生未定義指令異常;
(2)由于是在對未定義指令譯碼時發生異常,所以PC的值等于未定義指令的地址+4(即剛好為中斷返回地址),因此R14保存的值是 中斷返回地址 ,所以當異常要返回時可執行以下指令:
MOVS PC,R14_und
分析:SWI和和未定義指令異常中斷的返回:
指令地址
A PC-8 當前指令為SWI或未定義指令 此時發生異常PC的值還沒有更新.
A+4 PC-4 中斷時處理器將PC-4保存到LR ;lr!
A+8 PC
返回時,從發生中斷的指令A(PC-8)的下一條指令A+4(PC-4)處開始執行,所以直接
把LR的值賦給PC就行了,具體指令為MOV PC,LR (PC=A+4=LR)
白話解釋:對于SWI和未定義指令異常:
發生異常時pc沒有更新,根據ARM的三級流水線原理,pc沒有更新,仍然等于(A+8);
lr = pc – 4(這時處理器決定的,無法更改!)即A+4
由于這類異常返回后應執行下一條指令(A+4),所以返回時,pc = lr即可
后我們來總結一下:
引起PC更新的原因一種是數據中止,還有就是中斷了.
中斷必須是在一條指令執行完畢后才能被檢測到,所以它中斷的只是還未執行的那條指令(pc - 8),
所以pc = lr – 4;
與中斷相同,SWI和未定義指令異常也是返回到下一條指令(pc - 4),只是他們在執行時,PC的值并沒
有更新,所以pc = lr;
預取指令中止異常,也沒有發生pc更新,但它還得重新執行發生異常的那條指令,所以pc = lr – 4;
數據訪問中止異常,發生了pc更新,并且它也需要重新執行發生異常的那條指令,所以pc = lr – 8;
當多個異常同時發生時,一個固定的優先級系統決定它們被處理的順序:
二、SWI 實驗
(1)swi 指令
SWI指令用于產生軟中斷,從而實現在從戶模式變換到管理模式,并且將CPSR保存到管理模式的SPSR中,然后程序跳轉到SWI異常入口。在其它模式下也可使用SWI指令,處理器同樣地切換到管理模式。
該指令主要用于用戶程序調用操作系統的系統服務,操作系統在SWI異常處理程序中進行相應的系統服務。
注意:
ARM 內核不提供直接傳遞軟中斷(SWI)號到處理程序的機制:
SWI 處理程序必須定位SWI 指令,并提取SWI指令中的常數域
為此, SWI 處理程序必須確定SWI 調用是在哪一種狀態(ARM/Thumb).
檢查 SPSR 的 T-bit
SWI 指令在ARM 狀態下在 LR-4 位置, Thumb 狀態下在 LR-2位置
SWI 指令按相應的格式譯碼:
例如:
SWI 13
思考,當軟中斷產生后,怎么獲取它的軟中斷號呢?
實驗的核心代碼如下:
software_interrupt:
@設置軟中斷所在模式的棧
ldr sp,=0x34000
@保存用到的寄存器
stmfd sp!,{r0-r2,lr}
@獲取swi指令對應的機器碼
ldr r0,[lr,#-4]
@獲取軟中斷號
bic r0,r0,#0xff000000
@調用C處理函數,求累加和
bl calc_sum
@獲得函數返回值,存放在r1
mov r1,r0
@恢復現場,^表示目標寄存器是pc時,傳遞數據給pc,同時更新CPSR
ldmfd sp!,{r0-r2,pc}^