ARM 中斷狀態和SVC狀態的堆棧切換 (異常)【轉】

ARM 中斷狀態和SVC狀態的堆棧切換 (異常)學習

 

基礎知識:this

Arm的寄存器使用規則以及尋址指令:操作系統

R13  Sp     堆棧寄存器.net

R14  Lr     鏈接寄存器翻譯

R15  PC     程序計數器設計

 

多寄存器尋址:指針

LDMIA R0!,{R1-R4}code

執行之後的效果

R1  <——[R0]

R2  <——[R0+4]

R3  <——[R0+8]

R4  <——[R0+12]

 

堆棧尋址:

STMFD入棧指令,至關於STMDB

STMFD SP!,{R2-R4} 注意這個「!」的使用,在使用和不使用的狀況下會有不同的效果,在後面的代碼中具體分析。

[SP-4]  <­——R4

[SP-8]  <——R3 

[SP-12] <——R2   

 

LDMFD出棧指令,至關於LDMIA

LDMFD SP!,{R6-R8}

R6  <——[SP]

R7  <——[SP+4]

R8  <——[SP+8]

 

補充說明:

LDMIA / STMIA Increment After (先操做,後增長)

LDMIB / STMIB Increment Before(先增長,後操做)

LDMDA / STMDA Decrement After (先操做,後遞減)

LDMDB / STMDB Decrement Before(先遞減,後操做)

 

•STMFD (Push) 塊存儲- Full Descending stack [STMDB]

•LDMFD (Pop)  塊裝載- Full Descending stack [LDMIA]

 

這些使用規則以及默認的表達方法是給編譯器使用。可是在開發底層語言的同時,有必要知道這個麼命名的規則和使用方法。初始化代碼和部分關鍵代碼是靠彙編實現。這些代碼的理解難免和彙編打交道。所以瞭解一下基本的彙編規則仍是頗有幫助。

 

 Arm的工做模式:

Arm的工做模式以及相關寄存器設置:

1,用戶模式(usr)      [10000]:ARM處理器正常的程序執行狀態

2,快速中斷模式(fiq)  [10001]:用於高速數據傳輸或通道處理

3,外部中斷模式(irq)  [10010]:用於通用的中斷處理

4,管理模式(svc)      [10011]:操做系統使用的保護模式

5,停止模式(abt)          [10111]:當數據或指令預取終止時進入該模式,用於虛擬存

儲及存儲保護

6,未定義指令模式(und)[11011]:當未定義的指令執行時進入該模式,用於支持硬件

協處理器的軟件仿真

7,系統模式(sys)      [11111]:運行具備特權模式的操做系統任務

 

設置方法:

MRS R14,CPSR       讀取

MSR CPSR_c, R14     寫入

 

以上幾種模式存在的意義在於不一樣模式下特殊的幾個寄存器使用是有區別的。再svc模式下堆棧指針爲sp svc中斷模式下sp指針爲 sp irq等。一樣lr鏈接寄存器的內容也是有不一樣的含義。再不一樣模式切換中,lr寄存器保存的地址是由硬件完成,可是表示的是不一樣模式下的下一條指令,既返模式切換後的返回地址。

 

每一種模式對應不一樣的bank register。中文官方翻譯不詳。可是每一種模式要擁有本身獨立的寄存器組。而且每一種模式使用和可見寄存器的數量也是不相同的。

 

模式切換過程當中其實只針對spsr進行操做,而未涉及cpsr是的操做。這個過程之因此這樣主要是參考ARM cortex A8的TRM。其中這樣描述離開異常的狀況:

Typically the return instruction is an arithmetic orlogical operation with the S bit set to

1 and rd = r15, so the core copies the SPSR back to theCPSR.

 

也就是說離開異常,從異常狀況返回之後會自動把spsr的內同拷貝到cpsr中。因此在執行BL Lr指令以前使用的堆棧其實並位切換。

 

Linux中初始化:

1,  Svc模式的堆棧初始化:

堆棧的概念是給C 語言編譯之後的代碼使用,所以從head.S一直到C語言的執行,就是start_kernel。

__mmap_switched:

    @註釋 1:

    adr r3, __switch_data + 4

 

    ldmia   r3!, {r4, r5, r6, r7}

    cmp r4, r5              @ Copy datasegment if needed

1:  cmpne   r5, r6

    ldrne   fp, [r4], #4

    strne   fp, [r5], #4

    bne 1b

 

    mov fp, #0              @ Clear BSS(and zero fp)

1:  cmp r6, r7

    strcc   fp, [r6],#4

    bcc 1b

   

    @註釋 2:

    ldmia   r3, {r4, r5, r6, r7, sp}

    str r9, [r4]            @ Saveprocessor ID

    str r1, [r5]            @ Savemachine type

    str r2, [r6]            @ Saveatags pointer

    bic r4, r0, #CR_A           @ Clear'A' bit

    stmia   r7, {r0, r4}            @Save control register values

    @註釋 3:

    b   start_kernel

ENDPROC(__mmap_switched)

 

 

註釋1:

    __switch_data這是以個地址。Linker會安排這個地址具體的數值。打開Sysmap能夠發現這個數值爲:c0008123 t __switch_data

註釋 2:

    將r3所指的內容依次裝入{r4– r6,sp},這個時候sp指針就有了具體的數值了。

註釋 3:

    跳轉指令,指向C函數的start_kernel。這時候棧針開始起效。由於C語言編譯出來的代碼參數傳遞,調用變量保存等都使用sp指針。這個指針僅僅是給初始化代碼所使用。在進程的概念中還有進程堆棧的概念。這時候的sp具體指向的是描述進程結構的結構體task_info。

 

2,irq以及其餘模式的初始化:

    /*

     * setup stacks for re-entrant exceptionhandlers

     */

    __asm__ (

    "msr    cpsr_c, %1\n\t"

    "add    r14, %0, %2\n\t"

    "mov    sp, r14\n\t"

    "msr    cpsr_c, %3\n\t"

    "add    r14, %0, %4\n\t"

    "mov    sp, r14\n\t"

    "msr    cpsr_c, %5\n\t"

    "add    r14, %0, %6\n\t"

    "mov    sp, r14\n\t"

    "msr    cpsr_c, %7"

        :

        : "r" (stk),

          PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),

          "I" (offsetof(struct stack,irq[0])),

          PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),

          "I" (offsetof(struct stack,abt[0])),

          PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE),

          "I" (offsetof(struct stack,und[0])),

          PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE)

        : "r14");

    函數:cpu_init()文件:setup.c

    經過msr設置了cpsr寄存器。而後經過mov指令把具體的參數地址寫入sp寄存器。

其中offsetof(struct stack, irq[0])這個表達式表示的是偏移量。既是在結構體中的偏移量。

其實在這個函數中初始化的irq堆棧只有4 bytes x 3。這麼小的堆棧空間是否能夠知足中端的需求。答案是:能夠。在中端進入的函數中其實並無徹底使用irq模式下sp_irq指向的堆棧空間。在進入函數中立刻有利用了msr指令進行了模式切換,切換到了svc模式。而且放棄了irq的模式。從中端返回也是從svc模式返回,而非irq模式。

代碼:

vector_\name:

    .if \correction

    sub lr, lr, #\correction

    .endif

 

    @

    @ Save r0, lr_<exception>(parent PC) and spsr_<exception>

    @ (parent CPSR)

    @

@ 註釋 1:

stmia   sp, {r0, lr}        @ save r0,lr

    mrs lr, spsr

    str lr, [sp, #8]            @ save spsr

   

    @

    @ Prepare for SVC32 mode.  IRQs remain disabled.

    @

    mrs r0, cpsr

    eor r0, r0, #(\mode ^ SVC_MODE) 進入SVC模式

    msr spsr_cxsf, r0

 

    @

    @ the branch table mustimmediately follow this code

    @

    and lr, lr, #0x0f

    mov r0, sp

    ldr lr, [pc, lr, lsl #2]

    @註釋 2:

    movs    pc, lr         @ branch tohandler in SVC mode

    參照ARM的參考

ENDPROC(vector_\name)

註釋 1 :

保存irq模式下的sp和lr指針到前面初始的sp_irq中。記住只有4 bytes x 3大小的空間。在後面的代碼中還會看到str  lr, [sp, #8]保存了最後一個參數到sp_irq的空間中。這裏要注意:stmia  sp, {r0, lr}這條指令。沒有使用「!」號。這樣一來儘管指令執行後sp指針指向的地址不會自加。所以在正式切換到SVC模式以前sp_irq所指向的地址並無變化。這樣再次進入中斷模式時候,sp_irq不須要調整,能夠重複使用。

 

註釋 2:

    參照ARM 的芯片設計手冊能夠發現,離開異常,從異常狀況返回之後會自動把spsr的內同拷貝到cpsr中。因此在執行BL Lr指令以前使用的堆棧其實並位切換。

這樣一來儘管是中斷的模式進入系統,可是由中斷模式切換至SVC模式。在SVC模式中完成了中斷的後續相應和操做。

 

文章只是作了學習筆記已被後用,把這些思路羅列出來也給本身之後再回憶查找提供方便。

若是文章中有什麼不對的地方,還請高手指正。

謝謝

edwardlu

相關文章
相關標籤/搜索