概述
操作系统为在用户态运行的进程与硬件设备进行交互,提供操作系统的系统服务,提供了一组接口。在应用程序和硬件之间,内核提供的系统服务设置一个额外层具有很多优点。
首先,这使得编程更加容易,把用户从学习硬件设备的低级编程特性中解放出来。
其次,这极大地提高了系统的安全性,因为内核在试图满足某个请求之前在接口级就可以检查这种请求的正确性。
最后, 更重要的是这些接口使得程序具有可移植性,因为只要内核所提供的一组接口相同,那么在任一内核之上就可以正确地编译和执行程序。
ARM Linux系统利用SWI指令来从用户空间进入内核空间,还是先让我们了解下这个SWI指令吧。SWI指令用于产生软件中断,从而实现从用户模式到管理模 式的变换,CPSR保存到管理模式的SPSR,执行转移到SWI向量。在其他模式下也可使用SWI指令,处理器同样地切换到管理模式。指令格式如下:
SWI{cond} immed_24
其中:
immed_24 24位立即数,值为从0——16777215之间的整数。
使用SWI指令时,通常使用以下两种方法进行参数传递,SWI异常处理程序可以提供相关的服务,这两种方法均是用户软件协定。
1) 指令中24位的立即数指定了用户请求的服务类型,参数通过通用寄存器传递。SWI异常处理程序要通过读取引起软件中断的SWI指令,以取得24为立即数。如:
MOV R0,#34
SWI 12
2) 指令中的24位立即数被忽略,用户请求的服务类型由寄存器R0的值决定,参数通过其他的通用寄存器传递。如:
MOV R0, #12
MOV R1, #34
SWI 0
Arm Linux内核处理系统调用- 在分析arm中断处理过程中我们提到过arm架构中的异常处理向量表,其中有一个地址就是处理swi软件中断的入口。
- .globl __vectors_start
- __vectors_start:
- swi SYS_ERROR0:
- b vector_und + stubs_offset
- ldr pc, .LCvswi + stubs_offset
- b vector_pabt + stubs_offset
- b vector_dabt + stubs_offset
- b vector_addrexcptn + stubs_offset
- b vector_irq + stubs_offset
- b vector_fiq + stubs_offset
- .globl __vectors_end:
- __vectors_end:
- Cpu执行上面红色指令之后跳转到entry-common.s执行vector_swi处的指令,该函数就是内核中处理系统调用的入口。
- 在用户态调用swi指令,cpu从user状态切换到svc状态。首先需要执行的就是保存cpu现场,这样才能在陷入内核态执行了相应的系统调用处理过程之后,恢复cpu用户态的状态,重新执行用户态进程。
- ENTRY(vector_swi)
- subsp, sp, #S_FRAME_SIZE
- stmiasp, {r0 - r12}@ Calling r0 - r12
- addr8, sp, #S_PC
- stmdbr8, {sp, lr}^@ Calling sp, lr
- mrsr8, spsr@ called from non-FIQ mode, so ok.
- strlr, [sp, #S_PC]@ Save calling PC
- strr8, [sp, #S_PSR]@ Save CPSR
- strr0, [sp, #S_OLD_R0]@ Save OLD_R0
- zero_fp
- BLANK();
- DEFINE(S_R0,offsetof(struct pt_regs, ARM_r0));
- DEFINE(S_R1,offsetof(struct pt_regs, ARM_r1));
- DEFINE(S_R2,offsetof(struct pt_regs, ARM_r2));
- DEFINE(S_R3,offsetof(struct pt_regs, ARM_r3));
- DEFINE(S_R4,offsetof(struct pt_regs, ARM_r4));
- DEFINE(S_R5,offsetof(struct pt_regs, ARM_r5));
- DEFINE(S_R6,offsetof(struct pt_regs, ARM_r6));
- DEFINE(S_R7,offsetof(struct pt_regs, ARM_r7));
- DEFINE(S_R8,offsetof(struct pt_regs, ARM_r8));
- DEFINE(S_R9,offsetof(struct pt_regs, ARM_r9));
- DEFINE(S_R10,offsetof(struct pt_regs, ARM_r10));
- DEFINE(S_FP,offsetof(struct pt_regs, ARM_fp));
- DEFINE(S_IP,offsetof(struct pt_regs, ARM_ip));
- DEFINE(S_SP,offsetof(struct pt_regs, ARM_sp));
- DEFINE(S_LR,offsetof(struct pt_regs, ARM_lr));
- DEFINE(S_PC,offsetof(struct pt_regs, ARM_pc));
- DEFINE(S_PSR,offsetof(struct pt_regs, ARM_cpsr));
- DEFINE(S_OLD_R0,offsetof(struct pt_regs, ARM_ORIG_r0));
- DEFINE(S_FRAME_SIZE,sizeof(struct pt_regs));
- BLANK();
- ldrr10, [lr, #-4]@ get SWI instruction
- 之后,将swi指令保存在r10寄存器中
- enable_irq
- get_thread_info tsk
- adrtbl, sys_call_table@ load syscall table pointer
- ldrip, [tsk, #TI_FLAGS]@ check for syscall tracing
- 开启中断,将thread info结构的指针保存在tsk,将sys_call_table的地址保存在tbl中,将thread info结构中的flag保存ip中。
- scno.reqr7@ syscall number
- tbl.reqr8@ syscall table pointer
- why.reqr8@ Linux syscall (!= 0)
- tsk.reqr9@ current thread_info
- bicsr10, r10, #0xff000000
- eornescno, r10, #__NR_OABI_SYSCALL_BASE
- ldrnetbl, =sys_oabi_call_table
- #elif !defined(CONFIG_AEABI)
- bicscno, scno, #0xff000000@ mask off SWI op-code
- eorscno, scno, #__NR_SYSCALL_BASE@ check OS number
- #endif
- 检查swi命令中带的中断号是不是为0,如果不等于零说明是是old ABI方式调用的系统调用,这时将sys_oabi_call_table的地址载入到tbl中,同时得到系统调用的中断号,保存在scno中。如果swi指令中带点中断号为0,说明系统调用是利用scno寄存器传递中断号的。利用异或指令,将__NR_SYSCALL_BASE清除掉,得到实际的系统请求号(系统调用号定义为__NR_SYSCALL_BASE+x)。
- stmdbsp!, {r4, r5}@ push fifth and sixth args
- tstip, #_TIF_SYSCALL_TRACE@ are we tracing syscalls?
- bne__sys_trace
- 如果进程带有syscall trace标志就调用sys_trace,这处应该是作为调试用的。
- cmpscno, #NR_syscalls@ check upper syscall limit
- adrlr, ret_fast_syscall@ return address
- ldrccpc, [tbl, scno, lsl #2]@ call sys_* routine
- 如果系统调用号,不大于NR_syscalls,就跳转到系统调用函数去执行ldrccpc, [tbl, scno, lsl #2]
总结
Arm系统调用的简单概述。首先是用户态执行swi指令,swi指令使得cpu陷入svc状态,并跳转到固定地址去执行系统调用处理过程。用户态通过两种方法传递给内核执行系统调用的系统调用号。内核执行系统调用处理过程,首先保存cpu现场,之后会获取到系统调用号,以系统调用地址表的基地址,加系统调用号做偏移,跳转到相应的系统调用例程做相应的处理。