?
3.4? 異常中斷處理
異?;蛑袛嗍怯脩舫绦蛑凶罨镜囊环N執(zhí)行流程和形態(tài)。這部分主要對ARM架構下的異常中斷做詳細說明。
ARM有7種類型的異常,按優(yōu)先級從高到低的排列如下:復位異常(Reset)、數據異常(Data Abort)、快速中斷異常(FIQ)、外部中斷異常(IRQ)、預取異常(Prefetch Abort)、軟件中斷(SWI)和未定義指令異常(Undefined instruction)。
注意 |
在ARM文檔中,使用術語Exception來描述異常。Exception主要是從處理器被動接受異常的角度出發(fā),而Interrupt帶有向處理器主動申請的色彩。在本書中,對“異?!焙汀爸袛唷辈蛔鰢栏駞^(qū)分,兩者都是指請求處理器打斷正常的程序執(zhí)行流程,進入特定程序循環(huán)的一種機制。 |
3.4.1? 異常種類
ARM體系結構中,存在7種異常處理。當異常發(fā)生時,處理器會把PC設置為一個特定的存儲器地址。這一地址放在被稱為向量表(vector table)的特定地址范圍內。向量表的入口是一些跳轉指令,跳轉到專門處理某個異常或中斷的子程序。
存儲器映射地址0x00000000是為向量表(一組32位字)保留的。在有些處理器中,向量表可以選擇定位在存儲空間的高地址(從偏移量0xffff0000開始)。一些嵌入式操作系統(tǒng),如Linux和Windows CE就要利用這一特性。
表3.4列出了ARM的7種異常。
表3.4? ARM的7種異常
異 常 類 型 |
處理器模式 |
執(zhí)行低地址 |
執(zhí)行高地址 |
復位異常(Reset) |
特權模式 |
0x00000000 |
0xFFFF0000 |
未定義指令異常(Undefined interrupt) |
未定義指令中止模式 |
0x00000004 |
0xFFFF0004 |
軟中斷異常(Software Abort) |
特權模式 |
0x00000008 |
0xFFFF0008 |
預取異常(Prefetch Abort) |
數據訪問中止模式 |
0x0000000C |
0xFFFF000C |
數據異常(Data Abort) |
數據訪問中止模式 |
0x00000010 |
0xFFFF0010 |
外部中斷請求IRQ |
外部中斷請求模式 |
0x00000018 |
0xFFFF0018 |
快速中斷請求FIQ |
快速中斷請求模式 |
0x0000001C |
0xFFFF001C |
異常處理向量表如圖3.5所示。
當異常發(fā)生時,分組寄存器r14和SPSR用于保存處理器狀態(tài),操作偽指令如下。
R14_<exception_mode> = return link
SPSR_<exception_mode> = CPSR
CPSR[4∶0] = exception mode number
CPSR[5] = 0?? /*進入ARM狀態(tài)*/
If <exception_mode> = = reset or FIQ then
??? CPSR[6] = 1????????????????? /*屏蔽快速中斷FIQ*/
??? CPSR[7] = 1????????????????? /*屏蔽外部中斷IRQ*/
??? PC = exception vector address
圖3.5? 異常處理向量表
異常返回時,SPSR內容恢復到CPSR,連接寄存器r14的內容恢復到程序計數器PC。
1.復位異常
當處理器的復位引腳有效時,系統(tǒng)產生復位異常中斷,程序跳轉到復位異常中斷處理程序處執(zhí)行。復位異常中斷通常用在下面兩種情況下。
·? 系統(tǒng)上電。
·? 系統(tǒng)復位。
當復位異常時,系統(tǒng)執(zhí)行下列偽操作。
R14_svc = UNPREDICTABLE value
SPSR_svc = UNPREDICTABLE value
CPSR[4∶0] = 0b10011????????? /*進入特權模式*/
CPSR[5] = 0????????????????? /*處理器進入ARM狀態(tài)*/
CPSR[6] = 1????????????????? /*禁止快速中斷*/
CPSR[7] = 1????????????????? /*禁止外設中斷*/
If high vectors configured then
??? PC = 0xffff0000
Else
??? PC = 0x00000000
?
復位異常中斷處理程序將進行一些初始化工作,內容與具體系統(tǒng)相關。下面是復位異常中斷處理程序的主要功能。
·? 設置異常中斷向量表。
·? 初始化數據棧和寄存器。
·? 初始化存儲系統(tǒng),如系統(tǒng)中的MMU等。
·? 初始化關鍵的I/O設備。
·? 使能中斷。
·? 處理器切換到合適的模式。
·? 初始化C變量,跳轉到應用程序執(zhí)行。
2.未定義指令異常
當ARM處理器執(zhí)行協(xié)處理器指令時,它必須等待一個外部協(xié)處理器應答后,才能真正執(zhí)行這條指令。若協(xié)處理器沒有相應,則發(fā)生未定義指令異常。
未定義指令異??捎糜谠跊]有物理協(xié)處理器的系統(tǒng)上,對協(xié)處理器進行軟件仿真,或通過軟件仿真實現指令集擴展。例如,在一個不包含浮點運算的系統(tǒng)中,CPU遇到浮點運算指令時,將發(fā)生未定義指令異常中斷,在該未定義指令異常中斷的處理程序中可以通過其他指令序列仿真浮點運算指令。
仿真功能可以通過下面步驟實現。
① 將仿真程序入口地址鏈接到向量表中未定義指令異常中斷入口處(0x00000004或0xffff0004),并保存原來的中斷處理程序。
② 讀取該未定義指令的bits[27∶24],判斷其是否是一條協(xié)處理器指令。如果bits[27∶24]值為0b1110或0b110x,該指令是一條協(xié)處理器指令;否則,由軟件仿真實現協(xié)處理器功能,可以同過bits[11∶8]來判斷要仿真的協(xié)處理器功能(類似于SWI異常實現機制)。
③ 如果不仿真該未定義指令,程序跳轉到原來的未定義指令異常中斷的中斷處理程序執(zhí)行。
當未定義異常發(fā)生時,系統(tǒng)執(zhí)行下列的偽操作。
r14_und = address of next instruction after the undefined instruction
SPSR_und = CPSR
CPSR[4∶0] = 0b11011?????????????? /*進入未定義指令模式*/
CPSR[5] = 0????????????????????? /*處理器進入ARM狀態(tài)*/
/*CPSR[6]保持不變*/
CPSR[7] = 1????????????????????? /*禁止外設中斷*/
If? high vectors configured then
????? PC = 0xffff0004
Else
????? PC = 0x00000004
3.軟中斷SWI
軟中斷異常發(fā)生時,處理器進入特權模式,執(zhí)行一些特權模式下的操作系統(tǒng)功能。軟中斷異常發(fā)生時,處理器執(zhí)行下列偽操作。
r14_svc = address of next instruction after the SWI instruction
SPSR_und = CPSR
CPSR[4∶0] = 0b10011????????????? /*進入特權模式*/
CPSR[5] = 0???????????????????? /*處理器進入ARM狀態(tài)*/
/*CPSR[6]保持不變*/
CPSR[7] = 1???????????????????? /*禁止外設中斷*/
If? high vectors configured then
????? PC = 0xffff0008
Else
????? PC = 0x00000008
4.預取指令異常
預取指令異常使由系統(tǒng)存儲器報告的。當處理器試圖去取一條被標記為預取無效的指令時,發(fā)生預取異常。
如果系統(tǒng)中不包含MMU時,指令預取異常中斷處理程序只是簡單地報告錯誤并退出。若包含MMU,引起異常的指令的物理地址被存儲到內存中。
預取異常發(fā)生時,處理器執(zhí)行下列偽操作。
r14_svc = address of the aborted instruction + 4
SPSR_und = CPSR
CPSR[4∶0] = 0b10111?????????????? /*進入特權模式*/
CPSR[5] = 0????????????????????? /*處理器進入ARM狀態(tài)*/
/*CPSR[6]保持不變*/
CPSR[7] = 1????????????????????? /*禁止外設中斷*/
If? high vectors configured then
???? PC = 0xffff000C
Else
???? PC = 0x0000000C
5.數據訪問中止異常
數據訪問中止異常是由存儲器發(fā)出數據中止信號,它由存儲器訪問指令Load/Store產生。當數據訪問指令的目標地址不存在或者該地址不允許當前指令訪問時,處理器產生數據訪問中止異常。
當數據訪問中止異常發(fā)生時,處理器執(zhí)行下列偽操作。
r14_abt = address of the aborted instruction + 8
SPSR_abt = CPSR
CPSR[4∶0] = 0b10111
CPSR[5] = 0
/*CPSR[6]保持不變*/
CPSR[7] = 1????????????? /*禁止外設中斷*/
If? high vectors configured then
???? PC = 0xffff000C10
Else
???? PC = 0x00000010
?
當數據訪問中止異常發(fā)生時,寄存器的值將根據以下規(guī)則進行修改。
① 返回地址寄存器r14的值只與發(fā)生數據異常的指令地址有關,與PC值無關。
② 如果指令中沒有指定基址寄存器回寫,則基址寄存器的值不變。
③ 如果指令中指定了基址寄存器回寫,則寄存器的值和具體芯片的Abort Models有關,由芯片的生產商指定。
④ 如果指令只加載一個通用寄存器的值,則通用寄存器的值不變。
⑤ 如果是批量加載指令,則寄存器中的值是不可預知的值。
⑥ 如果指令加載協(xié)處理器寄存器的值,則被加載寄存器的值不可預知。
6.外部中斷IRQ
當處理器的外部中斷請求引腳有效,而且CPSR寄存器的I控制位被清除時,處理器產生外部中斷IRQ異常。系統(tǒng)中各外部設備通常通過該異常中斷請求處理器服務。
當外部中斷IRQ發(fā)生時,處理器執(zhí)行下列偽操作。
r14_irq = address of next instruction to be executed + 4
SPSR_irq = CPSR
CPSR[4∶0] = 0b10010????????????? /*進入特權模式*/
CPSR[5] = 0???????????????????? /*處理器進入ARM狀態(tài)*/
/*CPSR[6]保持不變*/
CPSR[7] = 1???????????????????? /*禁止外設中斷*/
If? high vectors configured then
???? PC = 0xffff0018
Else
???? PC = 0x00000018
7.快速中斷FIQ
當處理器的快速中斷請求引腳有效且CPSR寄存器的F控制位被清除時,處理器產生快速中斷請求FIQ異常。
當快速中斷異常發(fā)生時,處理器執(zhí)行下列偽操作。
r14_fiq = address of next instruction to be executed + 4
SPSR_fiq = CPSR
CPSR[4∶0] = 0b10001????????? /*進入FIQ模式*/
CPSR[5] = 0
CPSR[6] = 1
CPSR[7] = 1
If? high vectors configured then
???? PC= 0xffff001c
Else
???? PC = 0x0000001c
3.4.2? 異常優(yōu)先級
每一種異常按表3.5中設置的優(yōu)先級得到處理。
表3.5???? 異常優(yōu)先級
優(yōu)? 先? 級 |
異??? 常 |
最高????????? 1 |
復位異常 |
????????????? 2 |
數據中止 |
????????????? 3 |
快速中斷請求 |
????????????? 4 |
中斷請求 |
????????????? 5 |
預取指令異常 |
????????????? 6 |
軟件中斷 |
最低????????? 7 |
未定義指令 |
異常可以同時發(fā)生,處理器按表3.5的優(yōu)先級順序處理異常。例如,復位異常的優(yōu)先級最高,處理器上電時發(fā)生復位異常。所以當產生復位時,它將優(yōu)先于其他異常得到處理。同樣,當一個數據訪問中止異常發(fā)生時,它將優(yōu)先于除復位異常外的其他所有異常。
優(yōu)先級最低的2種異常是軟件中斷和未定義指令異常。因為正在執(zhí)行的指令不可能既是一條SWI指令,又是一條未定義指令,所以軟件中斷異常SWI和未定義指令異享有相同的優(yōu)先級。
3.4.3? 處理器模式和異常
每一種異常都會導致內核進入一種特定的模式。表3.6顯示了ARM處理器異常及其對應的模式。此外,也可以通過編程改變CPSR,進入任何一種ARM處理器模式。
注意 |
用戶和系統(tǒng)模式是僅有的不可通過異常進入的兩種模式,也就是說,要進入這兩種模式,必須通過編程改變CPSR。 |
表3.6? ARM處理器異常及其對應模式
異??? 常 |
模??? 式 |
用??? 途 |
快速中斷請求 |
FIQ |
進行快速中斷請求處理 |
外部中斷請求 |
IRQ |
進行外部中斷請求處理 |
SWI |
SVC |
進行操作系統(tǒng)的高級處理 |
復位 |
SVC |
進行操作系統(tǒng)的高級處理 |
預取指令中止異常 |
ABORT |
虛存和存儲器保護 |
數據中止異常 |
ABORT |
虛存和存儲器保護 |
未定義指令 |
Undefined |
軟件模擬硬件協(xié)處理器 |
?
3.4.4? 異常響應流程
1.判斷處理器狀態(tài)
當異常發(fā)生時,處理器自動切換到ARM狀態(tài),所以在異常處理函數中要判斷在異常發(fā)生前處理器是ARM狀態(tài)還是Thumb狀態(tài)。這可以通過檢測SPSR的T位來判斷。
通常情況下,只有在SWI處理函數中才需要知道異常發(fā)生前處理器的狀態(tài)。所以在Thumb狀態(tài)下,調用SWI軟中斷異常必須注意以下兩點。
① 發(fā)生異常的指令地址為(lr-2)而不是(lr-4)。
② Thumb狀態(tài)下的指令是16位的,在判斷中斷向量號時使用半字加載指令LDRH。
下面的例子顯示了一個標準的SWI處理函數,在函數中通過SPSR的T位判斷異常發(fā)生前的處理器狀態(tài)。
T_bit EQU 0x20??????????????? ; bit 5. SPSR中的ARM/Thumb狀態(tài)位,
:
:
SWIHandler
STMFD sp!, {r0-r3,r12,lr}????? ; 寄存器壓棧,保護程序現場
MRS r0, spsr???????????????? ; 讀SPSR寄存器,判斷異常發(fā)生前的處理器狀態(tài)
TST r0, #T_bit??????????????? ; 檢測SPSR的T位,判斷異常發(fā)生前是否為Thumb狀態(tài)
LDRNEH r0,[lr,#-2]??????????? ; 如果是Thumb狀態(tài),使用半字加載指令讀取發(fā)生異常的指令地址
BICNE r0,r0,#0xFF00?????????? ; .提取中斷向量號.
LDREQ r0,[lr,#-4]???????????? ; 如果是ARM狀態(tài),使用字加載指令,讀取發(fā)生異常的指令地址
BICEQ r0,r0,#0xFF000000??????? ; 提取中斷向量號并將中斷向量號存入r0
; r0 存儲中斷向量號
CMP r0, #MaxSWI?????????????? ; 判斷中斷是否超出范圍
LDRLS pc, [pc, r0, LSL#2]????? ; 如果未超出范圍,跳轉到軟中斷向量表Switable
B SWIOutOfRange?????????????? ; 如果超出范圍,跳轉到軟中斷越界處理程序
switable
DCD do_swi_1
DCD do_swi_2
:
:
do_swi_1
; 1號軟中斷處理函數
LDMFD sp!, {r0-r3,r12,pc}^ ; Restore the registers and return.
????????????????????????? ; 恢復寄存器并返回
do_swi_2 ??????????????? ?? ; 2號軟中斷處理函數
:
2.向量表
如前面介紹向量表時提到的,每一個異常發(fā)生時總是從異常向量表開始跳轉。最簡單的一種情況是向量表里面的每一條指令直接跳向對應的異常處理函數。其中快速中斷處理函數FIQ_handler()可以直接從地址0x1C處開始,省下一條跳轉指令,如圖3.6所示。
圖3.6? 異常處理向量表
但跳轉指令B的跳轉范圍為±32MB,但很多情況下不能保證所有的異常處理函數都定位在向量的32MB范圍內,需要更大范圍的跳轉,而且由于向量表空間的限制,只能由一條指令完成。具體實現方法有下面兩種。
(1)MOV? PC,#imme_value
這種辦法將目標地址直接賦值給PC。但這種方法受格式限制不能處理任意立即數。這個立即數由一個8位數值循環(huán)右移偶數位得到。
(2)LDR? PC,[PC+offset]
把目標地址先存儲在某一個合適的地址空間,然后把這個存儲器單元的32位數據傳送給PC來實現跳轉。
這種方法對目標地址值沒有要求。但是存儲目標地址的存儲器單元必須在當前指令的±4KB空間范圍內。
注意 |
在計算指令中引用offset數值的時候,要考慮處理器流水線中指令預取對PC值的影響。 |
?
3.4.5? 從異常處理程序中返回
當一個異常處理返回時,一共有3件事情需要處理:通用寄存器的恢復、狀態(tài)寄存器的恢復以及PC指針的恢復。通用寄存器的恢復采用一般的堆棧操作指令即可,下面重點介紹狀態(tài)寄存器的恢復以及PC指針的恢復。
1.恢復被中斷程序的處理器狀態(tài)
PC和CPSR的恢復可以通過一條指令來實現,下面是3個例子。
·? MOVS? PC,LR
·? SUBS? PC,LR,#4
·? LDMFD? SP!,{PC}^
這幾條指令是普通的數據處理指令,特殊之處在于它們把程序計數器寄存器PC作為目標寄存器,并且?guī)Я颂厥獾暮缶Y“S”或“^”。其中“S”或“^”的作用就是使指令在執(zhí)行時,同時完成從SPSR到CPSR的拷貝,達到恢復狀態(tài)寄存器的目的。
2.異常的返回地址
異常返回時,另一個非常重要的問題就是返回地址的確定。前面提到過,處理器進入異常時會有一個保存LR的動作,但是該保持值并不一定是正確中斷的返回地址。以一個簡單的指令執(zhí)行流水狀態(tài)圖來對此加以說明,如圖3.7所示。
圖3.7 ?3級流水線示例
在ARM架構里,PC值指向當前執(zhí)行指令地址加8。也就是說,當執(zhí)行指令A(地址0x8000)時,PC等于0x8000+8=0x8008,即等于指令C的地址。假設指令A是BL指令,則當執(zhí)行時,會把PC值(0x8008)保存到LR寄存器。但是,接下來處理器會對LR進行一次自動調整,使LR=LR-0x4。所以,最終保存在LR里面的是圖3.5中所示的B指令地址。所以當從BL返回時,LR里面正好是正確的返回地址。
同樣的跳轉機制在所有的LR自動保存操作中都存在。當進入中斷響應時,處理器對保存的LR也進行一次自動調整,并且跳轉動作也是LR=LR-0x04。由此,就可以對不同異常類型的返回地址依次比較。
假設在指令B處(地址0x8004)發(fā)生了異常,進入異常相應后,LR經過跳轉保存的地址值應該是C的地址0x8008。
(1)軟中斷異常
如果發(fā)生軟中斷異常,即指令B為SWI指令,從SWI中斷返回后下一條執(zhí)行指令就是C,正好是LR寄存器保存的地址,所以只有直接把LR恢復給PC即可。
(2)IRQ或FIQ異常
如果發(fā)生的是IRQ或FIQ異常,因為外部中斷請求中斷了正在執(zhí)行的指令B,當中斷返回后,需要重新回到B指令執(zhí)行,也就是說,返回地址應該是B(0x8004),需要把LR減4送PC。
(3)Data Abort數據中止異常
在指令B處進入數據異常的相應,但導致數據異常的原因卻應該是上一條指令A。當中斷處理程序恢復數據異常后,要回到A重新執(zhí)行導致數據異常的指令,因此返回地址應該是LR加8。
為方便起見,表3.7總結了各異常和返回地址的關系
表3.7????? 異常和返回地址
異??? 常 |
地??? 址 |
用??? 途 |
復位 |
- |
復位沒有定義LR |
數據中止 |
LR-8 |
指向導致數據中止異常的指令 |
FIQ |
LR-4 |
指向發(fā)生異常時正在執(zhí)行的指令 |
IRQ |
LR-4 |
指向發(fā)生異常時正在執(zhí)行的指令 |
預取指令中止 |
LR-4 |
指向導致預取指令異常的那條指令 |
SWI |
LR |
執(zhí)行SWI指令的下一條指令 |
未定義指令 |
LR |
指向未定義指令的下一條指令 |
?
3.4.6? 在應用程序中安裝異常處理程序
1.使用匯編語言安裝異常處理程序
如果系統(tǒng)啟動不依賴于Debug或Debug monitor軟件,可以使用匯編語言在系統(tǒng)啟動時直接安裝異常處理程序。
下面的例子顯示了系統(tǒng)從0x0地址啟動,直接安裝異常處理程序的方法。
Vector_Init_Block
???? LDR PC, Reset_Addr
???? LDR PC, Undefined_Addr
???? LDR PC, SWI_Addr
???? LDR PC, Prefetch_Addr
???? LDR PC, Abort_Addr
???? NOP???????????????????????? ;保留向量
???? LDR PC, IRQ_Addr
???? LDR PC, FIQ_Addr
Reset_Addr DCD Start_Boot
Undefined_Addr DCD Undefined_Handler
SWI_Addr DCD SWI_Handler
Prefetch_Addr DCD Prefetch_Handler
Abort_Addr DCD Abort_Handler
???? DCD 0?????????????????????? ;保留向量
IRQ_Addr DCD IRQ_Handler
FIQ_Addr DCD FIQ_Handler
?
有些情況下,系統(tǒng)0x0地址不一定是ROM。如果0x0地址為RAM,那么就系統(tǒng)將中斷向量表從ROM復制RAM,下面的例子顯示了這樣一個過程。
MOV R8, #0
ADR R9, Vector_Init_Block
LDMIA R9!,{r0-r7}??????????????? ;復制中斷向量表 (8 words)
STMIA R8!,{r0-r7}
LDMIA R9!,{r0-r7}??????????????? ;復制由偽操作 DCD定義的地址
STMIA R8!,{r0-r7}
注意 |
可以使用Scatter文件定義加載向量表的地址,這樣上述代碼的拷貝工作由C庫函數完成。 |
2.使用C語言安裝異常處理程序
程序中有時需要在main()函數中使用C語言安裝中斷向量表。這就要求指令經編譯后的解碼能安裝在內存的正確位置。
(1)向量表中使用跳轉指令的情況
如果在向量表中使用跳轉指令,使用下面的步驟完成向量表的安裝。
① 讀取異常處理程序的地址。
② 從異常處理程序地址中減去向量表中的偏移。
③ 為適應指令流水線,將上一步得到的地址減8。
④ 將得到的結果右移2位,得到以字為單位的地址偏移量。
⑤ 將結果的高8位清零,得到跳轉指令的24位偏移量。
⑥ 將上一步得到的結果和0xea000000(無條件跳轉指令編碼)做邏輯與操作,從而得到要寫到向量表中的跳轉指令的正確編碼。
下面的例子顯示了這樣一個標準過程。
unsigned Install_Handler (unsigned routine, unsigned *vector)
{ unsigned vec, oldvec;
vec = ((routine - (unsigned)vector - 0x8)>>2);
if ((vec & 0xFF000000))
{
/* diagnose the fault */
prinf ("Installation of Handler failed");
exit (1);
}
vec = 0xEA000000 | vec;
oldvec = *vector;
*vector = vec;
return (oldvec);
}
(2)在向量表中使用加載PC指令
在向量表中使用加載PC指令,按照下面的步驟完成。
① 讀取異常處理程序地址。
② 從異常處理程序地址中減去向量表中的偏移。
③ 為適應指令流水線,將上一步得到的地址減8。
④ 保留結果的后12位。
⑤ 將結果與0xe59ff000(LDR PC, [PC,#offset])做邏輯或操作,從而得到要寫到向量表中的跳轉指令的正確編碼。
⑥ 將異常處理程序的地址放到相應的存儲單元。
下面的例子顯示了一個標準的C語言過程。
unsigned Install_Handler (unsigned location, unsigned *vector)
{ unsigned vec, oldvec;
vec = ((unsigned)location - (unsigned)vector - 0x8) | 0xe59ff000;
oldvec = *vector;
*vector = vec;
return (oldvec);
}
3.4.7? FIQ和IRQ中斷處理函數的設計
1.中斷分支
ARM內核只有兩個外部中斷輸入信號nFIQ和nIRQ。但對于一個系統(tǒng)來說,中斷源可能多達幾十個。為此,在系統(tǒng)集成的時候,一般都會有一個異常控制器來處理異常信號,如圖3.8所示。
圖3.8? 中斷系統(tǒng)
這時候用戶程序可能存在多個IRQ/FIQ的中斷處理函數。為了使從向量表開始的跳轉始終能找到正確的處理函數入口,需要設置一套處理機制和方法。
多數情況下是由軟件來處理異常分支的,因為軟件可以通過讀取中斷控制器來獲得中斷源的信息,如圖3.9所示。
有些芯片可能支持特殊的硬件分支功能,這需要查看具體的芯片說明。
因為軟件的靈活性,可以設計出比圖3.9更好的流程控制方法,如圖3.10所示。
Int_vector_table是用戶自己開辟的一塊存儲器空間,里面按次序存放異常處理函數的地址。IRQ_Handler()從中斷控制器獲取中斷源信息,然后再從Int_vector_table中的對應地址單元得到異常處理函數的入口地址,完成一次異常響應的跳轉。這種方法的好處是用戶程序在運行過程中,能夠很方便地動態(tài)改變異常服務內容。
圖3.9? 軟件控制中斷分支
圖3.10? 靈活的軟件分支設計
?
進入異常處理程序后,用戶可以完全按照自己的意愿來進行程序設計,包括調用Thumb狀態(tài)的函數等。但對于絕大多數的系統(tǒng)來說,有兩個步驟必須處理,一是現場保護,二是要把中斷控制器中對應的中斷狀態(tài)標識清除,表明該中斷請求已經得到響應,否則,中斷函數退出以后,又會被再一次觸發(fā),從而進入周而復始的死循環(huán)。
?
2.ARM編譯器對中斷處理函數編寫的擴展
考慮到中斷處理函數在現場保護和返回地址的處理上與普通函數的不同之處,不能直接把普通函數體連接到異常向量表上,需要在上面加上一層封裝,下面是一個例子。
IRQ_Handler?????????????????????? ;中斷相應函數
??? STMFD??? SP!,{r0-r12,lr}????????? ;保護現場,一般只需要保護{r0-r3,LR}
??? BL?? IrqHandler???????????????? ;進入普通處理函數,C或匯編均可
……
??? LDMFD??? sp!,{r0-r12,LR}????????? ;恢復現場
??? SUBS??? pc,lr,#4???????????????? ;中斷返回,注意返回地址
為了方便使用高級語言直接編寫異常處理函數,ARM編譯器對此做了特定的擴展,可以使用函數聲明關鍵字_irq,這樣編譯出來的函數就可以滿足異常響應對現場保護和恢復的需要,并且自動加入LR減4的處理,符合IQR和FIQ中斷處理的要求。
下面的例子顯示了使用_irq對中斷處理函數產生的影響。
C語言源程序如下。
__irq void IRQHandler (void)
{
volatile unsigned int *base = (unsigned int *) 0x80000000;
if (*base == 1)
{
/*調用C語言中斷處理函數*/
C_int_handler();
}
/*清楚中斷標志*/
*(base+1) = 0;
}
使用armcc編譯出的匯編代碼如下。
IRQHandler PROC
??? STMFD sp!,{r0-r4,r12,lr}
??? MOV r4,#0x80000000
??? LDR r0,[r4,#0]
??? SUB sp,sp,#4
??? CMP r0,#1
??? BLEQ C_int_handler
??? MOV r0,#0
??? STR r0,[r4,#4]
??? ADD sp,sp,#4
??? LDMFD sp!,{r0-r4,r12,lr}
??? SUBS pc,lr,#4
??? ENDP
如果不使用_irq子程序聲明關鍵字,編譯出的匯編代碼如下。
IRQHandler PROC
??? STMFD sp!,{r4,lr}
??? MOV r4,#0x80000000
??? LDR r0,[r4,#0]
??? CMP r0,#1
??? BLEQ C_int_handler
??? MOV r0,#0
??? STR r0,[r4,#4]
??? LDMFD sp!,{r4,pc}
??? ENDP
3.可重入中斷設計
在缺省情況下,ARM中斷是不可重入的。因為一旦進入異常響應狀態(tài),ARM自動關閉中斷使能。如果在異常處理過程中,簡單地打開中斷使能而發(fā)生中斷嵌套時,顯然新的異常處理將破壞原來的中斷現場而導致出錯。但有時需要中斷必須是可重入的,因此要通過程序設計來解決這個問題。其中有兩個關鍵問題。
① 新中斷使能之前,必須要保護好前一個中斷的現場信息。比如LR_irq和SPSR_irq等,這一點比較容易做的。
② 中斷處理過程中對BL進行保護。
在中斷處理函數中發(fā)生函數調用BL是很常見的,假設有下面一種情況。
IRQ_Handler:
???? ……
???? BL??? Foo
???? ADD
其中,
Foo:
??? STMFD? SP!,{r0-r3,LR}
??? ……
??? LDMFD? SP!{r0-r3,PC}
上述程序,在IRQ處理函數IRQ_Handler()中調用了函數Foo()。若是在IRQ_Handler()里面中斷可重入的話,可能發(fā)生問題,考察下面的情況:當新的中斷請求恰好在“BL? Foo”指令執(zhí)行完成后發(fā)生。這時候LR_irq寄存器(因在IRQ模式下,所以是LR_irq)的值將調整為BL指令的下一條指令(ADD)地址,使其能從Foo()正確返回;但是因為這時候發(fā)生了中斷請求,接下來要進行新中斷的響應,處理器在新中斷響應過程中也要進行LR_irq保存。這次對LR_irq的操作發(fā)生了沖突,當新中斷返回后,往下執(zhí)行STMFD指令,這時候壓棧的LR已不是原來的ADD指令地址,從而使子程序Foo()無法正確返回。
這個問題無法通過增加額外的現場保護指令來解決。一個辦法就是在重新使能中斷之前改變處理器模式,也就是使上面程序的“BL? Foo”指令不要運行在IRQ模式下。這樣當新的中斷發(fā)生時,就不會造成LR寄存器的沖突??紤]ARM的所有運行模式,采用SYSTEM模式是比較合適的,因為它是特權模式,不是IRQ模式,與中斷響應無關。
下面的例子顯示了標準的IRQ/FIQ異常中斷處理程序。
??? PRESERVE8
??? AREA INTERRUPT, CODE, READONLY
??? IMPORT C_irq_handler
IRQ
??? SUB lr, lr, #4??????????????? ;跳轉返回地址
STMFD sp!, {lr}????????????????? ;保存返回地址
MRS r14, SPSR??????????????????? ;讀取SPSR
STMFD sp!, {r12, r14}???????????? ;保存寄存器
; 清除中斷源
MSR CPSR_c, #0x1F??????????????? ;切換到SYSTEM模式,
STMFD sp!, {r0-r3, lr}??????????? ;保存lr_USR 和其他使用到的寄存器
BL C_irq_handler???????????????? ;跳轉到C中斷處理函數
LDMFD sp!, {r0-r3, lr}??????????? ;恢復用戶模式寄存器
MSR CPSR_c, #0x92??????????????? ;切換回irq模式
LDMFD sp!, {r12, r14}
MSR SPSR_cf, r14
LDMFD sp!, {pc}^
END
?
3.4.8? SWI異常處理函數的設計
本小節(jié)主要介紹編寫SWI處理程序時需要注意的幾個問題,包括下面內容。
·? 判斷SWI中斷號。
·? 使用匯編語言編寫SWI異常處理函數。
·? 使用C語言編寫SWI異常處理函數。
·? 在特權模式下使用SWI異常中斷處理。
·? 從應用程序中調用SWI。
·? 從應用程序中動態(tài)調用SWI。
1.判斷SWI中斷號
當發(fā)生SWI異常,進入異常處理程序時,異常處理程序必須提取SWI中斷號,從而得到用戶請求的特定SWI功能。
在SWI指令的編碼格式中,后24位稱為指令的“comment field”。該域保存的24位數,即為SWI指令的中斷號,如圖3.11所示。
圖3.11? SWI指令編碼格式
第一級的SWI處理函數通過LR寄存器內容得到SWI指令地址,并從存儲器中得到SWI指令編碼。通常這些工作通過匯編語言、內嵌匯編來完成。
下面的例子顯示了提取中斷向量號的標準過程。
PRESERVE8
AREA TopLevelSwi, CODE, READONLY???????? ;第一級SWI處理函數.
EXPORT SWI_Handler
SWI_Handler
STMFD sp!,{r0-r12,lr}????????????????? ;保存寄存器
LDR r0,[lr,#-4]??????????????????????? ;計算SWI指令地址.
BIC r0,r0,#0xff000000????????????????? ;提取指令編碼的后24位
;
; 提取出的中斷號放r0寄存器,函數返回
;
LDMFD sp!, {r0-r12,pc}^???????????????? ;恢復寄存器
END
例子中,使用LR-4得到SWI指令的地址,再通過“BIC r0, r0, #0xFF000000”指令提取SWI指令中斷號。
2.匯編語言編寫SWI異常處理函數
最簡單的方法是利用得到的中斷向量號,使用跳轉表直接跳轉到實現相應SWI功能的處理程序。
下面的例子,使用匯編語言實現了這種跳轉。
??? CMP r0,#MaxSWI??????????????? ;中斷向量范圍檢測
??? LDRLS pc, [pc,r0,LSL #2]
??? B SWIOutOfRange
SWIJumpTable
??? DCD SWInum0
??? DCD SWInum1
; 使用DCD 定義各功能函數入口地址
SWInum0???????????????????????? ;0號中斷
??? B EndofSWI
SWInum1???????????????????????? ;1號中斷
??? B EndofSWI
;
EndofSWI
3.使用C語言編寫SWI異常處理函數
雖然第一級SWI處理函數(完成中斷向量號的提?。┍仨氂脜R編語言完成,但第二級中斷處理函數(根據提取的中斷向量號,跳轉到具體處理函數)就可以使用C語言來完成。
因為第一級的中斷處理函數已經將中斷號提取到寄存器r0中,所以根據AAPCS函數調用規(guī)則,可以直接使用BL指令跳轉到C語言函數,而且中斷向量號作為第一個參數被傳遞到C函數。
例如匯編中使用了“BL ?C_SWI_Handler”跳轉到C語言的第二級處理函數,則第二級的C語言函數示例如下所示。
void C_SWI_handler (unsigned number)
{
??? switch (number)
???? {
????? case 0 : /* SWI number 0 code */
????? break;
????? case 1 : /* SWI number 1 code */
????? break;
????? ...
????? default : /* Unknown SWI - report error */
???? }
}
另外,如果需要傳遞的參數多于1個,那么可以使用堆棧,將堆棧指針作為函數的參數傳遞給C類型的二級中斷處理程序,就可以實現在兩級中斷之間傳遞多個參數。
例如:
MOV r1, sp???????????????? ;將傳遞的第二個參數(堆棧指針)放到r1中
BL C_SWI_Handler??????????? ;調用C函數
相應的C函數的入口變?yōu)椋?/p>
void C_SWI_handler(unsigned number, unsigned *reg)
同時,C函數也可以通過堆棧返回操作的結果。
?
4.在特權模式下使用SWI異常處理
在特權模式下使用SWI異常處理,和IRQ/FIQ中斷嵌套基本類似。當執(zhí)行SWI指令后,處理器執(zhí)行下面操作。
① 處理器進入特權模式。
② 將程序狀態(tài)字內容CPSR保存到SPSR_svc。
③ 返回地址放入LR_svc。
如果處理器已經處于特權模式,再發(fā)生SWI異常,則LR_svc和SPSR_svc寄存器的值將丟失。
所以在特權模式下,調用SWI軟中斷異常,必須先將LR_svc和SPSR_svc寄存器的值壓棧保護。下面的例子顯示了一個可以在特權模式下調用的SWI處理函數。
???? ?AREA SWI_Area, CODE, READONLY
??? PRESERVE8
??? EXPORT SWI_Handler
??? IMPORT C_SWI_Handler
????????????????????? ?T_bit EQU 0x20
SWI_Handler
??? STMFD sp!,{r0-r3,r12,lr}????????????? ;寄存器壓棧保護
?? ?MOV r1, sp????????????????????????? ;堆棧指針放r1作為參數傳遞.
?? ?MRS r0, spsr???????????????????????? ;讀取spsr.
?? ?STMFD sp!, {r0, r3}?????????????????? ;將spsr壓棧保護
?? ?;
?? ?;
?? ?LDR r0,[lr,#-4]????????????????????? ;計算SWI指令地址.
?? ?BIC r0,r0,#0xFF000000???????????????? ;讀取SWI中斷向量號.
?? ?; r0存放中斷向量號
?? ?; r1 堆棧指針
?? ?BL C_SWI_Handler???????????????????? ;調用C程序的SWI處理函數.
?? ?LDMFD sp!, {r0, r3}?????????????????? ;從堆棧中讀取spsr.
?? ?MSR spsr_cf, r0????????????????????? ;恢復spcr
?? ?LDMFD sp!, {r0-r3,r12,pc}^???????????? ;恢復其他寄存器并返回.
?? ?END
5.從應用程序中調用SWI
可從匯編語言或 C/C++ 中調用 SWI。
(1)從匯編應用程序中調用SWI
從匯編語言程序中調用SWI,只要遵循AAPCS標準即可。調用前,設定所有必須的值并發(fā)出相關的 SWI。例如:
MOV r0, #65 ??????; 將軟中斷的子功能號放到r0中
SWI 0x0
注意 |
SWI指令和其他所以ARM指令一樣,可以被條件執(zhí)行。 |
(2)從C應用程序中調用SWI
在C或C++應用程序中調用SWI,要將C語言的子程序用編譯器擴展_swi聲明,例如:
__swi(0) void my_swi(int);
……
……
……
my_swi(65);
編譯器擴展_swi確保了SWI以內聯(lián)方式進行編譯,而沒有額外的開銷。但有如下的AAPCS限制。
·? 函數調用參數只能使用r0~r3傳遞。
·? 函數返回值只能通過r0~r3傳遞。
向內聯(lián)的SWI函數傳遞參數和向實際的子函數傳遞參數基本類似。但返回值的情況比較復雜。如果有兩到四個返回值,則必須告訴編譯程序返回值是以結構形式返回的,并使用__value_in_regs 偽操作聲明。這是因為基于結構值的函數通常被處理為一個void(空)型函數,且第一個自變量必須為存放結果結構的地址。
下面的例子顯示了對編號為0x0、0x1、0x2和0x3的SWI軟中斷的調用。其中,SWI0x0和SWI0x1傳遞兩個整型參數并返回一個單一結果;SWI0x2傳遞4個參數并返回一個單一結果;而SWI0x3傳遞4個參數并通過結構體返回4個結果。
#include <stdio.h>
#include "swi.h"
unsigned *swi_vec = (unsigned *)0x08;
extern void SWI_Handler(void);
int main( void )
{
int result1, result2;
struct four_results res_3;
Install_Handler( (unsigned) SWI_Handler, swi_vec );
printf("result1 = multiply_two(2,4) = %dn", result1 = multiply_two(2,4));
printf("result2 = multiply_two(3,6) = %dn", result2 = multiply_two(3,6));
printf("add_two( result1, result2 ) = %dn", add_two( result1, result2 ));
printf("add_multiply_two(2,4,3,6) = %dn", add_multiply_two(2,4,3,6));
res_3 = many_operations( 12, 4, 3, 1 );
printf("res_3.a = %dn", res_3.a );
printf("res_3.b = %dn", res_3.b );
printf("res_3.c = %dn", res_3.c );
printf("res_3.d = %dn", res_3.d );
return 0;
}
__swi(0) int multiply_two(int, int);
__swi(1) int add_two(int, int);
__swi(2) int add_multiply_two(int, int, int, int);
struct four_results
{
int a;
int b;
int c;
int d;
};
__swi(3) __value_in_regs struct four_results many_operations(int, int, int, int);
(3)應用程序中動態(tài)調用SWI
在某些情形下,需要調用直到運行時才會知道其編號的 SWI。例如,當有很多相關操作可在同一目標上執(zhí)行,并且每一個操作都有其自己的 SWI 時,就會發(fā)生這種情況。在此情況下,上一小節(jié)的方法不適用。
解決的方法有兩種。
·? 在運行時得到SWI功能號,然后構造出相應的SWI指令的編碼,將該編碼保存在某個存儲單元中,將PC指針指向該單元,執(zhí)行指令。
·? 使用一個通用的SWI異常中斷處理程序,將運行時需要調用的SWI功能號作為參數傳遞給該通用的SWI異常處理程序,通用的SWI異常中斷處理程序根據參數值調用相應的SWI處理程序完成需要的操作。
通過匯編語言可以實現第二種解決辦法:通過寄存器(通常為r0或r12)傳遞所需要的操作數,這樣可以重新編寫SWI處理程序,對相應寄存器中的值進行處理。
但有些情況下,為了節(jié)省程序開銷,需要直接使用SWI中斷號對程序調用。例如,操作系統(tǒng)可能會使用單一的一條SWI指令并用寄存器來傳遞所需運算的編號。這使得其他SWI空間可用于特定應用程序的SWI。在一個特定的應用程序中,如果從指令中提取SWI編號的開銷太大,就可使用這個方法。ARM(0x123456)和Thumb(0xAB)半主機方式的SWI就是這樣實現的。
?
下面的例子顯示了如何使用_swi將C函數調用映射到半主機方式的SWI。
#ifdef __thumb
/* Thumb 狀態(tài)的Semihosting軟中斷處理*/
#define SemiSWI 0xAB
#else
/* ARM狀態(tài)下的Semihosting的軟中斷處理*/
#define SemiSWI 0x123456
#endif
/* 使用Semihosting軟中斷輸出一個字符*/
__swi(SemiSWI) void Semihosting(unsigned op, char *c);
#define WriteC(c) Semihosting (0x3,c)
void write_a_character(int ch)
{
char tempch = ch;
WriteC( &tempch );
}
編譯程序含有一個機制,用以支持使用r12來傳遞所需運算的值。根據AAPCS標準,r12為IP寄存器,并且專用于函數調用。其他時間內可將其用作暫存寄存器。如前面所述,通用SWI參數和返回值通過r0~r3寄存器傳遞。而r12可用于傳遞通用SWI調用的中斷功能編號。
下面的例子顯示了通用SWI的C語言程序框架。
__swi_indirect(0x80)
unsigned SWI_ManipulateObject(unsigned operationNumber,
unsigned object,unsigned parameter);
unsigned DoSelectedManipulation(unsigned object,
unsigned parameter, unsigned operation)
{
return SWI_ManipulateObject(operation, object, parameter);
}
生成的匯編代碼如下。
DoSelectedManipulation PROC
STMFD sp!,{r3,lr}
MOV r12,r2
SWI 0x80
LDMFD sp!,{r3,pc}
ENDP