當發生系統調用、產生異常,外設發生中斷等事件時,會發生用戶棧和內核棧之間的切換,本文從系統調用角度分析用戶棧與內核棧的切換。
系統調用的演變
x86 的系統調用經歷了 int / iret 到 sysenter / sysexit 再到 syscall / sysret 實現方式的轉變,關于具體的演化和區別、系統調用的其他細節等將在以后的系統調用專欄里分析。本文從系統調用最原始的int 0x80開始分析用戶棧與內核棧的切換,重點看系統調用過程用戶棧與內核棧切換的過程中的一些細節。
系統調用-分析從用戶棧切換內核棧
內核SYSCALL 入口代碼在entry_64.S中:
//arch/x86/entry/entry_64.S
ENTRY(entry_SYSCALL_64)
UNWIND_HINT_EMPTY
/* Interrupts are off on entry. */
swapgs
// 將用戶棧偏移保存到 per-cpu 變量 rsp_scratch 中
movq %rsp, PER_CPU_VAR(rsp_scratch)
// 切換到進程內核棧
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
/* 在棧中倒序構建 struct pt_regs */
pushq $__USER_DS /* pt_regs- >ss */
pushq PER_CPU_VAR(rsp_scratch) /* pt_regs- >sp */
pushq %r11 /* pt_regs- >flags */
pushq $__USER_CS /* pt_regs- >cs */
pushq %rcx /* pt_regs- >ip */
GLOBAL(entry_SYSCALL_64_after_hwframe)
//rax 保存著系統調用號
pushq %rax /* pt_regs- >orig_ax */
PUSH_AND_CLEAR_REGS rax=$-ENOSYS
TRACE_IRQS_OFF
/* 保存參數到寄存器,調用do_syscall_64函數 */
movq %rax, %rdi
movq %rsp, %rsi
call do_syscall_64 /* returns with IRQs disabled */
上面的匯編指令中先將當前用戶棧(用戶空間棧頂)記錄在CPU獨占變量區域里(PER_CPU變量),如下所示:
movq %rsp, PER_CPU_VAR(rsp_scratch)
然后將CPU獨占區域里記錄的內核棧頂放入rsp/esp寄存器
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
就是這么簡潔,**上面兩句匯編:就 將用戶棧頂保存在了當前CPU的rsp_scratch這樣一個PER_CPU變量里,完成了用戶棧的保存 ,然后 將當前內核棧的地址存放到當前棧指針寄存器中 ,**那么此時棧寄存器指向的就是內核棧的棧頂,由此優雅、完美地完成了用戶棧到內核棧的切換!
接下來所有的壓棧操作都是在內核棧里操作了,依次將用戶空間寄存器壓棧,此時也是往內核棧push的, 內核使用struct pt_regs初始化內核棧,也就是通過push保存寄存器的值 (將用戶棧信息:用戶調用的系統調用號、參數、代碼段地址、數據段地址等以struct pt_regs形式壓入棧) ,形成一個pt_regs結構 ,如下圖(源于上篇文章中的分析):
在棧中順序固定且倒序壓棧(在x86_64中,內核棧rbx rbp r12 r13 r14 r15不是必須保存的項(為了訪問不越界相應空間必須保留),根據需要保存,linux后續版本采取都保存方式),其中rax保存系統調用號
//rax 保存著系統調用號
pushq %rax /* pt_regs- >orig_ax */
-
內核
+關注
關注
3文章
1372瀏覽量
40278 -
Linux
+關注
關注
87文章
11292瀏覽量
209328
發布評論請先 登錄
相關推薦
評論