1. SWI軟中斷
以ARMV7 A/R架構為例, SWI軟中斷和中斷一樣,內核空間處理始於異常向量表。Linux向量表默認地址0XFFFF0000,SWI向量偏移8字節為0xFFFF0008:

具體代碼,位於 \linux-3.4.x\arch\arm\kernel\entry-armv.S:
__vectors_start: ARM( swi SYS_ERROR0 ) THUMB( svc #0 ) THUMB( nop ) W(b) vector_und + stubs_offset W(ldr) pc, .LCvswi + stubs_offset W(b) vector_pabt + stubs_offset W(b) vector_dabt + stubs_offset W(b) vector_addrexcptn + stubs_offset W(b) vector_irq + stubs_offset W(b) vector_fiq + stubs_offset .globl __vectors_end __vectors_end: .LCvswi: .word vector_swi
vector_swi的具體實現,位於 \linux-3.4.x\arch\arm\kernel\entry-common.S,vector_swi函數完成的工作:
1. 保存異常前的現場
1 ENTRY(vector_swi) 2 sub sp, sp, #S_FRAME_SIZE 3 stmia sp, {r0 - r12} @ Calling r0 - r12 4 ARM( add r8, sp, #S_PC ) 5 ARM( stmdb r8, {sp, lr}^ ) @ Calling sp, lr 6 THUMB( mov r8, sp ) 7 THUMB( store_user_sp_lr r8, r10, S_SP ) @ calling sp, lr 8 mrs r8, spsr @ called from non-FIQ mode, so ok. 9 str lr, [sp, #S_PC] @ Save calling PC 10 str r8, [sp, #S_PSR] @ Save CPSR 11 str r0, [sp, #S_OLD_R0] @ Save OLD_R0 12 zero_fp
保存現場大小為S_FRAME_SIZE,大小是一個完整的寄存器棧幀:
DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs)); struct pt_regs { long uregs[18]; };
#define ARM_cpsr uregs[16]
#define ARM_pc uregs[15]
#define ARM_lr uregs[14]
#define ARM_sp uregs[13]
#define ARM_ip uregs[12]
#define ARM_fp uregs[11]
#define ARM_r10 uregs[10]
#define ARM_r9 uregs[9]
#define ARM_r8 uregs[8]
#define ARM_r7 uregs[7]
#define ARM_r6 uregs[6]
#define ARM_r5 uregs[5]
#define ARM_r4 uregs[4]
#define ARM_r3 uregs[3]
#define ARM_r2 uregs[2]
#define ARM_r1 uregs[1]
#define ARM_r0 uregs[0]
#define ARM_ORIG_r0 uregs[17]
從現場可以看出,每次用戶空間向內核空間切換時,線程內核棧會保留一份完整的寄存器現場,保存地址在內核棧的(最高地址-8):
線程內核棧基地址:0xc288a000
線程內核棧最高地址:0xc288a000+8k = 0xc288c000
保存形式:
=============================低地址
R0 R1 R2 R3
R4 R5 R6 R7
R8 R9 R10 R11
R12 SP LR PC
CPSR R0_OLD X X
========================== kernel stack start(最高地址)

值得注意的是,arm-linux把內核棧的(最高地址-8)作為棧起始地址,詳見 \linux-3.4.x\arch\arm\include\asm\thread_info.h
#define THREAD_SIZE 8192 #define THREAD_START_SP (THREAD_SIZE - 8)
原因:
https://git.kernel.org/pub/scm/linux/kernel/git/history/history.git/commit/?id=415395e19fd197ce4f248902dba54f4065af547c
Always leave 8 bytes free at the top of the kernel stack. This prevents the stack becoming completely empty when do_exit() is called from an exiting nfsd() thread, and causing the wrong pointer to be returned from current_thread_info()
[PATCH] ARM: Fix kernel stack offset calculations Various places in the ARM kernel implicitly assumed that kernel stacks are always 8K due to hard coded constants. Replace these constants with definitions. Correct the allowable range of kernel stack pointer values within the allocation. Arrange for the entire kernel stack to be zeroed, not just the upper 4K if CONFIG_DEBUG_STACK_USAGE is set. Signed-off-by: Russell King
2. 獲取系統調用號
vector_swi函數中有兩個重要的宏,分別為:
CONFIG_OABI_COMPAT
CONFIG_AEABI
OABI = Old application binary interface
EABI = Extended application binary interface
ABI = 應用程序二進制接口, OABI/EABI都是針對ARM處理器的接口,EABI也叫GNU EABI.
兩者的區別:
1. 調用規則(參數傳遞,返回值傳遞)
2. 系統調用數目以及應用程序怎么做系統調用
3. 目標文件二進制個數
4. 結構體中填充和對齊
EABI的好處
1. 支持軟件浮點/硬件浮點混用
2. 效率更高
3. 工具兼容
這兩個宏可以在make menuconfig時進行配置
kernel features -->
[*] Use the ARM EABI to compile the kernel
[*] Allow old ABI binary to run this kernel
兩個可以同時Yes, 也可以只選一個EABI或者都不選
OABI方式系統調用
SWI{cond} immed_24
immed_24: 24位立即數,指定了系統調用號,參數用通用寄存器傳遞
MOV R0,#34
SWI 12
EABI方式系統調用
MOV R7,#34
SWI 0X0
系統調用號由R7寄存器決定
在SWI異常處理程序中,得到系統調用號的方法:
- 如果定義了OABI,說明存在OABI方式的調用,需要從SWI指令中獲得調用號:
- 首先確定軟中斷的SWI指令時ARM指令還是Thumb指令,這可通過對SPSR訪問得到(前一步驟R8保存了SPSR);
- OABI方式無法使用Thumb指令實現SWI,因此下一步取得ARM指令中SWI操作的地址,這可通過訪問(LR-4)得到(最后一條指令即SWI XXX);
- 接着讀出指令,分解出立即數中低24位(這一步先保存整條指令在R10,待下一步驟分解);
#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 #else ldr r10, [lr, #-4] @ get SWI instruction #endif
- 如果沒有定義OABI,但是定義了EABI,系此時統調用號只會存放在R7寄存器(即scno), 不需要做任何處理,:
#elif defined(CONFIG_AEABI) /* * Pure EABI user space always put syscall number into scno (r7). */
- 如果沒有定義OABI/EABI,但是定義了CONFIG_ARM_THUMB:
#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 ldreq scno, [lr, #-4]
- 如果OABI/EABI/CONFIG_ARM_THUMB都沒有定義,此時保存整條SWI指令在R7,待下一步驟分解:
#else /* Legacy ABI only. */ ldr scno, [lr, #-4] @ get SWI instruction #endif
3. 以系統調用號為索引查找系統調用表,然后調用相應的處理例程
- 首先打開中斷,並把tbl變量賦值sys_call_table
enable_irq
get_thread_info tsk
adr tbl, sys_call_table @ load syscall table pointer
- 如果定義了OABI:
#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 eorne scno, r10, #__NR_OABI_SYSCALL_BASE ldrne tbl, =sys_oabi_call_table
- 首先把R10(SWI指令)取低24位,得到系統調用號
- 然后檢查該系統調用號是不是0,如果是0表明這是一個EABI系統調用,不做任何處理;如果不是0則把系統調用號與__NR_OABI_SYSCALL_BASE做異或處理,tbl賦值sys_oabi_call_table
- 如果OABI/EABI都沒有定義,把R7(系統調用號)取低24位,並與__NR_OABI_SYSCALL_BASE做異或處理
#elif !defined(CONFIG_AEABI) bic scno, scno, #0xff000000 @ mask off SWI op-code eor scno, scno, #__NR_SYSCALL_BASE @ check OS number #endif
- 其他情況不做處理
- 然后根據轉換后的系統調用號(都存在R7),在對應的調用跳轉表(tbl)中進行偏移后,得到相應的調用函數
cmp scno, #NR_syscalls @ check upper syscall limit adr lr, BSYM(ret_fast_syscall) @ return address ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine
系統調用跳轉表在vector_swi中有兩份:
sys_call_table
sys_oabi_call_table
- 如果OABI和EABI都選擇,應用程序使用OABI方式時調用sys_oabi_call_table,應用程序使用EABI時調用sys_call_table
- 如果只選擇EABI,使用sys_call_table
- 如果都不選,使用sys_call_table
- 由於OABI對EABI有依賴關系,所以不能單獨選擇OABI
1 ENTRY(sys_call_table) 2 #include "calls.S" 3 #undef ABI 4 #undef OBSOLETE 5 6 /*============================================================================ 7 * Special system call wrappers 8 */ 9 @ r0 = syscall number 10 @ r8 = syscall table 11 sys_syscall: 12 bic scno, r0, #__NR_OABI_SYSCALL_BASE 13 cmp scno, #__NR_syscall - __NR_SYSCALL_BASE 14 cmpne scno, #NR_syscalls @ check range 15 stmloia sp, {r5, r6} @ shuffle args 16 movlo r0, r1 17 movlo r1, r2 18 movlo r2, r3 19 movlo r3, r4 20 ldrlo pc, [tbl, scno, lsl #2] 21 b sys_ni_syscall 22 ENDPROC(sys_syscall)
