linux系统调⽤过程解析(基于ARM处理器)
1、系统调⽤相关代码注释
1.1、中断向量
.section .vectors, "ax", %progbits
__vectors_start:
W(b) vector_rst ;// 系统复位中断向量
W(b) vector_und ;// 未定义指令中断向量
W(ldr) pc, __vectors_start + 0x1000 ;// swi软中断中断向量(系统调⽤⼊⼝)urlpattern 匹配多个
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq ;// irq中断向量
W(b) vector_fiq
1.2、swi软中断(系统调⽤)
1.2.1、系统调⽤进⼊
.align 5
access例句ENTRY(vector_swi)
......
sub sp, sp, #S_FRAME_SIZE ;// ARM栈由⾼地址往低地址递减,减去72个字节地址(共18个寄存器⼤⼩),⽤于保存中断上下⽂
stmia sp, {r0 - r12}@ Calling r0 - r12 ;// ⽤户模式与SVC特权模式共⽤r0-r12寄存器(13*4=52字节),"ia"即increase after,低寄存器存低地址,⾼寄存器存⾼地址,从低地址到⾼地址依次存储r0-r12,sp指向r0 (sp等价于pt_regs的低地址),此时栈内容为r0-r12
ARM( add r8, sp, #S_PC ) ;// sp + pt_regs->pc偏移存储到r8(ARM pc偏移地址为15*4)
ARM( stmdb r8, {sp, lr}^ )@ Calling sp, lr ;// ⽤户模式sp、lr寄存器存储到栈⾥⾯,r8 = offset(pt_regs-pc),r8先减4再⼊栈,即将⽤户模式sp,lr保存到pc位置之前
THUMB( mov r8, sp )
THUMB( store_user_sp_lr r8, r10, S_SP)@ calling sp, lr
mrs r8, spsr@ called from non-FIQ mode, so ok. ;// 保存系统调⽤前程序状态寄存器spsr(cpsr_usr)到r8寄存器
清除cache分区是什么意思str lr, [sp, #S_PC]@ Save calling PC ;// 此处的lr是svc模式lr,保存的是⽤户模式的pc寄存器值,系统调⽤前指令的位置,保存到栈对应偏移位置
str r8, [sp, #S_PSR]@ Save CPSR ;// ⽤户模式的保存到栈对应偏移位置
str r0, [sp, #S_OLD_R0]@ Save OLD_R0 ;// r0寄存器的值到栈对应偏移位置(r0保存了两次)
#endif
zero_fp
alignment_trap r10, ip, __cr_alignment
enable_irq
ct_user_exit
get_thread_info tsk (将sp低13位清0即为线程栈的起始地址,栈底保存了线程的thread_info信息,tsk为r9寄存器)
/*
* Get the system call number.
*/
#if defined(CONFIG_OABI_COMPAT)
......
#elif defined(CONFIG_AEABI)
/*
* Pure EABI user space always put syscall number into scno (r7).
*/
#elif defined(CONFIG_ARM_THUMB)
......
#else
/* Legacy ABI only. */
USER( ldr scno, [lr, #-4] )@ get SWI instruction
#endif
uaccess_disable tbl
adr tbl, sys_call_table@ load syscall table pointer ;// 获取系统调⽤表的地址到r8寄存器
.
.....
local_restart:
ldr r10, [tsk, #TI_FLAGS]@ check for syscall tracing ;// 读取thread_info->flags到r10
stmdb sp!, {r4, r5}@ push fifth and sixth args ;// 第5、6个参数⼊栈(1-4参数保存在r0-r3⾥⾯,这个过程没有修改,直接传递到下⼀级函数,第5、6个参数⼊栈应该是编译器参数传递规则,可以个有5个以上参数的函数反汇编,查看参数怎么取的即可验证⼊参规则)
tst r10, #_TIF_SYSCALL_WORK@ are we tracing syscalls?
bne __sys_trace
cmp scno, #NR_syscalls@ check upper syscall limit ;// scno即r7寄存器,glibc系统调⽤的时候将系统调⽤号保存在r7⾥⾯,检查系统调⽤号是否超出范围(系统调⽤号有可能编译到指令⾥⾯,具体看编译器)
badr lr, ret_fast_syscall@ return address ;// 设置系统调⽤返回地址(系统调⽤完还处于内核状态,需要从内核态返回到⽤户态,或者调度等)
ldrcc pc, [tbl, scno, lsl #2]@ call sys_* routine ;// scno左移两位等价于scno * 4,因为每个系统调⽤函数指针占4个字节,系统调⽤表就是个函数指针数组,系统调⽤表基址加上偏移即得到真正的系统调⽤⼊⼝函数地址(scno⼩于NR_syscalls才执⾏此命令,即系统调⽤号有效才执⾏)
add r1, sp, #S_OFF
2: cmp scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)
eor r0, scno, #__NR_SYSCALL_BASE@ put OS number back
linux操作系统书籍bcs arm_syscall ;// 执⾏arm_syscall,注意此处以及ldrcc系统调⽤都是直接跳转的,返回地址都是lr(ret_fast_syscall)⽽不是下⼀条命令
mov why, #0@ no longer a real syscall ;// ⽆效的系统调⽤,r8设置为0
b sys_ni_syscall@ not private fun
c ;// 跳转到sys_ni_syscall,该函数将返回-ENOSYS错误码并保存到r0寄存器
......
ENDPROC(vector_swi)
1.2.2、系统调⽤返回
前⾯执⾏真正的系统调⽤前已经将lr设置为ret_fast_syscall,lr即为c语⾔函数的返回地址,函数执⾏完返回的时候会将pc设置为lr,即可返回到指定地址继续执⾏。
ret_fast_syscall:
UNWIND(.fnstart )
UNWIND(.cantunwind )
str r0, [sp, #S_R0 + S_OFF]! @ save returned r0 ;// 系统调⽤函数(例如:SyS_write)会把return的值保存在r0⾥⾯,c 函数返回值都是保存在r0⾥⾯的,将执⾏结果保存到系统栈⾥⾯
disable_irq_notrace @ disable interrupts
ldr r1, [tsk, #TI_FLAGS] @ re-check for syscall tracing
tst r1, #_TIF_SYSCALL_WORK | _TIF_WORK_MASK
;// _TIF_WORK_MASK=_TIF_NEED_RESCHED|_TIF_SIGPENDING|_TIF_NOTIFY_RESUME|_TIF_UPROBE,系统调⽤有可能会阻塞,例如读写硬盘,需要等待,或者系统调⽤就是执⾏任务切换或者sleep操作,那么新的线程进程就需要重新调度,⽽不是直接返回到⽤户进程,系统调⽤函数就会设置_TIF_NEED_RESCHED标志位,对于信号理论上应该是⼀致的,信号函数优先级⾼于⽤户态函数
beq no_work_pending ;// 如果没有标志位被设置(没有挂起的待处理⼯作要运⾏),则跳转到no_work_pending继续执⾏
UNWIND(.fnend )
ENDPROC(ret_fast_syscall)
/* Slower path - fall through to work_pending */
#endif
tst r1, #_TIF_SYSCALL_WORK
bne __sys_trace_return_nosave
slow_work_pending:
mov r0, sp @ 'regs' ; // sp栈保存了进程上下⽂,作为第⼀个参数传递给do_work_pending,因为
do_work_pending⾥⾯有可能需要切换等,有等待信号要处理的情况下,得先执⾏信号函数,⽽信号函数是在⽤户态执⾏,因此需要将当前上下⽂额外保存,对于Nucleus Plus是将信号处理函数相关寄存器(⼊⼝以及参数等)构造⼀个新的恢复上下⽂,先恢复信号上下⽂,再由信号处理函数恢复系统调⽤上下⽂,linux内核信号处理流程不在此处细看
mov r2, why @ 'syscall'
bl do_work_pending
cmp r0, #0
beq no_work_pending
movlt scno, #(__NR_restart_syscall - __NR_SYSCALL_BASE)
ldmia sp, {r0 - r6} @ have to reload r0 - r6
b local_restart @ ... and off we go
ENDPROC(ret_fast_syscall)
......
ENTRY(ret_to_user)
ret_slow_syscall:
disable_irq_notrace @ disable interruptssql2008 32位安装包
ENTRY(ret_to_user_from_irq)
ldr r1, [tsk, #TI_FLAGS]
tst r1, #_TIF_WORK_MASK
bne slow_work_pending
no_work_pending:
asm_trace_hardirqs_on save = 0
/* perform architecture specific actions before user return */
arch_ret_to_user r1, lr
ct_user_enter save = 0
restore_user_regs fast = 0, offset = 0 ;// 系统调⽤如果不需要重新调度或者信号等处理的话,mmu是没有切换的,因此直接恢复程序状态寄存器、返回值寄存器r0及其他通⽤寄存器即可(lr->pc, spsr->cpsr),详细细节参考restore_user_regs定义
ENDPROC(ret_to_user_from_irq)
ENDPROC(ret_to_user)
1.2.3、恢复⽤户进程上下⽂
.macro restore_user_regs, fast = 0, offset = 0
uaccess_enable r1, isb=0
#ifndef CONFIG_THUMB2_KERNEL
@ ARM mode restore
mov r2, sp
ldr r1, [r2, #\offset + S_PSR] @ get calling cpsr ;// 从内核栈获取⽤户态cpsr
ldr lr, [r2, #\offset + S_PC]! @ get pc ;// 从内核栈获取⽤户态pc,即下⼀条指令的地址(多级流⽔线,pc并不是系统调⽤地址,⽽是下⼀条指令的地址,具体pc值可参考相关书籍,查看swi指令是否有对pc减4操作)
msr spsr_cxsf, r1 @ save in spsr_svc ;// 将⽤户态的cpsr保存到内核态的spsr,因为,状态切换是会将当前模式的spsr恢复到cpsr
#if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_32v6K)
@ We must avoid clrex due to Cortex-A15 erratum #830321
strex r1, r2, [r2] @ clear the exclusive monitor
#endif
.if \fast
ldmdb r2, {r1 - lr}^ @ get calling r1 - lr
.else
ldmdb r2, {r0 - lr}^ @ get calling r0 - lr ;// r0-lr恢复到⽤户态寄存器
.endif
mov r0, r0 @ ARMv5T and earlier require a nop
@ after ldm {}^
add sp, sp, #\offset + S_FRAME_SIZE ;// 释放进程上下⽂占⽤的栈空间
movs pc, lr @ return & move spsr_svc into cpsr ;// 恢复pc寄存器(跳转到中断前指令地址),同时恢复cpsr #elif defined(CONFIG_CPU_V7M)
@ V7M restore.
@ Note that we don't need to do clrex here as clearing the local
@ monitor is part of the exception entry and exit sequence.
.if \offset
add sp, #\offset
.endif
v7m_exception_slow_exit ret_r0 = \fast
#else
@ Thumb mode restore
mov r2, sp
load_user_sp_lr r2, r3, \offset + S_SP @ calling sp, lr
ldr r1, [sp, #\offset + S_PSR] @ get calling cpsr
ldr lr, [sp, #\offset + S_PC] @ get pc
add sp, sp, #\offset + S_SP
msr spsr_cxsf, r1 @ save in spsr_svc
@ We must avoid clrex due to Cortex-A15 erratum #830321
strex r1, r2, [sp] @ clear the exclusive monitor
.if \fast
ldmdb sp, {r1 - r12} @ get calling r1 - r12
.else
ldmdb sp, {r0 - r12} @ get calling r0 - r12
.endif
add sp, sp, #S_FRAME_SIZE - S_SP
movs pc, lr @ return & move spsr_svc into cpsr
#endif /* !CONFIG_THUMB2_KERNEL */
.endm
2、系统调⽤执⾏流程
上⾯已经介绍了个模块内部执⾏细节,在此从总体上介绍下系统调⽤流程。
2.1、glibc系统调⽤
2.1.1、参数传递
参数传递⼤致如下,就是将参数按顺序保存到寄存器⾥⾯
#define LOAD_ARGS_0()
#define ASM_ARGS_0
#define LOAD_ARGS_1(a1) \
_a1 = (int) (a1); \
LOAD_ARGS_0 ()
#define ASM_ARGS_1 ASM_ARGS_0, "r" (_a1)
#define LOAD_ARGS_2(a1, a2) \
docker安装青龙面板register int _a2 asm ("a2") = (int) (a2); \
LOAD_ARGS_1 (a1)
#define ASM_ARGS_2 ASM_ARGS_1, "r" (_a2)
#define LOAD_ARGS_3(a1, a2, a3) \
register int _a3 asm ("a3") = (int) (a3); \
LOAD_ARGS_2 (a1, a2)
2.1.2、系统调⽤(进⼊内核态)
ARM最终都是通过swi指令切换的svc模式
#define INTERNAL_SYSCALL_RAW(name, err, nr, )\
({ unsigned int _sys_result; \
{ \
register int _a1 asm ("a1"); \
LOAD_ARGS_##nr (args) \
asm volatile ("swi %1@ syscall " #name\
: "=r" (_a1)\
: "i" (name) ASM_ARGS_##nr\
: "memory");\
_sys_result = _a1; \
} \
(int) _sys_result; })
2.2、系统调⽤(中断向量)
__vectors_start + 0x1000地址正好存储vector_swi函数地址,具体参考vmlinux.lds.S以及early_trap_init中断向量拷贝函数
__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, __vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq
W(b) vector_fiq
3、系统调⽤总结
系统调⽤主要就是glibc通过寄存器及swi指令切换到内核态,内核态保存⽤户态上下⽂,执⾏系统调⽤,判断是否有挂起任务待处理,执⾏挂起任务或者直接恢复⽤户态寄存器。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论