Linux ARM系统调用实现
一.Arm SWI 异常中断
本部分参考《ARM体系结构与编程》杜春雷
ARM版本的Linux的系统调用的实现是通过发一个SWI异常中断的方式实现的,通过SWI异常可以实现用户空间陷入到内核的操作。通常SWI异常中断处理程序可以分为两级:第一级SWI异常中断处理程序为汇编语言,用于确定SWI中的24位立即数;第二级SWI异常中断处理程序实现SWI的各个功能。
1.1 SWI指令
在SWI指令中包含有24位的立即数,通过该立即数可以使用户跳转到不同的执行分支上。在进入SWI异常时,LR寄存器保存的是SWI指令的下一条,所以,SWI指令应该是:
LDR R0,[LR, # -4]
取SWI指令的24位立即数
BIC R0,R0,#0XFF00 0000
在linux系统调用时,采用该两行代码取得系统调用号。
1.2实现代码
AREA TopLevelSWI, CODE, READONLY
EXPORT SWI_Handler
SWI_Handler
STMFD sp!, [r0-r12, lr]
LDR r0, [lr, # -4]
BIC r0, r0, #0xff00 0000
BLC C_SWI_Handler //使用R0寄存器中的值,调用相应的SWI异常中断的第二级处理程序。
LDMFD sp!, [r0-r12, pc]^
END
/
Void C_SWI_Handler(unsigned number)
{
Switch(number)
Case 0://执行分支0;
Break;
Case 1://执行分支1;
Break;
Default:
break
}
二.Linux系统调用的实现
Linux系统调用是应用程序的API函数调用内核提供的系统调用实现对linux内核或者硬件的访问,在ARM平台上是通过产生SWI异常来实现的。
2.1SWI异常执行函数的注册
void __init trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE;
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
}
该函数在start_kernel()中执行,即初始化的时候执行。
.LCvswi:
.word vector_swi/* SYS_CALL*/
.globl __stubs_end
__stubs_end:
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
.globl __vectors_start
__vectors_start:
swi SYS_ERROR0
b vector_und + stubs_offset
ldrpc, .LCvswi + stubs_offset //此处即为定义的SWI函数,指向vector_swi
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:
.data
.globl cr_alignment
.globl cr_no_alignment
cr_alignment:
.space 4
cr_no_alignment:
.space 4
2.2 linux SWI函数vector_swi
.align 5
ENTRY(vector_swi)/**/
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0 - r12
add r8, sp, #S_PC
stmdb r8, {sp, lr}^ @ Calling sp, lr
mrs r8, spsr @ called from non-FIQ mode, so ok.
str lr, [sp, #S_PC] @ Save calling PC
str r8, [sp, #S_PSR] @ Save CPSR
str r0, [sp, #S_OLD_R0] @ Save OLD_R0
zero_fp
//保存寄存器值到栈中
/*
* Get the system call number.
*/
#if defined(CONFIG_OABI_COMPAT)
/*
* If we have CONFIG_OABI_COMPAT then we need to look at the swi
* value to determine if it is an EABI or an old ABI call.
*/
#ifdef CONFIG_ARM_THUMB
tst r8, #PSR_T_BIT
movne r10, #0 @ no thumb OABI emulation
ldreq r10, [lr, #-4] @ get SWI instruction,取swi指令中的立即数,即系统调用号
#else
ldr r10, [lr, #-4] @ get SWI instruction // here get syscall num swi
A710( and ip, r10, #0x0f000000 @ check for SWI )
A710( teq ip, #0x0f000000 )
A710( bne .Larm710bug )
#endif
#elif defined(CONFIG_AEABI)
/*
* Pure EABI user space always put syscall number into scno (r7).
*/
A710( ldr ip, [lr, #-4] @ get SWI instruction )
A710( and ip, ip, #0x0f000000 @ check for SWI )
A710( teq ip, #0x0f000000 )
A710( bne .Larm710bug )
#elif defined(CONFIG_ARM_THUMB)
/* Legacy ABI only, possibly thumb mode. */
tst r8, #PSR_T_BIT @ this is SPSR from save_user_regs
addne scno, r7, #__NR_SYSCALL_BASE @ put OS number in /*__NR_SYSCALL_BASE*/
ldreq scno, [lr, #-4]// scno是寄存器r7的别名
#else
/* Legacy ABI only. */
ldr scno, [lr, #-4] @ get SWI instruction
A710( and ip, scno, #0x0f000000 @ check for SWI )
A710( teq ip, #0x0f000000 )
A710( bne .Larm710bug )
#endif
#ifdef CONFIG_ALIGNMENT_TRAP
ldr ip, __cr_alignment
ldr ip, [ip]
mcr p15, 0, ip, c1, c0 @ update control register
#endif
enable_irq
get_thread_info tsk
adr tbl, sys_call_table @ load syscall table pointer/* important here load sys_call_table, details see line268,line269,and see call.s*///系统调用函数的基指针
ldr ip, [tsk, #TI_FLAGS] @ check for syscall tracing
#if defined(CONFIG_OABI_COMPAT)
/*
* If the swi argument is zero, this is an EABI call and we do nothing.
*
* If this is an old ABI call, get the syscall number into scno and
* get the old ABI syscall table address.
*/
bics r10, r10, #0xff000000 // here get syscall num swi
eorne scno, r10, #__NR_OABI_SYSCALL_BASE
ldrne tbl, =sys_oabi_call_table
#elif !defined(CONFIG_AEABI)
bic scno, scno, #0xff000000 @ mask off SWI op-code //here get syscall num swi
eor scno, scno, #__NR_SYSCALL_BASE @ check OS number
#endif
stmdb sp!, {r4, r5} @ push fifth and sixth args
tst ip, #_TIF_SYSCALL_TRACE @ are we tracing syscalls?
bne __sys_trace
cmp scno, #NR_syscalls @ check upper syscall limit
adr lr, ret_fast_syscall @ return address系统调用结束后的返回函数,恢复寄存器
ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine /* execut the call function,将调用号逻辑左移2位*/系统调用的执行函数
add r1, sp, #S_OFF
2: mov why, #0 @ no longer a real syscall
cmp scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)
eor r0, scno, #__NR_SYSCALL_BASE @ put OS number back
bcs arm_syscall //系统调用号不正确
b sys_ni_syscall @ not private func
2.3 linux系统调用的返回函数
ret_fast_syscall:
disable_irq @ disable interrupts
ldr r1, [tsk, #TI_FLAGS]
tst r1, #_TIF_WORK_MASK
bne fast_work_pending
/* perform architecture specific actions before user return */
arch_ret_to_user r1, lr
@ fast_restore_user_regs
ldr r1, [sp, #S_OFF + S_PSR] @ get calling cpsr
ldr lr, [sp, #S_OFF + S_PC]! @ get pc
msr spsr_cxsf, r1 @ save in spsr_svc
ldmdb sp, {r1 - lr}^ @ get calling r1 - lr
mov r0, r0
add sp, sp, #S_FRAME_SIZE - S_PC
movs pc, lr @ return & move spsr_svc into cpsr