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