1. 程式人生 > >ARM異常中斷初探----轉載

ARM異常中斷初探----轉載

1。中斷/異常相量的裝入和執行方式。      
      中斷和異常都是非同步發生的事件,當該事件發生,系統將停止目前正在執行的程式碼轉而執行事件響應的服務程式。而事件服務程式的入口點就是中斷/異常向量所在的位置。arm的中斷向量可以是0x0開始的低地址向量,也可以是在FFFF0000位置的高向量地址。winCE下使用高地址作為trap區,所以在CE下arm使用高地址向量。下面我們來了解一下中斷/異常向量的安裝和執行過程。 
在kernelStart的過程中通過程式將如下程式碼複製到ffff0000的位置. 
VectorInstructions 
        ldr     pc, [pc, #0x3E0-8]              ; reset 
        ldr     pc, [pc, #0x3E0-8]              ; undefined instruction 
        ldr     pc, [pc, #0x3E0-8]              ; SVC 
        ldr     pc, [pc, #0x3E0-8]              ; Prefetch abort 
        ldr     pc, [pc, #0x3E0-8]              ; data abort 
        ldr     pc, [pc, #0x3E0-8]              ; unused vector location 
        ldr     pc, [pc, #0x3E0-8]              ; IRQ 
        ldr     pc, [pc, #0x3E0-8]              ; FIQ 

  而在ffff03e0的位置放上如下的資料,每一項(32bit)對應一個異常的跳轉地址也就是winCE的異常/中斷向量跳轉表。該表項的內容就是發生異常後將要執行的服務程式的入口地址。具體如下。 
VectorTable 
        DCD     -1                              ; reset 
        DCD     UndefException                  ; undefined instruction 
        DCD     SWIHandler                      ; SVC 
        DCD     PrefetchAbort                   ; Prefetch abort 

        IF :DEF:ARMV4T :LOR: :DEF:ARMV4I 
        DCD     OEMDataAbortHandler             ; data abort 
        ELSE 
        DCD     DataAbortHandler                ; data abort 
        ENDIF 

        DCD     -1                              ; unused vector 
        DCD     IRQHandler                      ; IRQ 
        DCD     FIQHandler                      ; FIQ 
      在上面的這些程式碼/資料在記憶體空間上按照上述要求放置好以後,每次觸發一個異常就自動執行到相應跳轉表項所對應的地址執行。 
  
2.異常/中斷服務程式 
  在arm下,由於有7種異常狀態包括reset、Undef exception、software interrupt(swi)、Prefech Abort、DataAbort、IRQ、FIQ七種異常/中斷。reset僅在復位時發生,其他6種都是在系統執行時發生。當任何一個異常發生並得到響應時,ARM 核心自動完成以下動作: 
拷貝 CPSR 到 SPSR_<mode> 
設定適當的 CPSR 位: 
改變處理器狀態進入 ARM 狀態 
改變處理器模式進入相應的異常模式 
設定中斷禁止位禁止相應中斷 
更新 LR_<mode> 
設定 PC 到相應的異常向量 
同時不管異常發生在ARM 還是Thumb 狀態下,處理器都將自動進入ARM 狀態。並且中斷使能會自動被關閉。在這個時候由於部分通用暫存器是不同模式公用的,所以還需要儲存這些將會被破壞的暫存器,待到處理完成的時候恢復這些暫存器被中斷前的狀態。另外在進入異常模式後,lr的值不一定就是我們所需恢復執行的位置,該位置受到異常型別和流水線誤差的影響。在SWI模式下,LR就是返回值。在IRQ和FIQ中LR=LR-4,DataAbort下LR=LR-8;具體原因我們就不討論了,有興趣可以參看<基於ARM 的嵌入式程式開發要點>一文。下面分別對這些服務程式進行分析。 
   
2-1.undef exception服務程式 
      
undef exception在執行到過非法的指令時產生,通常來模擬一些處理器不支援的功能,如浮點運算。簡單說一下undef exception的過程:噹噹前指令為一條處理器不支援的指令時,處理器會自動動將該指令送交各協處理器(如MMU、FPU)處理,如果這些協處理器都無法識別這條指令的時候,就產生該異常。下面開始看相應的程式碼。 
        NESTED_ENTRY    UndefException 
        sub     lr, lr, #4                      ; (lr) = address of undefined instruction 
        stmdb   sp, {r0-r3, lr} 
        mov     r1, #ID_UNDEF_INSTR 
        b       CommonHandler 
        ENTRY_END UndefException 

上面就是undef Exception的服務程式的入口處(已經將不參與編譯和Thumb模式下的程式碼去掉),通過lr-=4計算出觸發異常前的指令地址,同時儲存r0-r3和lr入undef_exception stack用於最後恢復現場和取得異常指令本身,隨後進入分發程式CommonHandler.CommonHandler是一個公共的異常服務程式,它通過不同的傳入引數來進行處理,在這裡mov r1,#ID_UNDEF_INSTR就是指定異常模式為undef Exception. 
  
2-2.swi服務程式 
      
      按在ARM處理器的設計意圖,系統軟體的系統呼叫(SystemCalls)都是通過SWI指令完成。SWI相當於一箇中斷指令,不同的是SWI不是由外部中斷源產生的,同時對應於SWI的異常向量位於0xc的位置或0xffff 000c的位置。也就是說當執行一個swi指令後,當前程式流中斷,並轉入0xc或0xffff000c執行,同時將CPSR_mode(當前程式狀態暫存器)複製入SPSR_svc,轉入SVC模式執行(使用特權模式的暫存器組)。也就是說系統通過執行SWI引發系統swi異常後切換入特權模式,系統呼叫功能號由swi xx後的xx決定,在執行完指定功能的程式碼後返回異常時的地址並恢復使用者模式。我們看看,Wince中這部分程式碼是如何實現的。 
        DCD     SWIHandler                      ; SVC<<--------------------------SWI入口點。 
         
     LEAF_ENTRY SWIHandler 
  IF {FALSE}                
  ... 
  ENDIF 
        movs    pc, lr 
        ENTRY_END SWIHandler 
        上面IF {FALSE}到ENDIF之間的程式碼在編譯的時候是得不到編譯的(事實上這部分程式碼是用於開發中除錯使用的,針對特殊的硬體平臺,一般與我們使用的硬體平臺無關。所以下面摘抄的程式碼都不將不參與編譯的內容寫入),因此SWI服務程式就是一句話。movs    pc, lr也就是直接回到SWI的地方,同時將SPSR_svc恢復到CPSR_mode中。這個過程中並沒有進行在系統態執行特定系統指令序的工作,而僅僅是簡單的返回,所以這不是系統呼叫,系統呼叫還需要根據呼叫號的不同執行指定的核心態程式碼。也就是說Wince的系統呼叫不是通過SWI來完成的,而是通過其他的異常處理手段達成的。 


2-3 中斷服務程式 

IRQ(大概是最熟悉的異常方式了)在外部中斷源在需要向處理器請求服務時發生,比如:時鐘、外圍器件FIFO上/下溢位、按鍵等等。IRQHandler就是中斷的處理控制代碼,下面我們來具體看看。 
----------------------------------------------------------------------------------     
    NESTED_ENTRY IRQHandler 
        sub     lr, lr, #4                      ; fix return address 
        stmfd   sp!, {r0-r3, r12, lr}       ;儲存將要用到的暫存器和lr壓入stack_irq 
        PROLOG_END 
        和上面一樣,服務程式的入口處都是例行公事的計算返回位置以抵消流水線誤差。再將要用到的暫存器壓入STACK_IRQ,這樣,準備工作就做完了。 
        ; Test interlocked API status.        
        ;INTERLOCKED_START EQU USER_KPAGE+0x380 
    ;INTERLOCKED_END EQU USER_KPAGE+0x400 
        sub     r0, lr, #INTERLOCKED_START 
        cmp     r0, #INTERLOCKED_END-INTERLOCKED_START 
        bllo    CheckInterlockedRestart 
        上面這部分的內容是關於互鎖的檢測,由於如訊號量這些同步手段都必須作為原子操作進行,不允許打斷。所以如果中斷髮生在互鎖API的執行過程中,就需要專門的處理了。這些API都是放在INTERLOCKED_START和INTERLOCKED_END之間的,通過LR很容易就檢查出是否是INTERLOCKEDXXX的過程中。這裡並不關心互鎖的實現就繞開這部分程式碼繼續往下看,當作中斷沒有發生在interlock過程處理。 
        ; 
        ; CAREFUL! The stack frame is being altered here. It's ok since 
        ; the only routine relying on this was the Interlock Check. Note that 
        ; we re-push LR onto the stack so that the incoming argument area to 
        ; OEMInterruptHandler will be correct. 
        ; 
        mrs     r1, spsr                        ; (r1) = saved status reg 
        stmfd   sp!, {r1}                       ; save SPSR onto the IRQ stack    
        mov     r0,lr                           ; parameter to OEMInterruptHandler 
     msr     cpsr_c, #SVC_MODE:OR:0x80       ; switch to supervisor mode w/IRQs disabled 
        stmfd   sp!, {lr}                       ; save LR onto the SVC stack        
        stmfd   sp!, {r0}                       ; save IRQ LR (in R0) onto the SVC stack (param) 
        ; 
        ; Now we call the OEM's interrupt handler code. It is up to them to 
        ; enable interrupts if they so desire. We can't do it for them since 
        ; there's only on interrupt and they haven't yet defined their nesting. 
        ; 

        CALL    OEMInterruptHandler 
        ldmfd   sp!, {r1}                       ; dummy pop (parameter) 
        ldmfd   sp!, {lr}                       ; restore SVC LR from the SVC stack 
        msr     cpsr_c, #IRQ_MODE:OR:0x80       ; switch back to IRQ mode w/IRQs disabled 
    ; Restore the saved program status register from the stack. 
        ; 
        ldmfd   sp!, {r1}                       ; restore IRQ SPSR from the IRQ stack 
        msr     spsr, r1                        ; (r1) = saved status reg 
        ldr     lr, =KData                      ; (lr) = ptr to KDataStruct 
        
        
        cmp     r0, #SYSINTR_RESCHED      ;->時間片已到,進行排程 
        beq     %F10            
        ;SYSINTR_DEVICES EQU 8         ;是否裝置中斷,中斷號是否有效 
    ;SYSINTR_MAX_DEVICES EQU 32    
        sub     r0, r0, #SYSINTR_DEVICES 
        cmp     r0, #SYSINTR_MAX_DEVICES 
                            ;由此可以看出windowsCE的系統中斷號最大支援32種從9-40. 
                            ;其中第16號(24)被定義為SYSINTR_FIRMWARE 
        ; If not a device request (and not SYSINTR_RESCHED) 
        
        ldrhsb  r0, [lr, #bResched]             ; (r0) = reschedule flag 
        bhs     %F20                            ; not a device request 
        
        ;PendEvents  EQU 0x340             ; offset 0x10*sizeof(DWORD) of aInfo 
                            ;device 中斷 
        ldr     r2, [lr, #PendEvents]           ; (r2) = pending interrupt event mask 
        mov     r1, #1 
        orr     r2, r2, r1, LSL r0              ; (r2) = new pending mask 
        str     r2, [lr, #PendEvents]           ; save it 
    ;*PendEvents = *PendEvents|(1<<InterruptNO); 
        ; 
        ; mark reschedule needed 
                            ;情況1:r0=SYSINTR_RESCHED=1 
                            ;情況2: r0 =r0-SYSINTR_DEVICES>=SYSINTR_MAX_DEVICES        
10      ldrb    r0, [lr, #bResched]             ; (r0) = reschedule flag 
        orr     r0, r0, #1                      ; set "reschedule needed bit" 
        strb    r0, [lr, #bResched]             ; update flag 

20      mrs     r1, spsr                        ; (r1) = saved status register value 
        and     r1, r1, #0x1F                   ; (r1) = interrupted mode 
        cmp     r1, #USER_MODE                  ; previously in user mode? 
        cmpne   r1, #SYSTEM_MODE                ; if not, was it system mode? 
        cmpeq   r0, #1                          ; user or system: is resched == 1 
        ;if(SytemMode(spsr)||UserMode(spsr))&&r0!=1) return; 
        ldmnefd sp!, {r0-r3, r12, pc}^          ; can't reschedule right now so return 
  ************************************************************************************* 
        sub     lr, lr, #4 
        ldmfd   sp!, {r0-r3, r12} 
        stmdb   lr, {r0-r3} 
        ldmfd   sp!, {r0} 
        str     r0, [lr]                        ; save resume address 
        mov     r1, #ID_RESCHEDULE              ; (r1) = exception ID 
        b       CommonHandler 
        ENTRY_END IRQHandler 
    將spsr_irq壓入IRQ堆疊儲存。為呼叫OEMInterruptHandler作準備。(通常中斷處理程式切換入系統態執行的目的在於避免使用終端模式下的暫存器,以方便是實現終端套嵌,這兒切入系統態時終端使能是關閉的,對於模態切換的原因我很迷惑。)OEMInterrupt需要在特權模式下執行,所以這裡增加了切換入特權(SVC)模式的內容。緊接著將要用與傳遞引數的暫存器儲存。設定傳入引數,r0就可以開始呼叫OEMInterruptHandler了,這裡的呼叫規則遵循windowsCE的規範而不是ATPCS的規範。具體過程參考ARM Parameter
[email protected]
。下面是函式原形。int OEMInterruptHandler(unsigned int ra);這裡傳入的引數就是上面的r0,事實上r0代表的引數ra並沒有實質的作用在這裡僅僅是形式上的實現一下而已,不過在這兒可以看到這個傳入的ra實際上就是被中斷的地址,如果需要知道被中斷的位置可以通過ra來查詢,而msdn裡面說這個引數是保留的。返回的引數也是儲存在r0中。其中返回值是系統中斷型別。其中SYSINTR_RESCHED為系統時鐘中斷,每次時間片用完,該時鐘便產生中斷,並設定kData結構的bResched位,進入排程流程。如果中斷型別是系統裝置中斷,那就設定PendEvents,待再次排程的時候處理中斷。所以OEMInterruptHandler必須提前就要對中斷進行響應對該中斷源設定mask,防止在這過程中同一中斷不停發生,導致中斷飽和影響程式流的執行,直道中斷處理真正完成後再次開放該中斷的mask。在這裡還可以看到的是系統裝置中斷號的範圍是從SYSINTR_DEVICES到SYSINTR_MAX_DEVICES,也就是從9-40一共32個裝置中斷號,其中SYSINTR_FIRMWARE為8+16號,這個在編寫OAL的中斷服務程式時需要注意。如果當前的返回值既不是裝置中斷號又不是排程中斷號,則讀出當前排程標示,根據該標示進行判斷是否排程/或返回.如果是進入排程流程則恢復初始的暫存器狀態,再按CommonHandler的要求儲存暫存器。進入CommonHandler,等待分發。 
    
2-3 FIQ服務程式 
        照例看看程式 
        NESTED_ENTRY FIQHandler 
        sub     lr, lr, #4                      ; fix return address 
        stmfd   sp!, {r0-r3, r12, lr} 
        PROLOG_END 
        CALL    OEMInterruptHandlerFIQ 
        ldmfd   sp!, {r0-r3, r12, pc}^          ; restore regs & return for NOP 
        ENTRY_END FIQHandler 
        LTORG 

FIQ是arm體系下特有的異常方式,其工作過程與IRQ類似都是由外部引腳觸發但設計用途不同,IRQ用於通常的外部中斷源的處理,是作為統一、通用的與外部器件互動的手段,而IRQ僅僅用於處理週期短同時又需要快速處理的場合其觸發的事件源通常也來此外部FIQ中斷。如:更換電池、資料傳輸這類工作。可想而知FIQ講究的是快速,精幹。因此FIQ服務程式通常沒有分發,而僅僅是針對單一的工作進行處理保證處理的實時性。因此FIQ的處理相對IRQ就簡單很多,直接呼叫OEMInterruptHandlerFIQ進行處理後返回就完成了整個 FIQ服務程式。    2-4 DataAbort服務程式     
   由資料異常觸發,通常有三種指令引發資料異常,這些指令都是訪存操作,而且都是由MMU的引入後才可能會發生的情況。1.LDR/STR指令.2.SWAP指令。3.LDM/STM指令。而MMU的失效型別又分為4種:儲存訪問失效、地址對齊失效、地址變換失效、域控制器失效、訪問控制權限失效.因此當異常發生後,需要通過訪問CP15來獲知異常的產生具體原因和情況。mfc是微軟的asmarm巨集彙編器專用的巨集指令,相當於mcr指令。資料異常和中斷模式一樣都有可能在互鎖時發生,所以同樣需要對執行互鎖的情形進行處理。正常的情況下在儲存完相關的暫存器後就會讀取CP15的c6,c5,c13三個暫存器。這三個暫存器分別是失效地址暫存器(FAR)、失效狀態暫存器(FSR)、程序號暫存器(這個翻譯得不好PCP15)然後根據具體的失效型別來進行處理。在ARM處理器中對於CP15有三種地址型別,VA,PA,MVA。VA(virtual address)也就是我們通常說的虛擬地址或邏輯地址也就是通過CP15按照PT轉換後的地址,而PA(physical Address)則是對應於AMBA上的地址,對應的是電氣介質也就是實體地址。而MVA(Modified virtual address)則是對應於Cache和TLB中轉換地址。 

        NESTED_ENTRY    DataAbortHandler 
        sub     lr, lr, #8                      ; repair continuation address 
        stmfd   sp!, {r0-r3, r12, lr} 
        PROLOG_END 

        sub     r0, lr, #INTERLOCKED_START 
        cmp     r0, #INTERLOCKED_END-INTERLOCKED_START 
        bllo    CheckInterlockedRestart 
        mfc15   r0, c6                          ; (r0) = FAR        
        mfc15   r1, c5                          ; (r1) = FSR 
        mfc15   r2, c13                         ; (r2) = process base address 
        
        ;  FAR=Fault address register 
    ;  CP = 15: CRn = 6, CRm = 0, op_1 = 0, op_2 = 0 
        ;  FSR=Fault status register 
        ;  CP = 15: CRn = 5, CRm = 0, op_1 = 0, op_2 = 0 
        ;  PCP15: PID  Process ID register 
    ;  CP = 15: CRn = 13, CRm = 0, op_1 = 0, op_2 = 0 
        
        tst     r0, #0xFE000000                 ; slot 0 reference? 
        orreq   r0, r0, r2                      ; (r0) = process slot based address 
        and     r1, r1, #0x0D                   ; type of data abort 
        cmp     r1, #0x05                       ; translation error? 
        movne   r0, #0 
        CALLEQ  LoadPageTable                   ; (r0) = !0 if entry loaded 
        tst     r0, r0 
        ldmnefd sp!, {r0-r3, r12, pc}^          ; restore regs & continue 
        ;********************************************************************* 
        ldr     lr, =KData-4 
        ldmfd   sp!, {r0-r3, r12} 
        stmdb   lr, {r0-r3} 
        ldmfd   sp!, {r0} 
        str     r0, [lr]                        ; save resume address 
        mov     r1, #ID_DATA_ABORT              ; (r1) = exception ID 
        b       CommonHandler 

        ENTRY_END DataAbortHandler 

在DataAbort發生後c6中的資料儲存的就是導致異常的MVA地址,通過windowsCE memory layout可以瞭解到,當前程序的執行空間是在slot0,也就是0x0-0x1fffffff的位置,事實上這個slot上的資料僅僅是實際程序的一個副本所以如果資料異常發生在slot0就需要去找到程序所在的實際slot的存放地址,然後嘗試將核心的頁表複製到硬體實際使用的頁表以達到恢復的目的。如果複製動作成功則返回,否則進入異常分發程式CommonHandler。 
2-5 PrefetchAbort服務程式 
   對於ARM處理器來說,由於其內部使用了哈佛結構---獨立的資料的指令匯流排因此,在資料/指令的讀取過程中產生的異常也就很自然地可以區分開來,本質上而言,這些異常都是同屬於儲存訪問失敗產生的異常,因此這些異常都由MMU相關,在ARM手冊中DataAbort和PrefetchAbort都稱為Memory abort。Prefetch也就是在預取指令的動作後產生的,當處理器執行到這個無效的指令時(這個無效與undefined exception中的不可識別不同,是指不存在或是無法得到)就觸發該異常。所以不是所有的指令無效都產生異常,例如:一個分支程式指向一個不可訪問的區域,而之前的分支指向另一個可訪問區域時。後一個區域儘管預取無效但是由於該分支並不執行所以並不產生異常。所以prefetch的準確定義應該是prefetch and executes Abort:).在ARMV5指令集中BKPT也可以產生預取無效但由於這兒的ARM通常都是ARM9的,也就是使用ARMV4指令所以不討論BKPT的情形。由於資料異常和指令異常同屬儲存異常而且兩個異常不可能會相互中斷所以在ARM的設計上這兩個異常使用同一組暫存器abort組。 

  ALTERNATE_ENTRY PrefetchAbort 

        sub     lr, lr, #0xF0000004  ;考察產生異常的地址是否在0xf0000000-0xf0010400   
        cmp     lr, #0x00010400    ;之間,如果是進入系統呼叫處理 
        bhs     ProcessPrefAbort      ;->>正常的預取異常 執行ProcessPrefAbort 
    ...       
ProcessPrefAbort 
        add     lr, lr, #0xF0000000             ; repair continuation address 
        stmfd   sp!, {r0-r3, r12, lr} 
  
        mov     r0, lr                          ; (r0) = faulting address 
        mfc15   r2, c13                         ; (r2) = process base address 
        tst     r0, #0xFE000000                 ; slot 0 reference? 
        orreq   r0, r0, r2                      ; (r0) = process slot based address 
        CALL    LoadPageTable                   ; (r0) = !0 if entry loaded 
        tst     r0, r0 
        ldmnefd sp!, {r0-r3, r12, pc}^          ; restore regs & continue 
        ldmfd   sp!, {r0-r3, r12} 
        ldr     lr, =KData-4 
        stmdb   lr, {r0-r3} 
        ldmfd   sp!, {r0} 
        str     r0, [lr]                        ; save resume address 
        mov     r1, #ID_PREFETCH_ABORT          ; (r1) = exception ID 
        b       CommonHandler 

下面來結合windowsCE的情形。PrefetchAbort就是該服務程式的入口,在程式的一開始將lr,也就是產生異常的地址+4(流水線導致)的地址減掉0xf000 0004並比較是否在0-0x10400之間,這是為什麼呢?原來windowsCE除了使用PrefetchAbort服務程式作為正常的異常處理以外還使用這個異常作為系統呼叫的手段。通過0xf0000000-0xf0010400這段地址的預取異常來進行系統呼叫。我們下面看處理預取失敗的情況,繞開系統呼叫的先不管。也就是ProcessPrefAbort的分支。 這個分支的內容就與上面DataAbort的內容一樣了,我就不再重複了。