1、理解整個中斷/系統調用流程的關鍵是kernel_entry和kernel_exit,也就是如何保存現場,並且恢復現場的。
我們先來看下armv8的寄存器,PLR(X30)無論是用戶態還是內核態都用這個寄存器來存儲程序的返回值。
sp_el0,sp_el1分別是有用戶態和內核態的堆棧。
ELR_EL1用於存儲,當在發生系統調用、異常、中斷時,當前程序的pc值(無論是用戶態還是內核態)。
SPSR_EL1用於存儲,當在發生系統調用、異常、中斷時,當前程序的PSTATE(無論是用戶態還是內核態)。
2、當發生中斷、異常、系統調用時,硬件會自動:
1)把當前程序的pc值放入ELR_EL1中
2)把當前狀態PSTATE存入SPSR_EL1中
3)根據發生在內核態還是用戶態,中斷還是異常,會自動跳轉到el1_sync,el1_irq,el0_sync,el0_irq
4)改變PSTATE,如果是用戶態發生中斷、異常、系統調用,此時已經進入內核態,堆棧是sp_el1。
3、kernel_entry
執行完kernel_entry的堆棧,ELR_EL1存放的是返回的PC值,SPSR_EL1存放的是返回的PSTATE。
如果是用戶態發生的中斷、異常、系統調用,則棧中保存都是用戶態的寄存器信息。
如果是內核態發生的中斷、異常,則棧中保存的內核態的寄存器信息。
4、kernel_exit
前面我們已經說過:
el1_sync,el1_irq調用的是kernel_entry 1,kernel_exit 1,也就是上面宏el為1。
el0_sync,el0_irq調用的是kernel_entry 0,kernel_exit 0,也就是上面宏el為0。
我們可以看到 .macro kernel_exit, el, ret = 0,還有一個參數ret,只有在el0_sync處理系統調用時會被置成1。
發生中斷、異常、系統調用前是用戶態,則返回用戶態的寄存器(pc,lr,sp_el0,pstate)。注意還要把內核態的棧平衡了:ldr lr, [sp], #S_FRAME_SIZE - S_LR // 恢復lr,恢復內核sp_el1
發生中斷、異常、系統調用前是內核態,則返回內核態的寄存器(pc,lr,sp_el1,pstate)。
如果處理系統調用x0存放的是系統的調用的返回值,所以不需要從堆棧中恢復。
系統調用實現
.macro kernel_ventry, el, label, regsize = 64 .align = 7 // 地址對齊要求 sub sp, sp, #S_FRAME_SIZE //堆棧指針處理 b el\()\el\()_\label //跳轉 .endm
系統調用是從“kernel_ventry 0, sync”進入,即el=0,label=sync。因此最終跳轉進入el0_sync匯編
其中" \()"是匯編符號連接,\el和\label是匯編宏的參數引用。
匯編宏el0_sync
匯編宏el0_sync主要分為兩部分:第一部分實現從用戶空間到內核空間的上下文切換, kernel_entry 0;第二部是根據異常症狀寄存器esr_el1判斷異常原因,然后再進入具體處理函數。系統調用是用戶態執行SVC指令導致的,因此要進入el0_svc處理函數。
/* * 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 ccmp x24, #ESR_ELx_EC_WFx, #4, ne 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
案例
[root@centos7 arm]# uname -a Linux centos7 4.14.0-115.el7a.0.1.aarch64 #1 SMP Sun Nov 25 20:54:21 UTC 2018 aarch64 aarch64 aarch64 GNU/Linux [root@centos7 arm]#
svc指令
//靜態編譯
[root@centos7 arm]# gcc test.c -o test --static
//反匯編
objdump -D test > test.txt
20634 0000000000413d8c <__libc_open>: 20635 413d8c: a9af7bfd stp x29, x30, [sp,#-272]! 20636 413d90: 910003fd mov x29, sp 20637 413d94: f90073a2 str x2, [x29,#224] 20638 413d98: a90153f3 stp x19, x20, [sp,#16] 20639 413d9c: 2a0103e2 mov w2, w1 20640 413da0: f90077a3 str x3, [x29,#232] 20641 413da4: f9007ba4 str x4, [x29,#240] 20642 413da8: f9007fa5 str x5, [x29,#248] 20643 413dac: f90083a6 str x6, [x29,#256] 20644 413db0: f90087a7 str x7, [x29,#264] 20645 413db4: 3d801ba0 str q0, [x29,#96] 20646 413db8: 3d801fa1 str q1, [x29,#112] 20647 413dbc: 3d8023a2 str q2, [x29,#128] 20648 413dc0: 3d8027a3 str q3, [x29,#144] 20649 413dc4: 3d802ba4 str q4, [x29,#160] 20650 413dc8: 3d802fa5 str q5, [x29,#176] 20651 413dcc: 3d8033a6 str q6, [x29,#192] 20652 413dd0: 3d8037a7 str q7, [x29,#208] 20653 413dd4: aa0003e1 mov x1, x0 20654 413dd8: 37300262 tbnz w2, #6, 413e24 <__libc_open+0x98> 20655 413ddc: 52880003 mov w3, #0x4000 // #16384 20656 413de0: 72a00803 movk w3, #0x40, lsl #16 20657 413de4: 0a030043 and w3, w2, w3 20658 413de8: 7150107f cmp w3, #0x404, lsl #12 20659 413dec: d2800003 mov x3, #0x0 // #0 20660 413df0: 540001a0 b.eq 413e24 <__libc_open+0x98> 20661 413df4: f0000460 adrp x0, 4a2000 <initial+0x248> 20662 413df8: b94f0800 ldr w0, [x0,#3848]
20663 413dfc: 35000380 cbnz w0, 413e6c <__libc_open+0xe0> 20664 413e00: 92800c60 mov x0, #0xffffffffffffff9c // #-100 20665 413e04: 93407c42 sxtw x2, w2 20666 413e08: d2800708 mov x8, #0x38 // #56 20667 413e0c: d4000001 svc #0x0 20668 413e10: b140041f cmn x0, #0x1, lsl #12 20669 413e14: 540001e8 b.hi 413e50 <__libc_open+0xc4> 20670 413e18: a94153f3 ldp x19, x20, [sp,#16] 20671 413e1c: a8d17bfd ldp x29, x30, [sp],#272 20672 413e20: d65f03c0 ret 20673 413e24: 910383a0 add x0, x29, #0xe0 20674 413e28: f9002ba0 str x0, [x29,#80] 20675 413e2c: 128005e0 mov w0, #0xffffffd0 // #-48 20676 413e30: 910443a4 add x4, x29, #0x110 20677 413e34: b9005ba0 str w0, [x29,#88] 20678 413e38: 12800fe0 mov w0, #0xffffff80 // #-128 20679 413e3c: b980e3a3 ldrsw x3, [x29,#224] 20680 413e40: f90023a4 str x4, [x29,#64] 20681 413e44: f90027a4 str x4, [x29,#72] 20682 413e48: b9005fa0 str w0, [x29,#92] 20683 413e4c: 17ffffea b 413df4 <__libc_open+0x68> 20684 413e50: 90000462 adrp x2, 49f000 <__FRAME_END__+0x10aa8> 20685 413e54: f947c842 ldr x2, [x2,#3984] 20686 413e58: 4b0003e0 neg w0, w0 20687 413e5c: d53bd041 mrs x1, tpidr_el0 20688 413e60: b8226820 str w0, [x1,x2] 20689 413e64: 92800000 mov x0, #0xffffffffffffffff // #-1 20690 413e68: 17ffffec b 413e18 <__libc_open+0x8c> 20691 413e6c: f9001ba1 str x1, [x29,#48]
可以看到將寄存器x8設置為系統調用號0x38 (openat),
然后調用了svc進入異常處理。
進入異常模式后,內核根據異常類型(同步異常)及當前所處的ELx, 調用相應的異常處理函數,這里是el0_sync。
進入異常模式后,內核根據異常類型(同步異常)及當前所處的ELx, 調用相應的異常處理函數,這里是el0_sync。
arch/arm64/kernel/entry.S
667,668行:通過讀取esr_el1得到產生異常的原因,保存到寄存器x24
669行:判斷x24中保存的異常原因是否為svc
670行:上一行如果判斷相等,調用el0_svc
914行:讀入syscall table的指針
915行:將系統調用號(w8)保存到wscno中,上面的講解中libc庫將系統調用號寫入了x8寄存器
928行:以系統調用號為索引,得到syscall table表中相應的函數地址,這里就是sys_openat
929行:調用sys_openat
更詳細分析見:
https://cloud.tencent.com/developer/article/1413292