Linux体系经过向内核宣布体系调用(system call)完结了用户态进程和硬件设备之间的大部分接口。
体系调用是操作体系供给的服务,用户程序经过各种体系调用,来引证内核供给的各种服务,体系调用的履行让用户程序堕入内核,该堕入动作由swi软中止完结。
1、用户能够经过两种办法运用体系调用:
榜首种办法是经过C库函数,包括体系调用在C库中的封装函数和其他一般函数。
第二种办法是运用_syscall宏。2.6.18版别之前的内核,在include/asm-i386/unistd.h文件中界说有7个_syscall宏,别离是:
_syscall0(type,name) _syscall1(type,name,type1,arg1) _syscall2(type,name,type1,arg1,type2,arg2) _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) _syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5) _syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5,type6,arg6)
其间,type标明所生成体系调用的回来值类型,name标明该体系调用的称号,typeN、argN别离标明第N个参数的类型和称号,它们的数目和_syscall后边的数字相同大。
这些宏的效果是创立名为name的函数,_syscall后边跟的数字指明晰该函数的参数的个数。
比方sysinfo体系调用用于获取体系整体计算信息,运用_syscall宏界说为:
_syscall1(int, sysinfo, struct sysinfo *, info);
翻开后的方法为:
int sysinfo(struct sysinfo * info) { long __res; __asm__ volatile("int $0x80" : "=a" (__res) : "0" (116),"b" ((long)(info))); do { if ((unsigned long)(__res) >= (unsigned long)(-(128 + 1))) { errno = -(__res); __res = -1; } return (int) (__res); } while (0); }
能够看出,_syscall1(int, sysinfo, struct sysinfo *, info)翻开成一个名为sysinfo的函数,原参数int便是函数的回来类型,原参数struct sysinfo *和info别离构成新函数的参数。
在程序文件里运用_syscall宏界说需求的体系调用,就能够在接下来的代码中经过体系调用称号直接调用该体系调用。下面是一个运用sysinfo体系调用的实例。
代码清单5.1 sysinfo体系调用运用实例
#include#include #include #include /* for struct sysinfo */ _syscall1(int, sysinfo, struct sysinfo *, info); int main(void) { struct sysinfo s_info; int error; error = sysinfo(&s_info); printf("code error = %d/n", error); printf("Uptime = %lds/nLoad: 1 min %lu / 5 min %lu / 15 min %lu/n" "RAM: total %lu / free %lu / shared %lu/n" "Memory in buffers = %lu/nSwap: total %lu / free %lu/n" "Number of processes = %d/n", s_info.uptime, s_info.loads[0], s_info.loads[1], s_info.loads[2], s_info.totalram, s_info.freeram, s_info.sharedram, s_info.bufferram, s_info.totalswap, s_info.freeswap, s_info.procs); exit(EXIT_SUCCESS); }
可是自2.6.19版别开端,_syscall宏被废弃,咱们需求运用syscall函数,经过指定体系调用号和一组参数来调用体系调用。
syscall函数原型为:
int syscall(int number, ...);
其间number是体系调用号,number后边应次序接上该体系调用的一切参数。下面是gettid体系调用的调用实例。
代码清单5.2 gettid体系调用运用实例
#include#include #include #define __NR_gettid 224 int main(int argc, char *argv[]) { pid_t tid; tid = syscall(__NR_gettid); }
大部分体系调用都包括了一个SYS_符号常量来指定自己到体系调用号的映射,因而上面第10行可重写为:
tid = syscall(SYS_gettid);
2 体系调用与运用编程接口(API)差异
运用编程接口(API)与体系调用的不同在于,前者仅仅一个函数界说,阐明晰怎样取得一个给定的服务,而后者是经过软件中止向内核宣布的一个清晰的恳求。POSIX标准针对API,而不针对体系调用。Unix体系给程序员供给了许多API库函数。libc的标准c库所界说的一些API引证了封装例程(wrapper routine)(其仅有意图便是发布体系调用)。一般状况下,每个体系调用对应一个封装例程,而封装例程界说了运用程序运用的API。反之则否则,一个API没必要对应一个特定的体系调用。从编程者的观念看,API和体系调用之间的不同是没有联系的:仅有相关的作业便是函数名、参数类型及回来代码的意义。可是,从内核设计者的观念看,这种不同的确有联系,由于体系调用归于内核,而用户态的库函数不归于内核。
大部分封装例程回来一个整数,其值的意义依赖于相应的体系调用。回来-1一般标明内核不能满意进程的恳求。体系调用处理程序的失利或许是由无效参数引起的,也或许是由于缺少可用资源,或硬件出了问题等等。在libd库中界说的errno变量包括特定的犯错码。每个犯错码界说为一个常量宏。
当用户态的进程调用一个体系调用时,CPU切换到内核态并开端履行一个内核函数。由于内核完结了许多不同的体系调用,因而进程有必要传递一个名为体系调用号(system call number)的参数来辨认所需的体系调用。一切的体系调用都回来一个整数值。这些回来值与封装例程回来值的约定是不同的。在内核中,整数或0标明体系调用成功完毕,而负数标明一个犯错条件。在后一种状况下,这个值便是寄存在errno变量中有必要回来给运用程序的负犯错码。
3 体系调用履行进程
ARM Linux体系运用SWI指令来从用户空间进入内核空间,仍是先让咱们了解下这个SWI指令吧。SWI指令用于发生软件中止,然后完结从用户形式变换到管理形式,CPSR保存到管理形式的SPSR,履行转移到SWI向量。在其他形式下也可运用SWI指令,处理器相同地切换到管理形式。指令格局如下:
SWI{cond} immed_24
其间:
immed_2424位当即数,值为从0——16215之间的整数。
运用SWI指令时,一般运用一下两种办法进行参数传递,SWI反常处理程序能够供给相关的服务,这两种办法均是用户软件协议。SWI反常中止处理程序要经过读取引起软件中止的SWI指令,以取得24为当即数。
1)、指令中24位的当即数指定了用户恳求的服务类型,参数经过通用寄存器传递。如:
MOV R0,#34SWI 12
2)、指令中的24位当即数被疏忽,用户恳求的服务类型有寄存器R0的只决议,参数经过其他的通用寄存器传递。如:
MOV R0, #12MOV R1, #34SWI 0
在SWI反常处理程序中,去除SWI当即数的过程为:首要确认一同软中止的SWI指令时ARM指令仍是Thumb指令,这可经过对SPSR拜访得到;然后取得该SWI指令的地址,这可经过拜访LR寄存器得到;接着读出指令,分解出当即数(低24位)。
由用户空间进入体系调用
一般状况下,咱们写的代码都是经过封装的C lib来调用体系调用的。以0.9.30版uClibc中的open为例,来追寻一下这个封装的函数是怎样一步一步的调用体系调用的。在include/fcntl.h中有界说:
# define open open64
open实践上仅仅open64的一个别号罢了。
在libc/sysdeps/linux/common/open64.c中能够看到:
extern __typeof(open64) __libc_open64;extern __typeof(open) __libc_open;
可见open64也只不过是__libc_open64的别号,而__libc_open64函数在同一个文件中界说:
libc_hidden_proto(__libc_open64)int __libc_open64 (const char *file, int oflag, ...){mode_t mode = 0;if (oflag & O_CREAT){va_list arg;va_start (arg, oflag);mode = va_arg (arg, mode_t);va_end (arg);}return __libc_open(file, oflag O_LARGEFILE, mode);}libc_hidden_def(__libc_open64)
终究__libc_open64又调用了__libc_open函数,这个函数在文件libc/sysdeps/linux/common/open.c中界说:
libc_hidden_proto(__libc_open)int __libc_open(const char *file, int oflag, ...){mode_t mode = 0;if (oflag & O_CREAT) {va_list arg;va_start (arg, oflag);mode = va_arg (arg, mode_t);va_end (arg);}return __syscall_open(file, oflag, mode);}libc_hidden_def(__libc_open)
__syscall_open在同一个文件中界说:
static __inline__ _syscall3(int, __syscall_open, const char *, file, int, flags, __kernel_mode_t, mode)
在文件libc/sysdeps/linux/arm/bits/syscalls.h文件中能够看到:
#undef _syscall3#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \type name(type1 arg1,type2 arg2,type3 arg3) \{ \return (type) (INLINE_SYSCALL(name, 3, arg1, arg2, arg3)); \}
这个宏实践上完结界说一个函数的作业,这个宏的榜首个参数是函数的回来值类型,第二个参数是函数名,之后的参数就好像它的参数名所标明的那样,别离是函数的参数类型及参数名。__syscall_open实践上为:
int __syscall_open (const char * file,int flags, __kernel_mode_t mode){return (int) (INLINE_SYSCALL(__syscall_open, 3, file, flags, mode));}
INLINE_SYSCALL为同一个文件中界说的宏:
#undef INLINE_SYSCALL#define INLINE_SYSCALL(name, nr, args...) \({ unsigned int _inline_sys_result = INTERNAL_SYSCALL (name, , nr, args); \if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (_inline_sys_result, ), 0)) \{ \__set_errno (INTERNAL_SYSCALL_ERRNO (_inline_sys_result, )); \_inline_sys_result = (unsigned int) -1; \} \(int) _inline_sys_result; })#undef INTERNAL_SYSCALL#if !defined(__thumb__)#if defined(__ARM_EABI__)#define INTERNAL_SYSCALL(name, err, nr, args...) \({unsigned int __sys_result; \{ \register int _a1 __asm__ ("r0"), _nr __asm__ ("r7"); \LOAD_ARGS_##nr (args) \_nr = SYS_ify(name); \__asm__ __volatile__ ("swi 0x0 @ syscall " #name \: "=r" (_a1) \: "r" (_nr) ASM_ARGS_##nr \: "memory"); \__sys_result = _a1; \} \(int) __sys_result; })#else /* defined(__ARM_EABI__) */#define INTERNAL_SYSCALL(name, err, nr, args...) \({ unsigned int __sys_result; \{ \register int _a1 __asm__ ("a1"); \LOAD_ARGS_##nr (args) \__asm__ __volatile__ ("swi %1 @ syscall " #name \: "=r" (_a1) \: "i" (SYS_ify(name)) ASM_ARGS_##nr \: "memory"); \__sys_result = _a1; \} \(int) __sys_result; })#endif#else /* !defined(__thumb__) *//* We cant use push/pop inside the asm because that breaksunwinding (ie. thread cancellation).*/#define INTERNAL_SYSCALL(name, err, nr, args...) \({ unsigned int __sys_result; \{ \int _sys_buf[2]; \register int _a1 __asm__ ("a1"); \register int *_v3 __asm__ ("v3") = _sys_buf; \*_v3 = (int) (SYS_ify(name)); \LOAD_ARGS_##nr (args) \__asm__ __volatile__ ("str r7, [v3, #4]\n" \"\tldr r7, [v3]\n" \"\tswi 0 @ syscall " #name "\n" \"\tldr r7, [v3, #4]" \: "=r" (_a1) \: "r" (_v3) ASM_ARGS_##nr \: "memory"); \__sys_result = _a1; \} \(int) __sys_result; })#endif /*!defined(__thumb__)*/
这儿也将同文件中的LOAD_ARGS宏的界说贴出来:
#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) \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)
这项宏用来在相应的寄存器中加载相应的参数。SYS_ify宏取得体系调用号
#define SYS_ify(syscall_name) (__NR_##syscall_name)
也便是__NR___syscall_open,在libc/sysdeps/linux/common/open.c中能够看到这个宏的界说:
#define __NR___syscall_open __NR_open
__NR_open在内核代码的头文件中有界说。在r7寄存器中寄存体系调用号,而参数传递好像和一般的函数调用的参数传递也没有什么差异。
在这个当地,得留意那个EABI,EABI是什么东西呢?ABI,Application Binary Interface,运用二进制接口。在较新的EABI标准中,是将体系调用号压入寄存器r7中,而在老的OABI中则是履行的swi中止号的办法,也便是说本来的调用办法(Old ABI)是经过跟随在swi指令中的调用号来进行的。一起这两种调用办法的体系调用号也是存在这差异的,在内核的文件arch/arm/inclue/asm/unistd.h中能够看到:
#define __NR_OABI_SYSCALL_BASE0x900#if defined(__thumb__) defined(__ARM_EABI__)#define __NR_SYSCALL_BASE 0#else#define __NR_SYSCALL_BASE __NR_OABI_SYSCALL_BASE#endif/** This file contains the system call numbers.*/#define __NR_restart_syscall (__NR_SYSCALL_BASE+ 0)#define __NR_exit (__NR_SYSCALL_BASE+ 1)#define __NR_fork (__NR_SYSCALL_BASE+ 2)#define __NR_read (__NR_SYSCALL_BASE+ 3)#define __NR_write (__NR_SYSCALL_BASE+ 4)#define __NR_open (__NR_SYSCALL_BASE+ 5)……
接下来来看操作体系对体系调用的处理。咱们回到ARM Linux的反常向量表,由于当履行swi时,会从反常向量表中取例程的地址然后跳转到相应的处理程序中。在文件arch/arm/kernel/entry-armv.S中:
/** We group all the following data together to optimise* for CPUs with separate I & D caches.*/.align 5.LCvswi:.word vector_swi.globl __stubs_end__stubs_end:.equ stubs_offset, __vectors_start + 0x200 - __stubs_start.globl __vectors_start__vectors_start:ARM( swi SYS_ERROR0 )THUMB( svc #0 )THUMB( nop )W(b) vector_und + stubs_offsetW(ldr) pc, .LCvswi + stubs_offsetW(b) vector_pabt + stubs_offsetW(b) vector_dabt + stubs_offsetW(b) vector_addrexcptn + stubs_offsetW(b) vector_irq + stubs_offsetW(b) vector_fiq + stubs_offset.globl __vectors_end__vectors_end:
而.LCvswi在同一个文件中界说为:
.LCvswi:.word vector_swi
也便是终究会履行例程vector_swi来完结对体系调用的处理,接下来咱们来看下在arch/arm/kernel/entry-common.S中界说的vector_swi例程:
/*=============================================================================* SWI handler*--*//* If were optimising for StrongARM the resulting code wont run on an ARM7 and we can save a couple of instructions. --pb */#ifdef CONFIG_CPU_ARM710#define A710(code...) code.Larm710bug:ldmia sp, {r0 - lr}^ @ Get calling r0 - lrmov r0, r0add sp, sp, #S_FRAME_SIZEsubs pc, lr, #4#else#define A710(code...)#endif.align 5ENTRY(vector_swi)sub sp, sp, #S_FRAME_SIZEstmia sp, {r0 - r12} @ Calling r0 - r12ARM( add r8, sp, #S_PC )ARM( stmdb r8, {sp, lr}^ ) @ Calling sp, lrTHUMB( mov r8, sp )THUMB( store_user_sp_lr r8, r10, S_SP ) @ calling sp, lrmrs r8, spsr @ called from non-FIQ mode, so ok.str lr, [sp, #S_PC] @ Save calling PCstr r8, [sp, #S_PSR] @ Save CPSRstr r0, [sp, #S_OLD_R0] @ Save OLD_R0zero_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_THUMBtst r8, #PSR_T_BITmovne r10, #0 @ no thumb OABI emulationldreq r10, [lr, #-4] @ get SWI instruction#elseldr r10, [lr, #-4] @ get SWI instructionA710( and ip, r10, #0x0f @ check for SWI )A710( teq ip, #0x0f )A710( bne .Larm710bug )#endif#ifdef CONFIG_CPU_ENDIAN_BE8rev r10, r10 @ little endian instruction#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, #0x0f @ check for SWI )A710( teq ip, #0x0f )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_regsaddne scno, r7, #__NR_SYSCALL_BASE @ put OS number inldreq scno, [lr, #-4]#else/* Legacy ABI only. */ldr scno, [lr, #-4] @ get SWI instructionA710( and ip, scno, #0x0f @ check for SWI )A710( teq ip, #0x0f )A710( bne .Larm710bug )#endif#ifdef CONFIG_ALIGNMENT_TRAPldr ip, __cr_alignmentldr ip, [ip]mcr p15, 0, ip, c1, c0 @ update control register#endifenable_irq
//tsk 是寄存器r9的别号,在arch/arm/kernel/entry-header.S中界说:// tsk .req r9 @current thread_info
// 取得线程目标的基地址。
get_thread_info tsk
// tbl是r8寄存器的别号,在arch/arm/kernel/entry-header.S中界说:
// tbl .req r8 @syscall table pointer,
// 用来寄存体系调用表的指针,体系调用表在后边调用
adr tbl, sys_call_table @ load syscall table pointer#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, #0xffeorne scno, r10, #__NR_OABI_SYSCALL_BASEldrne tbl, =sys_oabi_call_table#elif !defined(CONFIG_AEABI) // scno是寄存器r7的别号bic scno, scno, #0xff @ mask off SWI op-codeeor scno, scno, #__NR_SYSCALL_BASE @ check OS number#endifldr r10, [tsk, #TI_FLAGS] @ check for syscall tracingstmdb sp!, {r4, r5} @ push fifth and sixth args#ifdef CONFIG_SECCOMPtst r10, #_TIF_SECCOMPbeq 1fmov r0, scnobl __secure_computing add r0, sp, #S_R0 + S_OFF @ pointer to regsldmia r0, {r0 - r3} @ have to reload r0 - r31:#endiftst r10, #_TIF_SYSCALL_TRACE @ are we tracing syscalls?bne __sys_tracecmp scno, #NR_syscalls @ check upper syscall limitadr lr, BSYM(ret_fast_syscall) @ return addressldrcc pc, [tbl, scno, lsl #2] @ call sys_* routineadd r1, sp, #S_OFF
// why也是r8寄存器的别号
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 backbcs arm_syscall b sys_ni_syscall @ not private funcENDPROC(vector_swi)/** This is the really slow path. Were going to be doing* context switches, and waiting for our parent to respond.*/__sys_trace:mov r2, scnoadd r1, sp, #S_OFFmov r0, #0 @ trace entry [IP = 0]bl syscall_traceadr lr, BSYM(__sys_trace_return) @ return addressmov scno, r0 @ syscall number (possibly new)add r1, sp, #S_R0 + S_OFF @ pointer to regscmp scno, #NR_syscalls @ check upper syscall limitldmccia r1, {r0 - r3} @ have to reload r0 - r3ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routineb 2b__sys_trace_return:str r0, [sp, #S_R0 + S_OFF]! @ save returned r0mov r2, scnomov r1, spmov r0, #1 @ trace exit [IP = 1]bl syscall_traceb ret_slow_syscall.align 5#ifdef CONFIG_ALIGNMENT_TRAP.type __cr_alignment, #object__cr_alignment:.word cr_alignment#endif.ltorg/** This is the syscall table declaration for native ABI syscalls.* With EABI a couple syscalls are obsolete and defined as sys_ni_syscall.*/#define ABI(native, compat) native#ifdef CONFIG_AEABI#define OBSOLETE(syscall) sys_ni_syscall#else#define OBSOLETE(syscall) syscall#endif.type sys_call_table, #objectENTRY(sys_call_table)#include "calls.S"#undef ABI#undef OBSOLETE
上面的zero_fp是一个宏,在arch/arm/kernel/entry-header.S中界说:
.macro zero_fp#ifdef CONFIG_FRAME_POINTERmov fp, #0#endif.endm//而fp位寄存器r11。
像每一个反常处理程序相同,要做的榜首件事当然便是维护现场了。紧接着是取得体系调用的体系调用号。
然后以体系调用号作为索引来查找体系调用表,假如体系调用号正常的话,就会调用相应的处理例程来处理,便是上面的那个ldrccpc, [tbl, scno, lsl #2]句子,然后经过例程ret_fast_syscall来回来。
在这个当地咱们接着来评论ABI的问题。现在,咱们首要来看两个宏,一个是CONFIG_OABI_COMPAT意思是说与old ABI兼容,另一个是CONFIG_AEABI意思是说指定现在的办法为EABI。这两个宏能够一起装备,也能够都不配,也能够装备任何一种。咱们来看一下内核是怎样处理这一问题的。咱们知道,sys_call_table在内核中是个跳转表,这个表中存储的是一系列的函数指针,这些指针便是体系调用函数的指针,如(sys_open)。体系调用是依据一个体系调用号(一般便是表的索引)找到实践该调用内核哪个函数,然后经过运转该函数完结的。
首要,关于old ABI,内核给出的处理是为它树立一个独自的system call table,叫sys_oabi_call_table,这样,兼容办法下就会有两个system call table,以old ABI办法的体系调用会履行old_syscall_table表中的体系调用函数,EABI办法的体系调用会用sys_call_table中的函数指针。
装备无外乎以下4中:
榜首、两个宏都装备行为便是上面说的那样。
第二、只装备CONFIG_OABI_COMPAT,那么以old ABI办法调用的会用sys_oabi_call_table,以EABI办法调用的用sys_call_table,和1实质上是相同的。仅仅状况1愈加清晰。
第三、只装备CONFIG_AEABI体系中不存在sys_oabi_call_table,对old ABI办法调用不兼容。只能 以EABI办法调用,用sys_call_table。
第四、两个都没有装备,体系默许会只允许old ABI办法,可是不存在old_syscall_table,终究会经过sys_call_table完结函数调用
体系会依据ABI的不同而将相应的体系调用表的基地址加载进tbl寄存器,也便是r8寄存器。接下来来看体系调用表,如前面所说的那样,有两个,相同都在文件arch/arm/kernel/entry-common.S中:
/** This is the syscall table declaration for native ABI syscalls.* With EABI a couple syscalls are obsolete and defined as sys_ni_syscall.*/#define ABI(native, compat) native#ifdef CONFIG_AEABI#define OBSOLETE(syscall) sys_ni_syscall#else#define OBSOLETE(syscall) syscall#endif.type sys_call_table, #objectENTRY(sys_call_table)#include "calls.S"#undef ABI#undef OBSOLETE
别的一个为:
/** This is the syscall table declaration for native ABI syscalls.* With EABI a couple syscalls are obsolete and defined as sys_ni_syscall.*/#define ABI(native, compat) native#ifdef CONFIG_AEABI#define OBSOLETE(syscall) sys_ni_syscall#else#define OBSOLETE(syscall) syscall#endif.type sys_call_table, #objectENTRY(sys_call_table)#include "calls.S"#undef ABI#undef OBSOLETE
这样看来形似两个体系调用表是彻底相同的。这儿预处理指令include的共同用法也挺有意思,在体系调用表的内容便是整个arch/arm/kernel/calls.S文件的内容这个文件的内容如下(由于太长,这儿就不悉数列出了):
/** linux/arch/arm/kernel/calls.S** Copyright (C) 1995-2005 Russell King** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License version 2 as* published by the Free Software Foundation.** This file is included thrice in entry-common.S*//* 0 */ CALL(sys_restart_syscall)CALL(sys_exit)CALL(sys_fork_wrapper)CALL(sys_read)CALL(sys_write)/* 5 */ CALL(sys_open)CALL(sys_close)CALL(sys_ni_syscall) /* was sys_waitpid */CALL(sys_creat)CALL(sys_link)...
这个是相同在文件arch/arm/kernel/entry-common.S中的宏CALL()的界说:
.equ NR_syscalls,0#define CALL(x) .equ NR_syscalls,NR_syscalls+1#include "calls.S"#undef CALL#define CALL(x) .long x
终究再罗嗦一点,假如用sys_open来搜的话,是搜不到体系调用open的界说的,体系调用函数都是用宏来界说的,比方关于open,在文件fs/open.c文件中这样界说:
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode){long ret;if (force_o_largefile())flags = O_LARGEFILE;ret = do_sys_open(AT_FDCWD, filename, flags, mode);/* avoid REGPARM breakage on x86: */asmlinkage_protect(3, ret, filename, flags, mode);return ret;}
持续回到vector_swi,而假如体系调用号不正确,则会调用arm_syscall函数来进行处理,这个函数在文件arch/arm/kernel/traps.c中界说:
/** Handle all unrecognised system calls.* 0x9f0 - 0x9fffff are some more esoteric system calls*/#define NR(x) ((__ARM_NR_##x) - __ARM_NR_BASE)asmlinkage int arm_syscall(int no, struct pt_regs *regs){struct thread_info *thread = current_thread_info();siginfo_t info;if ((no >> 16) != (__ARM_NR_BASE>> 16))return bad_syscall(no, regs);switch (no & 0xffff) {case 0: /* branch through 0 */info.si_signo = SIGSEGV;info.si_errno = 0;info.si_code = SEGV_MAPERR;info.si_addr = NULL;arm_notify_die("branch through zero", regs, &info, 0, 0);return 0;case NR(breakpoint): /* SWI BREAK_POINT */regs->ARM_pc -= thumb_mode(regs) ? 2 : 4;ptrace_break(current, regs);return regs->ARM_r0;/** Flush a region from virtual address r0 to virtual address r1* _exclusive_. There is no alignment requirement on either address;* user space does not need to know the hardware cache layout.** r2 contains flags. It should ALWAYS be passed as ZERO until it* is defined to be something else. For now we ignore it, but may* the fires of hell burn in your belly if you break this rule. ;)** (at a later date, we may want to allow this call to not flush* various aspects of the cache. Passing 0 will guarantee that* everything necessary gets flushed to maintain consistency in* the specified region).*/case NR(cacheflush):do_cache_op(regs->ARM_r0, regs->ARM_r1, regs->ARM_r2);return 0;case NR(usr26):if (!(elf_hwcap & HWCAP_26BIT))break;regs->ARM_cpsr &= ~MODE32_BIT;return regs->ARM_r0;case NR(usr32):if (!(elf_hwcap & HWCAP_26BIT))break;regs->ARM_cpsr = MODE32_BIT;return regs->ARM_r0;case NR(set_tls):thread->tp_value = regs->ARM_r0;if (tls_emu)return 0;if (has_tls_reg) {asm ("mcr p15, 0, %0, c13, c0, 3": : "r" (regs->ARM_r0));} else {/** User space must never try to access this directly.* Expect your app to break eventually if you do so.* The user helper at 0xffff0fe0 must be used instead.* (see entry-armv.S for details)*/*((unsigned int *)0xffff0ff0) = regs->ARM_r0;}return 0;#ifdef CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG/** Atomically store r1 in *r2 if *r2 is equal to r0 for user space.* Return zero in r0 if *MEM was changed or non-zero if no exchange* happened. Also set the user C flag accordingly.* If access permissions have to be fixed up then non-zero is* returned and the operation has to be re-attempted.** *NOTE*: This is a ghost syscall private to the kernel. Only the* __kuser_cmpxchg code in entry-armv.S should be aware of its* existence. Dont ever use this from user code.*/case NR(cmpxchg):for (;;) {extern void do_DataAbort(unsigned long addr, unsigned int fsr,struct pt_regs *regs);unsigned long val;unsigned long addr = regs->ARM_r2;struct mm_struct *mm = current->mm;pgd_t *pgd; pmd_t *pmd; pte_t *pte;spinlock_t *ptl;regs->ARM_cpsr &= ~PSR_C_BIT;down_read(&mm->mmap_sem);pgd = pgd_offset(mm, addr);if (!pgd_present(*pgd))goto bad_access;pmd = pmd_offset(pgd, addr);if (!pmd_present(*pmd))goto bad_access;pte = pte_offset_map_lock(mm, pmd, addr, &ptl);if (!pte_present(*pte) !pte_dirty(*pte)) {pte_unmap_unlock(pte, ptl);goto bad_access;}val = *(unsigned long *)addr;val -= regs->ARM_r0;if (val == 0) {*(unsigned long *)addr = regs->ARM_r1;regs->ARM_cpsr = PSR_C_BIT;}pte_unmap_unlock(pte, ptl);up_read(&mm->mmap_sem);return val;bad_access:up_read(&mm->mmap_sem);/* simulate a write access fault */do_DataAbort(addr, 15 + (1 << 11), regs);}#endifdefault:/* Calls 9f00xx..9f07ff are defined to return -ENOSYSif not implemented, rather than raising SIGILL. Thisway the calling program can gracefully determine whethera feature is supported. */if ((no & 0xffff) <= 0x7ff)return -ENOSYS;break;}#ifdef CONFIG_DEBUG_USER/** experience shows that these seem to indicate that* something catastrophic has happened*/if (user_debug & UDBG_SYSCALL) {printk("[%d] %s: arm syscall %d\n",task_pid_nr(current), current->comm, no);dump_instr("", regs);if (user_mode(regs)) {__show_regs(regs);c_backtrace(regs->ARM_fp, processor_mode(regs));}}#endifinfo.si_signo = SIGILL;info.si_errno = 0;info.si_code = ILL_ILLTRP;info.si_addr = (void __user *)instruction_pointer(regs) -(thumb_mode(regs) ? 2 : 4);arm_notify_die("Oops - bad syscall(2)", regs, &info, no, 0);return 0;}
还有那个sys_ni_syscall,这个函数在kernel/sys_ni.c中界说,它的效果好像也仅仅是要给用户空间回来过错码ENOSYS。
/* we cant #includehere,but tell gcc to not warn with -Wmissing-prototypes */asmlinkage long sys_ni_syscall(void);/** Non-implemented system calls get redirected here.*/asmlinkage long sys_ni_syscall(void){return -ENOSYS;}
体系调用号正确也好不正确也好,终究都是经过ret_fast_syscall例程来回来,相同在arch/arm/kernel/entry-common.S文件中:
.align 5/** This is the fast syscall return path. We do as little as* possible here, and this includes saving r0 back into the SVC* stack.*/ret_fast_syscall:UNWIND(.fnstart )UNWIND(.cantunwind )disable_irq @ disable interruptsldr r1, [tsk, #TI_FLAGS]tst r1, #_TIF_WORK_MASKbne fast_work_pending#if defined(CONFIG_IRQSOFF_TRACER)asm_trace_hardirqs_on#endif/* perform architecture specific actions before user return */arch_ret_to_user r1, lrrestore_user_regs fast = 1, offset = S_OFFUNWIND(.fnend )
四.声明体系调用的相关宏
linux下的体系调用函数界说接口:
1.SYSCALL_DEFINE1~6(include/linux/syscalls.h)
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
2.SYSCALL_DEFINEx
#ifdef CONFIG_FTRACE_SYSCALLS#define SYSCALL_DEFINEx(x, sname, ...) \static const char *types_##sname[] = { \__SC_STR_TDECL##x(__VA_ARGS__) \}; \static const char *args_##sname[] = { \__SC_STR_ADECL##x(__VA_ARGS__) \}; \SYSCALL_METADATA(sname, x); \__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)#else#define SYSCALL_DEFINEx(x, sname, ...) \__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)#endif
3.__SYSCALL_DEFINEx
#ifdef CONFIG_HAVE_SYSCALL_WRAPPERS#define SYSCALL_DEFINE(name) static inline long SYSC_##name#define __SYSCALL_DEFINEx(x, name, ...) \asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__)); \static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__)); \asmlinkage long SyS##name(__SC_LONG##x(__VA_ARGS__)) \{ \__SC_TEST##x(__VA_ARGS__); \return (long) SYSC##name(__SC_CAST##x(__VA_ARGS__)); \} \SYSCALL_ALIAS(sys##name, SyS##name); \static inline long SYSC##name(__SC_DECL##x(__VA_ARGS__))#else /* CONFIG_HAVE_SYSCALL_WRAPPERS */#define SYSCALL_DEFINE(name) asmlinkage long sys_##name#define __SYSCALL_DEFINEx(x, name, ...) \asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))#endif /* CONFIG_HAVE_SYSCALL_WRAPPERS */
4.__SC_最初的宏
#define __SC_DECL1(t1, a1) t1 a1#define __SC_DECL2(t2, a2, ...) t2 a2, __SC_DECL1(__VA_ARGS__)#define __SC_DECL3(t3, a3, ...) t3 a3, __SC_DECL2(__VA_ARGS__)#define __SC_DECL4(t4, a4, ...) t4 a4, __SC_DECL3(__VA_ARGS__)#define __SC_DECL5(t5, a5, ...) t5 a5, __SC_DECL4(__VA_ARGS__)#define __SC_DECL6(t6, a6, ...) t6 a6, __SC_DECL5(__VA_ARGS__)#define __SC_LONG1(t1, a1) long a1#define __SC_LONG2(t2, a2, ...) long a2, __SC_LONG1(__VA_ARGS__)#define __SC_LONG3(t3, a3, ...) long a3, __SC_LONG2(__VA_ARGS__)#define __SC_LONG4(t4, a4, ...) long a4, __SC_LONG3(__VA_ARGS__)#define __SC_LONG5(t5, a5, ...) long a5, __SC_LONG4(__VA_ARGS__)#define __SC_LONG6(t6, a6, ...) long a6, __SC_LONG5(__VA_ARGS__)#define __SC_CAST1(t1, a1) (t1) a1#define __SC_CAST2(t2, a2, ...) (t2) a2, __SC_CAST1(__VA_ARGS__)#define __SC_CAST3(t3, a3, ...) (t3) a3, __SC_CAST2(__VA_ARGS__)#define __SC_CAST4(t4, a4, ...) (t4) a4, __SC_CAST3(__VA_ARGS__)#define __SC_CAST5(t5, a5, ...) (t5) a5, __SC_CAST4(__VA_ARGS__)#define __SC_CAST6(t6, a6, ...) (t6) a6, __SC_CAST5(__VA_ARGS__)...
5.针对SYSCALL_DEFINE1(close, unsigned int, fd)来剖析一下
SYSCALL_DEFINE1(close, unsigned int, fd)依据#define SYSCALL_DEFINE1(name, …) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
化简SYSCALL_DEFINEx(1, _close, __VA_ARGS__)【 ##是连接符的意思】,依据SYSCALL_DEFINEx的界说
化简__SYSCALL_DEFINEx(1, _close, __VA_ARGS__) 依据__SYSCALL_DEFINEx的界说
#define __SYSCALL_DEFINEx(1, _close, ...) \asmlinkage long sys_close(__SC_DECL1(__VA_ARGS__)); \static inline long SYSC_close(__SC_DECL1(__VA_ARGS__)); \asmlinkage long SyS_close(__SC_LONG1(__VA_ARGS__)) \{ \__SC_TEST1(__VA_ARGS__); \return (long) SYSC_close(__SC_CAST1(__VA_ARGS__)); \} \SYSCALL_ALIAS(sys_close, SyS_close); \static inline long SYSC_close(__SC_DECL1(__VA_ARGS__))
这儿__VA_ARGS__是可变参数宏,能够以为等于unsigned int, fd
依据__SC_宏化简
#define __SYSCALL_DEFINEx(1, _close, ...) \asmlinkage long sys_close(unsigned int fd); \static inline long SYSC_close(unsigned int fd); \asmlinkage long SyS_close(long fd)) \{ \BUILD_BUG_ON(sizeof(unsigned int) > sizeof(long)) \return (long) SYSC_close((unsigned int)fd); \} \SYSCALL_ALIAS(sys_close, SyS_close); \static inline long SYSC_close(unsigned int fd)
声明晰sys_close函数
界说了SyS_close函数,函数体调用SYSC_close函数,并回来其回来值
SYSCALL_ALIAS宏
#define SYSCALL_ALIAS(alias, name) \asm ("\t.globl " #alias "\n\t.set " #alias ", " #name)
刺进汇编代码 让履行sys_close等同于履行SYS_close
#define SYSCALL_ALIAS(alias, name) \asm ("\t.globl " #alias "\n\t.set " #alias ", " #name)
【#是预处理的意思】
BUILD_BUG_ON宏是个过错判断检测的功用
终究一句是SYSC_close的函数界说
所以在SYSCALL_DEFINE1宏界说后边紧跟的是{}包围起来的函数体
6.依据5的解析可推断出
SYSCALL_DEFINE1的1代表的是sys_close的参数个数为1
同理SYSCALL_DEFINE?的/代表的是sys_name的参数为?个
7.体系调用函数的界说用SYSCALL_DEFINE宏润饰
体系调用函数的外部声明在include/linux/Syscalls.h头文件中
5 增加新的体系调用
榜首、翻开arch/arm/kernel/calls.S,在终究增加体系调用的函数原型的指针,例如:
CALL(sys_set_senda)
弥补阐明一点关于NR_syscalls的东西,这个常量标明体系调用的总的个数,在较新版别的内核中,文件arch/arm/kernel/entry-common.S中能够找到:
.equ NR_syscalls,0#define CALL(x) .equ NR_syscalls,NR_syscalls+1#include "calls.S"#undef CALL#define CALL(x) .long x
适当的奇妙,不是吗?在体系调用表中每增加一个体系调用,NR_syscalls就主动增加一。在这个当地先求出NR_syscalls,然后从头界说CALL(x)宏,这样也能够不影响文件后边体系调用表的树立。
第二、翻开include/asm-arm/unistd.h,增加体系调用号的宏,感觉这步能够省掉,由于这个当地界说的体系调用号主要是个C库,比方uClibc、Glibc用的。例如:
#define __NR_plan_set_senda (__NR_SYSCALL_BASE+365)
为了向后兼容,体系调用只能增加而不能削减,这儿的编号增加时,也有必要按次序来。否则会导致中心运转过错。
第三,实例化该体系调用,即编写新增加体系调用的完结例如:
SYSCALL_DEFINE1(set_senda, int,iset){if(iset)UART_PUT_CR(&at91_port[2],AT91C_US_SENDA);elseUART_PUT_CR(&at91_port[2],AT91C_US_RSTSTA);return 0;}
第四、翻开include/linux/syscalls.h增加函数声明
asmlinkage long sys_set_senda(int iset);
第五、在运用程序中调用该体系调用,能够参阅uClibc的完结。
第六、完毕。