字元裝置驅動-------Linux異常處理體系結構
裸機中斷流程
- 外部觸發
- CPU 發生中斷, 強制的跳到異常向量處
- 跳轉到具體函式
- 儲存被中斷處的現場(各種暫存器的值)
- 執行中斷處理函式,處理具體任務
- 恢復被中斷的現場
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 externchar __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 異常處理體系結構: