Linux系统调用过程中user栈的保存与恢复

前言

Linux应用程序中使用的open/close等函数属于glibc库提供的系统函数,调用这些函数最后都需要进入内核。所以完整的open实现可分为两部分,一部分在USER space的glibc中实现;另一部分在内核中实现。这里只分析进入内核时USER栈的保存与恢复。系统调用的基本逻辑可以参考:[https://www.cnblogs.com/pengdonglin137/p/3878316.html]

软硬件基础

ARM由好几种处理器模式。系统调用是通过swi指令来触发arm产生异常进入SVC模式的。借用网上的图,各模式下寄存器access情况如下。

因为用到了USER和SVC模式,所以重点关注这两种模式下的寄存器。

user和svc模式下寄存器的差异

1.USER和SVC共用r0-r12, PC, CPSR寄存器
2.USER和SVC有自己独立的r13(sp) r14(lr)
3.SVC比USER多一个SPSR寄存器。

lr寄存器用法

大多数人都知道lr用来存放返回地址。用mov pc, lr就可以返回到调用点。但是异常发生时,lr也会被设置。当异常中断发生时,该异常模式下的r14被设置成了该异常模式将要返回的的地址!
也就是说使用swi指令触发的异常,lr_swi中存放的是USER模式下的下一条准备执行的指令。理解这点就能理解为什么 ldr pc,lr_svc能返回到USER模式。

spsr寄存器用法

CPSR是程序状态寄存器,SPSR则是备份程序状态寄存器。顾名思义,SPSR是用来备份CPSR的。当SWI指令触发异常时,ARM core会将当前user状态的CPSR的状态备份到SPSR_svc中。当系统调用完毕准备从kernel返回时,只需将SPSR复制到CPSR中即能恢复异常触发时CPSR的状态,也就恢复user 模式现场的CPSR寄存器。

USER栈的保存

因为跳过了USER space的实现,所以这里假设USER space通过swi + nr号触发了异常。

1218 __vectors_start:                                                                                                                                                                                              
1219     W(b)    vector_rst             
1220     W(b)    vector_und             
1221     W(ldr)  pc, __vectors_start + 0x1000
1222     W(b)    vector_pabt            
1223     W(b)    vector_dabt            
1224     W(b)    vector_addrexcptn
1225     W(b)    vector_irq             
1226     W(b)    vector_fiq

swi异常会进入1221行,执行__vectors_start + 0x1000处代码。怪异的地址,直接看System.map查看这个地方链接时存放的是啥。

    1 00000000 t __vectors_start                                                                                                                                                                                   
    2 00000024 A cpu_ca8_suspend_size
    3 00000024 A cpu_v7_suspend_size
    4 0000002c A cpu_ca9mp_suspend_size
    5 00001000 t __stubs_start
    6 00001004 t vector_rst
    7 00001020 t vector_irq
    8 000010a0 t vector_dabt
    9 00001120 t vector_pabt
   10 000011a0 t vector_und
   11 00001220 t vector_addrexcptn
   12 00001240 t vector_fiq
   13 00001240 T vector_fiq_offset
   14 80004000 A swapper_pg_dir
   15 80008000 T _text
   16 80008000 T stext

可以看到__vectors_start+0x1000处存放的是__stubs_start。省去中间步骤直接到了vector_swi。

120 ENTRY(vector_swi)
121 #ifdef CONFIG_CPU_V7M
122     v7m_exception_entry
123 #else
124     sub sp, sp, #S_FRAME_SIZE
125     stmia   sp, {r0 - r12}          @ Calling r0 - r12
126  ARM(   add r8, sp, #S_PC       )
127  ARM(   stmdb   r8, {sp, lr}^       )   @ Calling sp, lr
128  THUMB( mov r8, sp          )
129  THUMB( store_user_sp_lr r8, r10, S_SP  )   @ calling sp, lr
130     mrs r8, spsr            @ called from non-FIQ mode, so ok.
131     str lr, [sp, #S_PC]         @ Save calling PC
132     str r8, [sp, #S_PSR]        @ Save CPSR
133     str r0, [sp, #S_OLD_R0]     @ Save OLD_R0
134 #endif

这里可以看到,现开辟了S_FRAME_SIZE(72Byte=18个32bit register)。然后先保存了r0到r12。第127行因为由^,所以保存了USER模式下的sp和lr。接着保存了lr_svc,根据前面的解释,这个lr_svc就是USER模式的下一条pc。然后是保存SPSR_svc,这个SPSR_svc等于USER模式的CPSR。最后保存r0.

内核栈最终的形状就如上图所示。

USER栈的恢复

在USER栈保存之后,内核便可以开始执行系统调用的相关操作了。
当系统调用完成准备返回USER空间时,内核是怎么操作的呢?先看一个正常的还是调用过程。

  1 #include <unistd.h>                                                                                                                                                                                            
  2 #include <stdio.h>
  3  
  4 void func1() {
  5     printf("In func1.\n");
  6 }  
  7  
  8 int main ()
  9 {
 10     func1();
 11     
 12     return 0;
 13 }  

对应的反汇编如下:

598 000103c8 <func1>:
 599    103c8:   b580        push    {r7, lr}
 600    103ca:   af00        add r7, sp, #0
 601    103cc:   f240 403c   movw    r0, #1084   ; 0x43c
 602    103d0:   f2c0 0001   movt    r0, #1
 603    103d4:   f7ff ef70   blx 102b8 <puts@plt>
 604    103d8:   bf00        nop
 605    103da:   bd80        pop {r7, pc}
 606            
 607 000103dc <main>:                                                                                                                                                                                              
 608    103dc:   b580        push    {r7, lr}
 609    103de:   af00        add r7, sp, #0
 610    103e0:   f7ff fff2   bl  103c8 <func1>
 611    103e4:   2300        movs    r3, #0
 612    103e6:   4618        mov r0, r3
 613    103e8:   bd80        pop {r7, pc}

可以看到在进入子函数的时候会将lr push到栈中,在子函数返回的时候通过pop将栈中lr寄存器的内容送到了pc寄存器,完成从子函数返回到main的操作。
同样的,在系统调用完成,返回用户空间的过程也通过类似的操作来实现。在vector_swi中可以看到跳到系统调用对应的内核实现函数之前,内核会把lr寄存器设置为ret_fast_syscall。所以内核完成系统调用后,会跳转到这个函数。

201     adr lr, BSYM(ret_fast_syscall)  @ return address
202     ldrcc   pc, [tbl, scno, lsl #2]     @ call sys_* routine           

看这个ret_fast_syscall的实现。

 32 ret_fast_syscall:
 33  UNWIND(.fnstart    )
 34  UNWIND(.cantunwind )
 35     disable_irq             @ disable interrupts
 36     ldr r1, [tsk, #TI_FLAGS]        @ re-check for syscall tracing
 37     tst r1, #_TIF_SYSCALL_WORK
 38     bne __sys_trace_return
 39     tst r1, #_TIF_WORK_MASK
 40     bne fast_work_pending
 41     asm_trace_hardirqs_on
 42    
 43     /* perform architecture specific actions before user return */
 44     arch_ret_to_user r1, lr
 45     ct_user_enter
 46    
 47     restore_user_regs fast = 1, offset = S_OFF
 48  UNWIND(.fnend      )   ```
这个函数中有和调度相关的代码,也有当前平台压根没实现的函数,比如arch_ret_to_user就是空的。和恢复USER现场相关只有最后一个restore_user_reegs函数。
```C
255     .macro  restore_user_regs, fast = 0, offset = 0                                                                                                                                                            
256     mov r2, sp
257     ldr r1, [r2, #\offset + S_PSR]  @ get calling cpsr
258     ldr lr, [r2, #\offset + S_PC]!  @ get pc
259     msr spsr_cxsf, r1           @ save in spsr_svc
260 #if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_32v6K)
261     @ We must avoid clrex due to Cortex-A15 erratum #830321
262     strex   r1, r2, [r2]            @ clear the exclusive monitor
263 #endif
264     .if \fast
265     ldmdb   r2, {r1 - lr}^          @ get calling r1 - lr
266     .else
267     ldmdb   r2, {r0 - lr}^          @ get calling r0 - lr
268     .endif
269     mov r0, r0              @ ARMv5T and earlier require a nop
270                         @ after ldm {}^
271     add sp, sp, #\offset + S_FRAME_SIZE
272     movs    pc, lr              @ return & move spsr_svc into cpsr
273     .endm

这个函数很简单不展开分析了,作用就是从内核栈中将之前保存的值恢复到user模式下的寄存器中。
其中S_OFF是因为EABI规范的需要,需要额外8byte。
另外,因为lr_svc就是user模式中下一条被执行指令,所以这里通过movs pc,lr就直接返回用户空间了。

猜你喜欢

转载自blog.csdn.net/rockrockwu/article/details/82852596
今日推荐