1、系統調用相關代碼註釋
1.1、中斷向量
__vectors_start:
W(b) vector_rst ;// 系統復位中斷向量
W(b) vector_und ;// 未定義指令中斷向量
W(ldr) pc, __vectors_start + 0x1000 ;// swi軟中斷中斷向量(系統調用入口)
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
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寄存器
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
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 func ;// 跳轉到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 interrupts
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 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) \
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、系統調用(進入內核態)
({ 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、系統調用(中斷向量)
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