windowsCE異常和中斷服務程序初探(=)

 http://www.cnblogs.com/nasiry/archive/2005/01/06/87381.html
                                                ---------by nasiry 轉載請說明出處


繼續上次的內容,在上次的分析中我們已經對SWI,FIQ,IRQ的流程有了一個大概的認識,下面繼續對DataAbort和PrefetchAbort以及公共分發程序CommonHandler進行一下認識,完整異常處理的流程。

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的內容一樣了,我就不再重複了。 

2-6異常分發 CommonHandler

到此爲止我們已經瞭解了windowsCE對各個異常/中斷模式下的處理情況已經基本做了一個瞭解,但是仍然有一些情況是送到CommonHandler來處理的,下面就對這個分發程序進行分析,完整windowsCE對整個異常流程的處理。
 
        ALTERNATE_ENTRY CommonHandler
        mrs     r2, spsr
        msr     cpsr_c, #SVC_MODE:OR:0x80       ; switch to Supervisor mode w/IRQs disabled
        ldr     r3, =KData                      ; (r3) = ptr to KData page
在CommonHandler開始系統就轉入Supervisor態來執行。     

; Save the processor state into a thread structure. If the previous state was
; User or System and the kernel isn't busy, then save the state into the current
; thread. Otherwise, create a temporary thread structure on the kernel stack.
;
;       (r1) = exception ID
;       (r2) = SPSR
;       (r3) = ptr to KData page
;       Interrupted r0-r3, and Pc saved at (r3-0x14)
;       In Supervisor Mode.
        ALTERNATE_ENTRY SaveAndReschedule

       and     r0, r2, #0x1f                   ; (r0) = previous mode
        cmp     r0, #USER_MODE                  ; 'Z' set if from user mode
        cmpne   r0, #SYSTEM_MODE                ; 'Z' set if from System mode
        bne     %F50                            ; reentering kernel, save state on stack
                        ; 現場保護分支
                        ;發生異常前模態是否是用戶態和系統態。FIQ/IRQ/SVC/Abort/Undef
        ldr     r0, [r3,#pCurThd]               ; (r0) = ptr to current thread
                            ; r0 =kData+pCurThd                          
        add     r0, r0, #TcxR4                  ; (r0) = ptr to r4 save
                            ; r0 =kData+pCurThd+TcxR4
                            ;THREAD_CONTEXT_OFFSET後的0x44bytes用於備份寄存器的內容
        stmia   r0, {r4-r14}^                   ; save User bank registers                
        ****************************************************
        ; Save registers for fault from a non-preemptible state.
50      sub     sp, sp, #TcxSizeof              ; allocate space for temp. thread structure
        cmp     r0, #SVC_MODE
        bne     %F55                            ; must mode switch to save state
        add     r0, sp, #TcxR4                  ; (r0) = ptr to r4 save area
        stmia   r0, {r4-r14}                    ; save SVC state registers
        add     r4, sp, #TcxSizeof              ; (r4) = old SVC stack pointer
        str     r4, [r0, #TcxSp-TcxR4]          ; update stack pointer value
        b       %B10

55
        msr     cpsr, r2                        ; switch to mode exception came from

        add     r0, sp, #TcxR4                  ; (r0) = ptr to r4 save area
        stmia   r0, {r4-r14}                    ; save mode's register state
        msr     cpsr_c, #SVC_MODE:OR:0x80       ; back to supervisor mode
        b       %B10                            ; go save remaining state

在進行統一的處理之前需要保存前態寄存器組的狀態以便後面恢復,在用戶態和系統態的情況下直接保存用戶態的寄存器。同時上面可以看到到達50的條件是前一狀態爲FIQ/IRQ/SVC/Abort/Undef,也就是說爲異常套嵌的情況,系統套嵌的情形前面已經處理過了。這裏首先處理的是SVC下被套嵌的情形,上面可以看到SVC模式都是用於異常/中斷後的具體事件處理(eg: HandleException),所以這個流程並不是獨立存在的,因此當前寄存器就是前態寄存器,所以到這裏需要重新計算stack指針的位置。而另外的FIQ/IRQ/Abort/Undef模式下的寄存器的保存則需要切換當前狀態來進行,所以在進入真正的處理程序之前需要不同的分支來保存前態寄存器狀態。可爲什麼前後都看不到System模式下的寄存器保存呢?這是因爲系統態和用戶態使用同一組寄存器所以保存用戶態寄存器組就達到了現場保護了。這種設計完全是因爲ARM分組寄存器的架構決定的,所以需要不同的處理。通過上面的處理所有的情況都已經統一的完成了現場保護的動作,下面就需要進一步處理這些異常了。
10      ldmdb   r3, {r3-r7}                     ; load saved r0-r3 & Pc
                        ;KData之前的16byte用作傳遞參數用
                        ;所以每個異常句柄最後都由將r0-r3和PC送到這個位置。               
        stmdb   r0!, {r2-r6}                    ; save Psr, r0-r3
        sub     r0, r0, #THREAD_CONTEXT_OFFSET  ; (r0) = ptr to Thread struct
        str     r7, [r0,#TcxPc]                 ; save Pc
        mfc15   r2, c6                          ; (r2) = fault address
        mfc15   r3, c5                          ; (r3) = fault status
    ;r0=&Kdata
    ;r1=exception ID
    ;r2=FAR
    ;r3=FSR   
; Process an exception or reschedule request.

FirstSchedule
20      msr     cpsr_c, #SVC_MODE               ; enable interrupts

        CALL    HandleException
        ldr     r2, [r0, #TcxPsr]               ; (r2) = target status
        and     r1, r2, #0x1f                   ; (r1) = target mode
        cmp     r1, #USER_MODE
        cmpne   r1, #SYSTEM_MODE
        bne     %F30                            ; not going back to user or system mode
        ;System mode and user mode branch
        add     r0, r0, #TcxR3
        ldmia   r0, {r3-r14}^                   ; reload user/system mode registers
        ldr     r1, =KData
        msr     cpsr_c, #SVC_MODE:OR:0x80       ; disable all interrupts
        ldrb    r1, [r1, #bResched]             ; (r1) = nest level + reschedule flag
        cmp     r1, #1
        mov     r1, #ID_RESCHEDULE
        beq     %B20                            ; interrupted, reschedule again
        msr     spsr, r2
        ldr     lr, [r0, #TcxPc-TcxR3]
        ldmdb   r0, {r0-r2}
        movs    pc, lr                          ; return to user or system mode

HandleException是實際進行異常處理的函數,針對上面沒有處理完的異常進一步分析並進行處理。這個函數是沒有公開代碼的,所以沒有辦法進一步深入下去。由於處理的異常類型比較多所以這個異常處理函數的代碼量是相當大的,因此會耗費相對比較多的時鐘週期,在之前的代碼中我們都是在關閉中斷的情況下進行異常處理,如果在這裏還不打開中斷的話整個異常處理過程會相當的長,這樣會很大程度上影響系統的實時性,所以在這裏調用HandleException之前是將中斷重新打開的,待到處理完成再將中斷關閉。對於這些異常,如果不能處理就只有兩種情況:1.結束該進程/線程。2.掛起系統.第二種情況下掛起系統HandleException是不會返回的。因此,只有異常處理正常流程和結束線程的可能。對於返回的情況,這個時候如果返回觸發異常的地址繼續運行的話,仍然會導致異常,所以結束進程/線程都需要重新調度才能完成了。對於異常處理成功的情形,就不必調度了,直接就可以返回產生異常的地方繼續執行。在這裏還要考慮套嵌(這裏僅僅是指系統模式和兼管模式的異常套嵌)的情形,也就是中斷/異常已經進入調度狀態又再次產生中斷/異常,這個時候就強行取消上一次調度,進而重新調度.這用於調度過程中遇到異常恢復和剝奪的情況,如果不屬於這種情況的話就直接恢復寄存器狀態並且返回中斷點繼續執行。

; Return to a non-preemptible privileged mode.
;
;       (r0) = ptr to THREAD structure
;       (r2) = target mode

30    msr     cpsr, r2                        ; switch to target mode
        add     r0, r0, #TcxR0
        ldmia   r0, {r0-r15}                    ; reload all registers & return
通過HandleException處理以後,已經完成了所有異常的處理,所以這裏只是考慮反回的情況,由於這裏不包含用戶模式下的處理,所以這裏處理的都是特權模式,完全可以訪問kdata區域,這裏就直接利用Kdata區域中的線程備份來完成恢復寄存器和返回。
    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章