1. 程式人生 > >字元裝置驅動-------Linux異常處理體系結構

字元裝置驅動-------Linux異常處理體系結構

 

裸機中斷流程

  1. 外部觸發
  2. CPU 發生中斷, 強制的跳到異常向量處
  3. 跳轉到具體函式
    1. 儲存被中斷處的現場(各種暫存器的值)
    2. 執行中斷處理函式,處理具體任務
    3. 恢復被中斷的現場

Linux處理異常流程

  異常發生時,會去異常向量表找到入口地址,(這算異常發生之後跳轉到第一個處理分支),進入異常模式,保護部分現場,強制進入SVC管理模式,根據異常發生前的工作模式,找到異常處理的第二級分支,在該模式下面接過異常模式堆疊中的資訊,接著儲存異常發生時異常模式還未儲存的資訊,準備好處理完畢返回處理程式的地址,呼叫異常處理函式,恢復現場。


 處理異常流程中的彙編處理流程:

  

 

Linux核心對異常的初始化設定

  在核心啟動時,核心會在start_kernel函式中呼叫trap_init,init_IRQ兩個函式來設定異常的處理函式

1. trap_init:(arch/arm/kernel/traps.c中定義)

  

 1 void __init trap_init(void)
 2 {
 3     unsigned long vectors = CONFIG_VECTORS_BASE;    /*CONFIG_VECTORS_BASE = 0xffff0000*/
 4     extern
char __stubs_start[], __stubs_end[]; 5 extern char __vectors_start[], __vectors_end[]; 6 extern char __kuser_helper_start[], __kuser_helper_end[]; 7 int kuser_sz = __kuser_helper_end - __kuser_helper_start; 8 9 /* 10 * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
11 * into the vector page, mapped at 0xffff0000, and ensure these 12 * are visible to the instruction stream. 13 */ 14 memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start); //void *memcpy(void *dest, const void *src, size_t count) 15 memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start); 16 memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz); 17 18 /* 19 * Copy signal return handlers into the vector page, and 20 * set sigreturn to be a pointer to these. 21 */ 22 memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes, 23 sizeof(sigreturn_codes)); 24 25 flush_icache_range(vectors, vectors + PAGE_SIZE); 26 modify_domain(DOMAIN_USER, DOMAIN_CLIENT); 27 }

 

  作用:用來設定各種異常的處理向量,即將異常向量表複製到0xffff0000處。Vectors = 0xffff0000, 地址_vectors_start ~~ __vectors_end之間的程式碼就是異常向量。

  所謂的異常向量就是被安放在固定位置的程式碼,只是一些跳轉指令。發生異常,CPU自動執行這些指令,跳轉執行更復雜的程式碼,比如:儲存被中斷程式的執行環境,呼叫異常處理函式,恢復被中斷程式的執行環境並重新執行。這些“更復雜的程式碼”在地址 __stubs_start, ~ __stubs_end 之間,第15行,將他們複製到vectors + 0x200 處。

  到這裡Linux核心異常向量設定的工作就算是完成了。可是想想:設定完這些異常向量之後,異常發生了,CPU是怎麼一個處理過程???接著往下分析

  從異常向量表來開始入手,__vectors_start和__vectors_end在arch/arm/kernel/entry-armv.S檔案中有定義。他們就是核心異常向量表的起始和結束地址。

 1     .equ    stubs_offset, __vectors_start + 0x200 - __stubs_start
 2 
 3     .globl    __vectors_start
 4 __vectors_start:
 5     swi    SYS_ERROR0           //復位時,CPU將執行這條指令
 6     b    vector_und + stubs_offset   //未定義異常時,CPU執行這條指令
 7     ldr    pc, .LCvswi + stubs_offset //swi異常
 8     b    vector_pabt + stubs_offset   //指令預取中止
 9     b    vector_dabt + stubs_offset   //資料訪問中止
10     b    vector_addrexcptn + stubs_offset
11     b    vector_irq + stubs_offset    //IRQ異常
12     b    vector_fiq + stubs_offset    //FIQ異常
13 
14     .globl    __vectors_end
15 __vectors_end:

 

  以第一個調轉指令“b  vector_und + stubs_offset”的分析為例,vector_und是個彙編巨集定義,由vector_stub 及後面的引數定義,和c語言裡面的巨集定義特點是一樣的,編譯時巨集在呼叫處的展開,就是用巨集定義的實體部分去完全取代巨集名稱,可以直接在該處執行,以下是彙編巨集定義規則:

       .macro    MACRO_NAME   PARA1   PARA2   ......

      ......實體---內容......

      .endm

在檔案中,找到了 vector_stub 這個巨集(以下第二部分程式碼),它根據後面的引數“ und, UND_MODE”定義了以“vector_und”為標號的一段程式碼,如下

 1 /*
 2  * Undef instr entry dispatcher
 3  * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 4  */
 5     vector_stub    und, UND_MODE
 6 
 7     .long    __und_usr            @  0 (USR_26 / USR_32)
 8     .long    __und_invalid            @  1 (FIQ_26 / FIQ_32)
 9     .long    __und_invalid            @  2 (IRQ_26 / IRQ_32)
10     .long    __und_svc            @  3 (SVC_26 / SVC_32)
11     .long    __und_invalid            @  4
12     .long    __und_invalid            @  5
13     .long    __und_invalid            @  6
14     .long    __und_invalid            @  7
15     .long    __und_invalid            @  8
16     .long    __und_invalid            @  9
17     .long    __und_invalid            @  a
18     .long    __und_invalid            @  b
19     .long    __und_invalid            @  c
20     .long    __und_invalid            @  d
21     .long    __und_invalid            @  e
22     .long    __und_invalid            @  f
23 
24     .align    5

  vector_ 巨集定義

 1 .macro    vector_stub, name, mode, correction=0
 2     .align    5  @將異常入口強制進行2^5位元組對齊,即一個cache line大小對齊,出於效能考慮
 3 
 4 vector_\name:    //und, UND_MODE ......
 5     .if \correction  @correction=0 所以分支無效
 6     sub    lr, lr, #\correction
 7     .endif
 8 
 9     @
10     @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
11     @ (parent CPSR)
12     @
13     stmia    sp, {r0, lr}        @ save r0, lr
14     mrs    lr, spsr
15     str    lr, [sp, #8]        @ save spsr
16 
17     @
18     @ Prepare for SVC32 mode.  IRQs remain disabled.
19     @
20     mrs    r0, cpsr
21     eor    r0, r0, #(\mode ^ SVC_MODE)
22     msr    spsr_cxsf, r0
23 
24     @
25     @ the branch table must immediately follow this code
26     @
27     and    lr, lr, #0x0f
28     mov    r0, sp
29     ldr    lr, [pc, lr, lsl #2]
30     movs    pc, lr            @ branch to handler in SVC mode
31     .endm

  

  以巨集“vector_stub  und,   UND_MODE”為例代入,將其展開為:

 1 vector_und:   
 2     @
 3     @ 此時已進入UND_MOD,lr=上一個模式被打斷時的PC值,下面三條指令是保護上個模式的現場
 4     @
 5     stmia    sp, {r0, lr}        @ save r0, lr
 6     mrs    lr, spsr              @ 準備儲存上個模式的cpsr值,因為他被放到了UND_MODE的spsr中
 7     str    lr, [sp, #8]          @ save spsr to stack
 8     @
 9     @ Prepare for SVC32 mode.  IRQs remain disabled. 注意前面的“Prepare”,這裡還不是真正切換到SVC,只是準備
10     @
11     mrs    r0, cpsr                     @ r0=0x1b  (UND_MODE)
12     eor    r0, r0, #(\mode ^ SVC_MODE)  @ 邏輯異或指令
13     msr    spsr_cxsf, r0                @ cxsf是spsr暫存器的控制域(C)、擴充套件域(X)、狀態域(S)、標誌域(F),注意這裡的spsr是UND管理模式的
14     @
15     @ the branch table must immediately follow this code 下一級跳轉表必須要緊跟在這一段程式碼之後(這一點很重要)
16     @
17     and    lr, lr, #0x0f     @ 執行這條指令之前:lr = 上個模式的cpsr值,現在取出其低四位--模式控制位的[4:0],關鍵點又來了:檢視2440晶片手冊可以知道,這低4位二進位制值為十進位制數值的 0-->User_Mode; 1-->Fiq_Mode; 
                    //2-->Irq_Mode; 3-->SVC_Mode; 7-->Abort_Mode; 11-->UND_Mode,明白了這些下面的處理就會恍然大悟,原來找到那些異常處理分支是依賴這4位的值來實現的
18 mov r0, sp @ 將SP值儲存到R0是為了之後切換到SVC模式時將這個模式下堆疊中的資訊轉而儲存到SVC模式下的堆疊中 19 ldr lr, [pc, lr, lsl #2] @ LDR的稀有用法:將pc+lr*4的計算結果重新儲存到lr中,我們知道pc是指向當前指令的下兩條指令處的地址的,也就是指向了“.long __und_usr” 20 movs pc, lr @ branch to handler in SVC mode 前方高能!關鍵的地方來了!在跳轉到第二級分支的同時CPU的工作模式從UND_MODE強制切換到SVC_MODE,這是由於MOVS指令在賦值的同時會將spsr的值賦給cpsr 21 ENDPROC(vector_und) 22 .long __und_usr    @ 0 (USR_26 / USR_32)執行使用者模式下觸發未定義指令異常 23 .long __und_invalid @ 1 (FIQ_26 / FIQ_32) 24 .long __und_invalid @ 2 (IRQ_26 / IRQ_32) 25 .long __und_svc    @ 3 (SVC_26 / SVC_32)執行使用者模式下觸發未定義指令異常 26 .long __und_invalid @ 4 其他模式下面不能發生未定義指令異常,否則都使用__und_invalid分支處理這種異常 27 .long __und_invalid @ 5 28 .long __und_invalid @ 6 29 .long __und_invalid @ 7 30 .long __und_invalid @ 8 31 .long __und_invalid @ 9 32 .long __und_invalid @ a 33 .long __und_invalid @ b 34 .long __und_invalid @ c 35 .long __und_invalid @ d 36 .long __und_invalid @ e 37 .long __und_invalid @ f

程式碼註釋出自:https://blog.csdn.net/clb1609158506/article/details/44348767

小結:vector_stub的巨集功能:計算處理完異常後的返回地址,儲存一些暫存器(r0,lr, spsr),然後進入管理模式,最後根據被中斷的工作模式呼叫第22行-37行中的某個跳轉分支。發生異常時,CPU根據異常型別進入某個工作模式,但是很快 vector_stub又會強制CPU進入管理模式,在管理模式下進行後續處理。

  eg: 執行到“movs pc, lr”這一句,找到了branch table中的一項,現在我們繼續往下分析,假設進入UND_MODE之前是User模式,那麼接下來會到__und_usr分支去繼續執行
__und_usr標號也是在該檔案中定義,

分支跳轉 __und_usr ------> b   __und_usr_unknown -----> b    do_undefinstr ----> 最終呼叫C函式進行復雜的處理 在arch/arm/kernel/traps.c中

 2.Init_IRQ函式分析

  中斷也是一種異常,之所以單獨列出來,是因為中斷的處理與具體開發板密切相關,除了一些必須、共用的中斷(比如系統時鐘中斷,片內外設UART中斷)之外,必須由驅動開發者提供處理函式。核心提煉出中斷處理的共性,搭建了一個極易擴充的中斷處理體系。

 Init_IRQ函式(arch/arm/kernel/irq.c 中定義)被用來初始化中斷的處理框架,設定各種中斷的預設處理函式。當發生中斷時,中斷總入口函式 asm_do_IRQ 就可以呼叫這些函式作進一步處理。

 

 Linux 異常處理體系結構: