上一期中我們介紹了 linux 4.19 核心的異常向量表,這一期我們將介紹 linux 4.19 核心在 arm64 處理器上的異常處理。
當異常發生時,處理器需要呼叫異常處理程式來處理異常,該呼叫過程可以粗略地分為儲存處理器當前狀態、呼叫異常處理程式和恢復異常發生前的處理器狀態三步,具體說來核心中異常處理的流程是[1]:
儲存處理器當前狀態。將當預處理器狀態 pstate 儲存在 spsr_el1 暫存器中,將返回位址儲存在 elr_el1 暫存器中,這兩個暫存器中的變數會在返回時被 eret 指令用於恢復處理器狀態。通過設定處理器狀態中的除錯掩碼位d、系統錯誤掩碼位a、中斷掩碼位i和快速中斷掩碼位f禁止除錯異常、系統錯誤異常、外部中斷和快速中斷。將發生錯誤的原因儲存在 esr_el1 暫存器中,將同步異常的錯誤位址儲存在 far_el1 暫存器中。如果處理器處於異常級別 el0 則將異常級別提公升到 el1。根據異常向量表基位址、生成異常的異常級別和異常型別計算出異常向量的位置,通過異常向量跳轉到異常處理程式的入口。異常向量表的基位址是儲存在 vbar_el1 暫存器中的。
呼叫異常處理程式。以異常級別 el0 下64位應用程式發生的同步異常為例,其異常處理程式入口為 sync,異常向量表通過 kernel_ventry 巨集跳轉到了該入口(同步異常和非同步異常的概念見第27期)。kernel_ventry 在跳轉的時候會為將異常級別加到跳轉入口之前,所以這時實際跳轉到的入口是 el0_sync,其彙編**在 openeuler/kernel/blob/kernel-4.19/arch/arm64/kernel/entry.s 檔案中可以找到:
/*
* el0 mode handlers.
*/.align 6
el0_sync:
kernel_entry 0
mrs x25, esr_el1 // read the syndrome register
lsr x24, x25, #esr_elx_ec_shift // exception class
cmp x24, #esr_elx_ec_svc64 // svc in 64-bit state
b.eq el0_svc
cmp x24, #esr_elx_ec_dabt_low // data abort in el0
b.eq el0_da
cmp x24, #esr_elx_ec_iabt_low // instruction abort in el0
b.eq el0_ia
cmp x24, #esr_elx_ec_fp_asimd // fp/asimd access
b.eq el0_fpsimd_acc
cmp x24, #esr_elx_ec_sve // sve access
b.eq el0_sve_acc
cmp x24, #esr_elx_ec_fp_exc64 // fp/asimd exception
b.eq el0_fpsimd_exc
cmp x24, #esr_elx_ec_sys64 // configurable trap
b.eq el0_sys
cmp x24, #esr_elx_ec_sp_align // stack alignment exception
b.eq el0_sp
cmp x24, #esr_elx_ec_pc_align // pc alignment exception
b.eq el0_pc
cmp x24, #esr_elx_ec_unknown // unknown exception in el0
b.eq el0_undef
cmp x24, #esr_elx_ec_breakpt_low // debug exception in el0
b.ge el0_dbg
b el0_inv
這段**首先通過 kernel_entry 巨集儲存了異常處理前的通用暫存器狀態,然後從 esr_el1 暫存器中讀取了錯誤的原因,移位之後和各種異常的標誌值相比較,若與某一異常的標誌值相同則跳轉到該異常的處理程式。kernel_entry 巨集會將通用暫存器的值儲存在當前程序的核心棧中。其彙編**比較複雜,可以在同乙個檔案中找到:
.macro kernel_entry, el, regsize =64.
if \regsize ==
32mov w0, w0 // zero upper 32 bits of x0
.endif
stp x0, x1,
[sp, #16*0
]stp x2, x3,
[sp, #16*1
]stp x4, x5,
[sp, #16*2
]stp x6, x7,
[sp, #16*3
]stp x8, x9,
[sp, #16*4
]stp x10, x11,
[sp, #16*5
]stp x12, x13,
[sp, #16*6
]stp x14, x15,
[sp, #16*7
]stp x16, x17,
[sp, #16*8
]stp x18, x19,
[sp, #16*9
]stp x20, x21,
[sp, #16*10
]stp x22, x23,
[sp, #16*11
]stp x24, x25,
[sp, #16*12
]stp x26, x27,
[sp, #16*13
]stp x28, x29,
[sp, #16*14
]……
跳轉到處理程式後將會進行異常處理,以異常級別el0下的資料中止異常為例,其處理程式的彙編**在同乙個檔案中可以找到:
el0_da:
/** data abort handling
*/mrs x26, far_el1
enable_daif
ct_user_exit
clear_address_tag x0, x26
mov x1, x25
mov x2, sp
bl do_mem_abort
b ret_to_user
在這段**中,處理器從 far_el1 暫存器中讀取了資料中止發生的虛擬位址,然後呼叫了c程式處理函式 do_mem_abort 並通過暫存器 x0 和 x1 傳遞了兩個引數,其中 x0 中儲存了錯誤發生的虛擬位址,x1 中儲存了錯誤發生的原因(el0_sync 巨集中從 elr_el1 暫存器讀到了 x25 中)。
恢復處理器狀態,繼續執行程式。異常處理程式執行完之後跳轉到了ret_to_user,其彙編**在同乙個檔案中可以找到:
ret_to_user:
disable_daif
gic_prio_kentry_setup tmp=x3
ldr x1,
[tsk, #tsk_ti_flags]
and x2, x1, #_tif_work_mask
cbnz x2, work_pending
finish_ret_to_user:
enable_step_tsk x1, x2
#ifdef config_gcc_plugin_stackleak
bl stackleak_erase
#endif
kernel_exit 0
這段**在最後呼叫了kernel_exit巨集,其作用是將之前kernel_entry中儲存的通用暫存器恢復,並使用eret指令返回異常發生前的程式執行位置,該位置的位址是被事先儲存在elr_el1暫存器中的,其取值情況為[1]:
kernel_exit 巨集的彙編**可以在同乙個檔案中找到:
……
msr elr_el1, x21 // set up the return data
msr spsr_el1, x22
ldp x0, x1,
[sp, #16*0
]ldp x2, x3,
[sp, #16*1
]ldp x4, x5,
[sp, #16*2
]ldp x6, x7,
[sp, #16*3
]ldp x8, x9,
[sp, #16*4
]ldp x10, x11,
[sp, #16*5
]ldp x12, x13,
[sp, #16*6
]ldp x14, x15,
[sp, #16*7
]ldp x16, x17,
[sp, #16*8
]ldp x18, x19,
[sp, #16*9
]ldp x20, x21,
[sp, #16*10
]ldp x22, x23,
[sp, #16*11
]ldp x24, x25,
[sp, #16*12
]ldp x26, x27,
[sp, #16*13
]ldp x28, x29,
[sp, #16*14
]ldr lr,
[sp, #s_lr]
add sp, sp, #s_frame_size // restore sp
……
本期我們考察了 linux 4.19 核心的異常處理,下一期我們將介紹 linux 核心中的中斷處理流程。
參考文獻
[1] 《linux核心深度解析》,余華兵著,2019
python第三十課 異常 異常物件傳遞過程
演示異常物件傳遞的過程 往上 拋 並將其解決def func1 print func1.print 10 0 deffunc2 print func2.try func1 except exception as e print e def func3 print func3.func2 try fu...
Leetcode第三十題 串聯所有單詞的子串
題目 給定乙個字串 s 和一些長度相同的單詞 words。找出 s 中恰好可以由 words 中所有單詞串聯形成的子串的起始位置。注意子串要與 words 中的單詞完全匹配,中間不能有其他字元,但不需要考慮 words 中單詞串聯的順序。示例 1 輸入 s barfoothefoobarman wo...
第三十次總結 程序和執行緒的關係
粘包的解決 1,延遲,阻塞 發訊息1 time.sleep 0.1 input,recv recvfrom 發訊息2 2,改變我們的傳送流程 客戶端 服務端 先傳送資料的長度 接收資料長度,存為乙個變數,cl 傳送資料的內容 連線套接字.recv cl struct模組 ret struct.pac...