你知道Arm Linux系统调用流程?

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——16777215之间的整数。
使用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 can't use push/pop inside the asm because that breaks unwinding (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_base0x900000#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_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在同一个文件中定义为:
.lcvswi: .word vector_swi
也就是最终会执行例程vector_swi来完成对系统调用的处理,接下来我们来看下在arch/arm/kernel/entry-common.s中定义的vector_swi例程:
/*============================================================================= * swi handler *----------------------------------------------------------------------------- */ /* if we're optimising for strongarm the resulting code won't 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 - lr mov r0, r0 add sp, sp, #s_frame_size subs pc, lr, #4#else#define a710(code...)#endif .align 5entry(vector_swi) sub sp, sp, #s_frame_size stmia sp, {r0 - r12} @ calling r0 - r12 arm( add r8, sp, #s_pc ) arm( stmdb r8, {sp, lr}^ ) @ calling sp, lr 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. 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#else ldr r10, [lr, #-4] @ get swi instruction a710( and ip, r10, #0x0f000000 @ check for swi ) a710( teq ip, #0x0f000000 ) a710( bne .larm710bug )#endif#ifdef config_cpu_endian_be8 rev 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, #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 ldreq scno, [lr, #-4]#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
//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, #0xff000000 eorne scno, r10, #__nr_oabi_syscall_base ldrne tbl, =sys_oabi_call_table#elif !defined(config_aeabi)// scno是寄存器r7的别名 bic scno, scno, #0xff000000 @ mask off swi op-code eor scno, scno, #__nr_syscall_base @ check os number#endif ldr r10, [tsk, #ti_flags] @ check for syscall tracing stmdb sp!, {r4, r5} @ push fifth and sixth args#ifdef config_seccomp tst r10, #_tif_seccomp beq 1f mov r0, scno bl __secure_computing add r0, sp, #s_r0 + s_off @ pointer to regs ldmia r0, {r0 - r3} @ have to reload r0 - r31:#endif tst r10, #_tif_syscall_trace @ are we tracing syscalls? bne __sys_trace 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 add 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 back bcs arm_syscall b sys_ni_syscall @ not private funcendproc(vector_swi) /* * this is the really slow path. we're going to be doing * context switches, and waiting for our parent to respond. */__sys_trace: mov r2, scno add r1, sp, #s_off mov r0, #0 @ trace entry [ip = 0] bl syscall_trace adr lr, bsym(__sys_trace_return) @ return address mov scno, r0 @ syscall number (possibly new) add r1, sp, #s_r0 + s_off @ pointer to regs cmp scno, #nr_syscalls @ check upper syscall limit ldmccia r1, {r0 - r3} @ have to reload r0 - r3 ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine b 2b__sys_trace_return: str r0, [sp, #s_r0 + s_off]! @ save returned r0 mov r2, scno mov r1, sp mov r0, #1 @ trace exit [ip = 1] bl syscall_trace b 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_pointer mov 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. * 0x9f0000 - 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. don't 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 还有那个sys_ni_syscall,这个函数在kernel/sys_ni.c中定义,它的作用似乎也仅仅是要给用户空间返回错误码enosys。
/* we can't #include here, 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 interrupts ldr r1, [tsk, #ti_flags] tst r1, #_tif_work_mask bne 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, lr restore_user_regs fast = 1, offset = s_off unwind(.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); else uart_put_cr(&at91_port[2],at91c_us_rststa); return 0;}
第四、打开include/linux/syscalls.h添加函数声明
asmlinkage long sys_set_senda(int iset);
第五、在应用程序中调用该系统调用,可以参考uclibc的实现。
第六、结束。

纯电动汽车为何买的人越来越少
银锌电池即将替代现有锂离子电池
触摸屏全贴合的好处都有哪些
USB充电手电筒的制作
二极管检波电路故障检测问题该怎么解决?
你知道Arm Linux系统调用流程?
日本川崎重工推出了新型协作机器人系统,2019年开始全面发售
协鑫能科发布2020年一季报业绩预告 盈利获显著提高
韩国 LG Display 宣布:位于广州的LG OLED工厂将会在2019年下半年投产
汽车胎压监测系统发射模块设计
射频通信系统的组成及电路图
手机芯片市场竞争加剧 高通联发科频频出招
异或门(XOR Gate)的基础知识
低压接地电阻柜的工作原理是什么
汽车底盘防尘罩是如何进行气密性防水检测的
云计算的量子计算服务如何工作?
边缘计算为实时数据分析和建模提供新的机会
一个典型的高端病人监护仪系统方案解析
如何将器件库迁移至DigiPCBA
VR体验店,赛车模拟器给你最真实的赛车体验