ARM Linux系统调用的原理

发布者:chinapxf最新更新时间:2016-06-15 来源: eefocus关键字:ARM  Linux  系统调用 手机看文章 扫描二维码
随时随地手机看文章
操作系统为在用户态运行的进程与硬件设备进行交互提供了一组接口。在应用程序和硬件之间设置一个额外层具有很多优点。首先,这使得编程更加容易,把 用户从学习硬件设备的低级编程特性中解放出来。其次,这极大地提高了系统的安全性,因为内核在试图满足某个请求之前在接口级就可以检查这种请求的正确性。 最后,更重要的是这些接口使得程序具有可移植性,因为只要内核所提供的一组接口相同,那么在任一内核之上就可以正确地编译和执行程序。

 

Unix系统通过向内核发出系统调用(systemcall)实现了用户态进程和硬件设备之间的大部分接口。系统调用是操作系统提供的服务,用户程序通过各种系统调用,来引用内核提供的各种服务,系统调用的执行让用户程序陷入内核,该陷入动作由swi软中断完成。

 

应用编程接口(API)与系统调用的不同在于,前者只是一个函数定义,说明了如何获得一个给定的服务,而后者是通过软件中断向内核发出的一个明确的 请求。POSIX标准针对API,而不针对系统调用。Unix系统给程序员提供了很多API库函数。libc的标准c库所定义的一些API引用了封装例程 (wrapper routine)(其唯一目的就是发布系统调用)。通常情况下,每个系统调用对应一个封装例程,而封装例程定义了应用程序使用的API。反之则不然,一个 API没必要对应一个特定的系统调用。从编程者的观点看,API和系统调用之间的差别是没有关系的:唯一相关的事情就是函数名、参数类型及返回代码的含 义。然而,从内核设计者的观点看,这种差别确实有关系,因为系统调用属于内核,而用户态的库函数不属于内核。

 

大部分封装例程返回一个整数,其值的含义依赖于相应的系统调用。返回-1通常表示内核不能满足进程的请求。系统调用处理程序的失败可能是由无效参数 引起的,也可能是因为缺乏可用资源,或硬件出了问题等等。在libc库中定义的errno变量包含特定的出错码,每个出错码定义为一个常量宏。

 

当用户态的进程调用一个系统调用时,CPU切换到内核态并开始执行一个内核函数。因为内核实现了很多不同的系统调用,因此进程必须传递一个名为系统 调用号(system call number)的参数来识别所需的系统调用。所有的系统调用核都返回一个整数值。这些返回值与封装例程返回值的约定是不同的。在内中,整数或0表示系统调 用成功结束,而负数表示一个出错条件。在后一种情况下,这个值就是存放在errno变量中必须返回给应用程序的负出错码。

 

ARM Linux系统利用SWI指令来从用户空间进入内核空间,还是先让我们了解下这个SWI指令吧。SWI指令用于产生软件中断,从而实现从用户模式到管理模 式的变换,CPSR保存到管理模式的SPSR,执行转移到SWI向量。在其他模式下也可使用SWI指令,处理器同样地切换到管理模式。指令格式如下:

SWI{cond} immed_24

其中:

immed_24  24位立即数,值为从0——16215之间的整数。

使用SWI指令时,通常使用以下两种方法进行参数传递,SWI异常处理程序可以提供相关的服务,这两种方法均是用户软件协定。

1)、指令中24位的立即数指定了用户请求的服务类型,参数通过通用寄存器传递。SWI异常处理程序要通过读取引起软件中断的SWI指令,以取得24为立即数。如:

MOV R0,#34

SWI 12

2)、指令中的24位立即数被忽略,用户请求的服务类型由寄存器R0的值决定,参数通过其他的通用寄存器传递。如:

MOV R0, #12

MOV R1, #34

SWI 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_listarg;

   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, intoflag, ...)

{

   mode_tmode = 0;

 

   if(oflag & O_CREAT) {

      va_listarg;

      va_start(arg, oflag);

      mode= va_arg (arg, mode_t);

      va_end (arg);

   }

 

   return__syscall_open(file, oflag, mode);

}

libc_hidden_def(__libc_open)

这个函数,也是仅仅根据打开标志oflag的值,来判断是否有第三个参数,若由,则获得其值。之后,便用获得的参数来调用__syscall_open(file,oflag, mode)。

 

__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,type3arg3) \

{ \

return (type) (INLINE_SYSCALL(name, 3,arg1, arg2, arg3)); \

}

这个宏实际上完成定义一个函数的工作,宏的第一个参数是函数的返回值类型,第二个参数是函数名,之后的参数就如同它们的参数名所表明的那样,分别是函数的参数类型及参数名。__syscall_open实际上为:

int __syscall_open (const char * file,intflags, __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; })

 

INLINE_SYSCALL宏中最值得注意的是INTERNAL_SYSCALL,其定义如下:

#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

 

这里也将同文件中的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)

这几个宏用来在寄存器中加载相应的参数,参数传递的方式和普通的C函数也没有什么太大的区别,同样都是将参数列表中的参数依次放入寄存器r0、r1、r2、r3…中。

 

上面的SYS_ify(name)宏,是用来获得系统调用号的。

#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在内核代码的头文件中有定义。

 

在这里我们忽略定义__thumb__的情况,而假设我们编译出来的库函数使用的都是ARM指令集。在上面的代码中,我们看到,根据是否定义宏__ARM_EABI__,INTERNAL_SYSCALL会被展开为两种不同的版本。关于这一点,与应用二进制接口ABI有关,不同的ABI,则会有不同的传递系统调用号的方法。对于比较新的EABI,则在r7寄存器保存系统调用号,通过swi   0x0来陷入内核。否则,通过swi指令的24位立即数参数来传递系统调用号。后面还会有内核中关于这个问题的更详细的说明。

 

同时这两种调用方式的系统调用号也是存在这区别的,在内核的文件arch/arm/inclue/asm/unistd.h中可以看到:

#define __NR_OABI_SYSCALL_BASE 0x900

 

#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)

……

 

接下来来看操作系统对系统调用的处理。我们回到ARMLinux的异常向量表,因为当执行swi时,会从异常向量表中取例程的地址从而跳转到相应的处理程序中。在文件arch/arm/kernel/entry-armv.S中我们看到SWI异常向量:

   W(ldr) pc,.LCvswi + stubs_offset

而.LCvswi在同一个文件中定义为:

.LCvswi:

   .word vector_swi

 

也就是最终会执行例程vector_swi来完成对系统调用的处理,接下来我们来看下在arch/arm/kernel/entry-common.S中定义的vector_swi例程(删去一些和我们的示例平台无关的代码):

   .align 5

ENTRY(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

   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 needto look at the swi

    * value to determine if it is an EABI or anold ABI call.

    */

   ldr   r10, [lr, #-4]        @ get SWI instruction

#ifdef CONFIG_CPU_ENDIAN_BE8

//rev指令的功能是反转字中的字节序

   rev   r10, r10        @little endian instruction

#endif

 

#elif defined(CONFIG_AEABI)

#else

 

   /*Legacy ABI only. */

   ldr   scno, [lr, #-4]       @ get SWI instruction

 

#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_infotsk

 

   // tbl是r8寄存器的别名,在arch/arm/kernel/entry-header.S中定义:

   // tbl  .req   r8     @syscall table pointer,

// 用来存放系统调用表的指针,系统调用表在后面调用

   adr   tbl, sys_call_table      @ load syscall table pointer

   ldr   ip, [tsk, #TI_FLAGS]     @ check for syscall tracing

 

#if defined(CONFIG_OABI_COMPAT)

   /*

    * If the swi argument is zero, this is an EABIcall and we do nothing.

    *

    * If this is an old ABI call, get the syscallnumber into scno and

    * get the old ABI syscall table address.

    */

   bics  r10, r10, #0xff

   eorne scno, r10, #__NR_OABI_SYSCALL_BASE

   ldrne tbl, =sys_oabi_call_table

#elif !defined(CONFIG_AEABI)

    // scno是寄存器r7的别名

   bic   scno, scno, #0xff     @ mask off SWI op-code

   eor   scno, scno, #__NR_SYSCALL_BASE @ check OS number

#endif

 

   stmdb sp!, {r4, r5}         @push fifth and sixth args

   tst   ip, #_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 func

ENDPROC(vector_swi)

 

上面的zero_fp是一个宏,在arch/arm/kernel/entry-header.S中定义:

   .macro zero_fp

#ifdef CONFIG_FRAME_POINTER

   mov   fp, #0

#endif

   .endm

而fp位寄存器r11。

 

像每一个异常处理程序一样,要做的第一件事当然就是保护现场了。紧接着是获得系统调用的系统调用号。然后以系统调用号作为索引来查找系统调用表,如果系统调用号正常的话,就会调用相应的处理例程来处理,就是上面的那个ldrcc  pc, [tbl, scno, lsl #2]语句,然后通过例程ret_fast_syscall来返回。

 

在这个地方我们接着来讨论ABI的问题。现在,我们首先来看两个宏,一个是CONFIG_OABI_COMPAT 意思是说与old ABI兼容,另一个是CONFIG_AEABI 意思是说指定现在的方式为EABI。这两个宏可以同时配置,也可以都不配,也可以配置任何一种。我们来看一下内核是怎么处理这一问题的。我们知 道,sys_call_table 在内核中是个跳转表,这个表中存储的是一系列的函数指针,这些指针就是系统调用函数的指针,如(sys_open)。内核是根据一个系统调用号(对于 EABI来说为系统调用表的索引)找到实际该调用内核哪个函数,然后通过运行该函数完成系统调用的。
 

    首先,对于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,那么以oldABI方式调用的会用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-armv.S中:

#define ABI(native, compat) native

#ifdef CONFIG_AEABI

#define OBSOLETE(syscall)sys_ni_syscall

#else

#define OBSOLETE(syscall) syscall

#endif

 

   .type sys_call_table, #object

ENTRY(sys_call_table)

#include "calls.S"

#undef ABI

#undef OBSOLETE

 

另外一个为:

#define ABI(native, compat) compat

#define OBSOLETE(syscall) syscall

 

   .type sys_oabi_call_table, #object

ENTRY(sys_oabi_call_table)

#include "calls.S"

#undef ABI

#undef OBSOLETE

这样看来貌似两个系统调用表是完全一样的。这里预处理指令include的独特用法也挺有意思,系统调用表的内容就是整个arch/arm/kernel/calls.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()是个宏,它同样在文件arch/arm/kernel/entry-armv.S中定义:

#define CALL(x) .equNR_syscalls,NR_syscalls+1

#include "calls.S"

#undef CALL

#define CALL(x) .long x

在定义宏CALL()的地方,我们看到calls.S已经被包含了一次,只不过在这里,不是为了建立系统调用表,而仅仅是为了获得系统的系统调用的数量,并保存在宏NR_syscalls中。在SWI向量中,我们也看到,是使用了这个宏的。

 

最后再罗嗦一点,如果用sys_open来搜的话,是搜不到系统调用open的定义的,系统调用函数都是用宏来定义的,比如对于open,有这样的定义:

 

fs/open.c

1066 SYSCALL_DEFINE3(open, const char__user *, filename, int, flags, int, mode)

1067 {

1068         long ret;

1069

1070         if (force_o_largefile())

1071                 flags = O_LARGEFILE;

1072

1073         ret = do_sys_open(AT_FDCWD, filename,flags, mode);

1074         /* avoid REGPARM breakage on x86: */

1075         asmlinkage_protect(3, ret, filename,flags, mode);

1076         return ret;

1077 }

 

 

继续回到vector_swi,如果系统调用号不正确,则会调用arm_syscall函数来进行处理,这个函数定义如下:

 

arch/arm/kernel/traps.c

465 #define NR(x) ((__ARM_NR_##x) -__ARM_NR_BASE)

466 asmlinkage int arm_syscall(int no,struct pt_regs *regs)

467 {

468  struct thread_info *thread = current_thread_info();

469  siginfo_t info;

470

471  if ((no >> 16) != (__ARM_NR_BASE>> 16))

472        return bad_syscall(no, regs);

473

474  switch (no & 0xffff) {

475  case 0: /* branch through 0 */

476        info.si_signo = SIGSEGV;

477        info.si_errno = 0;

478        info.si_code  = SEGV_MAPERR;

479        info.si_addr  = NULL;

480

481        arm_notify_die("branch throughzero", regs, &info, 0, 0);

482        return 0;

483

484  case NR(breakpoint): /* SWI BREAK_POINT */

485        regs->ARM_pc -= thumb_mode(regs) ? 2: 4;

486        ptrace_break(current, regs);

487        return regs->ARM_r0;

488

489 /*

490  * Flush a region from virtual address 'r0' to virtual address 'r1'

491 * _exclusive_.  There is no alignment requirement on eitheraddress;

492  * user space does not need to know the hardware cache layout.

493  *

494  * r2 contains flags.  It shouldALWAYS be passed as ZERO until it

495  * is defined to be something else. For now we ignore it, but may

496  * the fires of hell burn in your belly if you break this rule. ;)

497  *

498  * (at a later date, we may want to allow this call to not flush

499  * various aspects of the cache. Passing '' will guarantee that

500  * everything necessary gets flushed to maintain consistency in

501  * the specified region).

502  */

503  case NR(cacheflush):

504        do_cache_op(regs->ARM_r0,regs->ARM_r1, regs->ARM_r2);

505        return 0;

506

507  case NR(usr26):

508        if (!(elf_hwcap & HWCAP_26BIT))

509             break;

510        regs->ARM_cpsr &= ~MODE32_BIT;

511        return regs->ARM_r0;

512

513  case NR(usr32):

514        if (!(elf_hwcap & HWCAP_26BIT))

515             break;

516        regs->ARM_cpsr = MODE32_BIT;

517        return regs->ARM_r0;

518

519  case NR(set_tls):

520        thread->tp_value = regs->ARM_r0;

521 #if defined(CONFIG_HAS_TLS_REG)

522        asm ("mcr p15, 0, %0, c13, c0,3" : : "r" (regs->ARM_r0) );

523 #elif !defined(CONFIG_TLS_REG_EMUL)

524        /*

525         * User space must never try to accessthis directly.

526         * Expect your app to break eventuallyif you do so.

527         * The user helper at 0xffff0fe0 mustbe used instead.

528         * (see entry-armv.S for details)

529         */

530        *((unsigned int *)0xffff0ff0) =regs->ARM_r0;

531 #endif

532        return 0;

533

534 #ifdef CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG

535 /*

536  * Atomically store r1 in*r2 if *r2 is equal to r0 for user space.

537  * Return zero in r0 if *MEM was changed or non-zero if no exchange

538  * happened.  Also set the user Cflag accordingly.

539  * If access permissions have to be fixed up then non-zero is

540  * returned and the operation has to be re-attempted.

541  *

542  * *NOTE*: This is a ghost syscall private to the kernel.  Only the

543  * __kuser_cmpxchg code in entry-armv.S should be aware of its

544  * existence.  Don't ever use thisfrom user code.

545  */

546 case NR(cmpxchg):

547 for (;;) {

548     extern void do_DataAbort(unsigned long addr, unsigned int fsr,

549                             struct pt_regs*regs);

550     unsigned long val;

551     unsigned long addr = regs->ARM_r2;

552     struct mm_struct *mm = current->mm;

553     pgd_t *pgd; pmd_t *pmd; pte_t *pte;

554     spinlock_t *ptl;

 

556     regs->ARM_cpsr &= ~PSR_C_BIT;

557     down_read(&mm->mmap_sem);

558     pgd = pgd_offset(mm, addr);

559     if (!pgd_present(*pgd))

560            goto bad_access;

561     pmd = pmd_offset(pgd, addr);

562     if (!pmd_present(*pmd))

563             goto bad_access;

564     pte = pte_offset_map_lock(mm, pmd, addr, &ptl);

565     if (!pte_present(*pte) !pte_dirty(*pte)) {

566             pte_unmap_unlock(pte, ptl);

567             goto bad_access;

568     }

569     val = *(unsigned long *)addr;

570     val -= regs->ARM_r0;

571     if (val == 0) {

572             *(unsigned long *)addr =regs->ARM_r1;

573             regs->ARM_cpsr = PSR_C_BIT;

574     }

575     pte_unmap_unlock(pte, ptl);

576     up_read(&mm->mmap_sem);

577     return val;

578

579     bad_access:

580     up_read(&mm->mmap_sem);

581     /* simulate a write access fault */

582     do_DataAbort(addr, 15 + (1 << 11), regs);

583  }

584 #endif

585

586  default:

587        /* Calls 9f00xx..9f07ff are defined to return -ENOSYS

588           if not implemented, rather thanraising SIGILL.  This

589           way the calling program can gracefullydetermine whether

590           a feature is supported. */

591        if ((no & 0xffff) <= 0x7ff)

592             return -ENOSYS;

593        break;

594  }

595 #ifdef CONFIG_DEBUG_USER

596  /*

597   * experience shows that these seem to indicate that

598   * something catastrophic has happened

599   */

600  if (user_debug & UDBG_SYSCALL) {

601        printk("[%d] %s: arm syscall%d\n",

602           task_pid_nr(current),current->comm, no);

603        dump_instr("", regs);

604        if (user_mode(regs)) {

605             __show_regs(regs);

606             c_backtrace(regs->ARM_fp, processor_mode(regs));

607        }

608  }

609 #endif

610  info.si_signo = SIGILL;

611  info.si_errno = 0;

612  info.si_code  = ILL_ILLTRP;

613  info.si_addr  = (void __user*)instruction_pointer(regs) -

614             (thumb_mode(regs) ? 2 : 4);

615

616  arm_notify_die("Oops - bad syscall(2)", regs, &info, no,0);

617  return 0;

618 }

 

这个函数处理所有的辨别不出来的系统调用。系统调用号正确也好不正确也好,最终都是通过ret_fast_syscall例程来返回,因为我们看到,在进入系统调用处理函数之前,先加载了符号ret_fast_syscall进lr寄存器。ret_fast_syscall定义如下:

 

arch/arm/kernel/entry-common.S

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

 

   /*perform architecture specific actions before user return */

   arch_ret_to_userr1, lr

 

   restore_user_regsfast = 1, offset = S_OFF

 UNWIND(.fnend     )

fast_work_pending:

   str   r0, [sp, #S_R0+S_OFF]!      @ returned r0

work_pending:

   tst   r1, #_TIF_NEED_RESCHED

   bne   work_resched

   tst   r1, #_TIF_SIGPENDING_TIF_NOTIFY_RESUME

   beq   no_work_pending

   mov   r0, sp          @'regs'

   mov   r2, why            @'syscall'

   bl do_notify_resume

   b  ret_slow_syscall      @ Check work again

 

work_resched:

   bl schedule

/*

 * "slow" syscall return path.  "why" tells us if this was a realsyscall.

 */

ENTRY(ret_to_user)

ret_slow_syscall:

   disable_irq           @ disable interrupts

   ldr   r1, [tsk, #TI_FLAGS]

   tst   r1, #_TIF_WORK_MASK

   bne   work_pending

no_work_pending:

   /*perform architecture specific actions before user return */

   arch_ret_to_userr1, lr

 

   restore_user_regsfast = 0, offset = 0

ENDPROC(ret_to_user)

 

对于我们的平台来说,上面的arch_ret_to_user为空。restore_user_regs宏用于恢复现场并返回,restore_user_regs宏定义如下:

 

arch/arm/kernel/entry-header.S

   .macro restore_user_regs, fast = 0, offset = 0

   ldr   r1, [sp, #\offset + S_PSR]  @ get calling cpsr

   ldr   lr, [sp, #\offset + S_PC]!  @ get pc

   msr   spsr_cxsf, r1         @save in spsr_svc

#if defined(CONFIG_CPU_32v6K)

   clrex              @ clear the exclusive monitor

#elif defined (CONFIG_CPU_V6)

   strex r1, r2, [sp]       @clear the exclusive monitor

#endif

   .if   \fast

   ldmdb sp, {r1 - lr}^        @get calling r1 - lr

   .else

   ldmdb sp, {r0 - lr}^        @get calling r0 - lr

   .endif

   mov   r0, r0          @ARMv5T and earlier require a nop

                   @after ldm {}^

   add   sp, sp, #S_FRAME_SIZE - S_PC

   movs  pc, lr          @return & move spsr_svc into cpsr

   .endm

 

 

添加新的系统调用

第一、打开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添加函数声明

    asmlinkagelong sys_set_senda(int iset);

第五、在应用程序中调用该系统调用,可以参考uClibc的实现。

第六、结束。

 

参考文档:

[精华] arm Linux 2.6高版本中的系统调用方式

http://www.unixresources.net/linux/clf/linuxK/archive/00/00/67/92/679297.html

ARM Linux下添加新的系统调用

关键字:ARM  Linux  系统调用 引用地址:ARM Linux系统调用的原理

上一篇:zc301摄像头驱动以及在S3C2410中使用serfox和spcaview
下一篇:arm linux 系统调用实现

推荐阅读最新更新时间:2024-03-16 14:57

朱老师ARM裸机学习笔记(四):S5PV210启动过程详解
常用器件特性 内存: SRAM 静态内存 特点就是容量小、价格高,优点是不需要软件初始化直接上电就能用 DRAM 动态内存 特点就是容量大、价格低,缺点就是上电后不能直接使用,需要软件初始化后才可以使用。 单片机中:内存需求量小,而且希望开发尽量简单,适合全部用SRAM 嵌入式系统:内存需求量大,而且没有NorFlash等可启动介质 PC机: 内存需求量大,而且软件复杂,不在乎DRAM的初始化开销,适合全部用DRAM 外存: NorFlash:特点是容量小,价格高,优点是可以和CPU直接总线式相连,CPU上电后可以直接读取,所以一般用作启动介质。 NandFlash(跟硬盘一样):特点是容量大,价格低,缺点是不能总线式访问,也就是
[单片机]
朱老师<font color='red'>ARM</font>裸机学习笔记(四):S5PV210启动过程详解
ARM硬件仿真软件SkyEye使用简介
SkyEye是一个开源软件(opensource software)项目,中文名字是“天目”。SkyEye的目标是在通用的Linux和Windows平台实现一个仿真集成开发环境,模拟常见的嵌入式计算机系统(目前支持基于arm7tdmi的AT91开发板);可在SkyEye上运行uclinux以及uC/OS-II等多种嵌入式操作系统,并对它们进行源码级的分析和测试。SkyEye的推出具有下面两方面的意义:   首先,通过SkyEye仿真集成环境可以很方便地进入到嵌入式系统软件学习和开发的广阔天地中。尤其对于缺少嵌入式硬件开发环境的用户来说,它将是一个非常有效的学习工具和开发手段。因此,如果您想学习Liunx操作系统或者进行嵌入式系统开
[单片机]
嵌入式 arm平台kernel启动第二阶段分析
接着上面的分析,第一阶段的代码跳转后,会进入第二阶段的代码。 第二阶段的代码是从\arch\arm\kernel\head.S开始的。 内核启动第二阶段主要完成的工作有,cpu ID检查,machine ID(也就是开发板ID)检查,创建初始化页表,设置C代码运行环境,跳转到内核第一个真正的C函数startkernel开始执行。 这一阶段涉及到两个重要的结构体: (1) 一个是struct proc_info_list 主要描述CPU相关的信息,定义在文件arch\arm\include\asm\procinfo.h中,与其相关的函数及变量在文件arch/arm/mm/proc_arm920.S中被定义和赋值。 (2) 另一
[单片机]
嵌入式 <font color='red'>arm</font>平台kernel启动第二阶段分析
ARM S3C2410驱动TFT-LCD的研究
  介绍了S3C2410的LCD控制器的数据和控制管脚,并给出了LCD的控制流程和TFT-LCD的控制器设置规则。参照TFT-LCD CJM10C0101的逻辑要求和时序要求设计了其驱动电路,设置了各主要LCD寄存器。   开发了CJM10C0101在嵌入式LINUX下的显示驱动程序,并在CJM10C0101上显示了清晰稳定的画面。实验表明这套装置通用性好,能驱动大部分的TFT-LCD;可移植性强,经过少许修改即可应用在其他嵌入式系统中。它是S3C2410驱动TFT-LCD的一套较佳的解决方案。   1 引言   随着科技的发展,ARM在社会各个方面的应用越来越广。S3C2410是三星公司生产的基于ARM920T内核的RIS
[单片机]
<font color='red'>ARM</font> S3C2410驱动TFT-LCD的研究
基于linux-2.6.19内核的小型Linux系统制作移植
  引言   ARM9S3C2410微处理器与Linux的结合越来越紧密,逐渐在嵌入式领域得到广范的应用。目前,在便携式消费类电子产品、无线设备、汽车、网络、存储产品等都可以看到S3C2410与Linux相结合的身影。   S3C2410微处理器是一款由Samsung公司为手持终端设计的低价格、低功耗、高性能,基于ARM920T核的微处理器。它带有内存管理单元(MMU),采用0.18mm工艺和AMBA新型总线结构,主频可达203MHz。同时,它支持Thumb 16位压缩指令集,从而能以较小的存储空间获得32位的系统性能。   在众多嵌入式操作系统中,Linux目前发展最快、应用最为广泛 。性能优良、源码开放的Linux具有体
[单片机]
Linux驱动曝光AMD Vega20核心:有望对应7nm加速卡
  这两天发硬件新闻总市心有戚戚,即便看到挺靠谱的消息,也总是有一种愚人节埋雷的隐忧。不过,下面这则,笔者仔细查验了一番,应该可信。下面就随嵌入式小编一起来了解一下相关内容吧。   据VideoCardz,在 Linux 的Freedesktop分支中,出现了Vega20的芯片识别代码。   Vega20识别出来了6种型号,此前Vega 10在驱动中识别出9种,覆盖Instinct、Radeon Pro和RX系列产品。     不过,按照 AMD 的规划,GPU产品线在2018年分为三个级别,其中高端桌面的Vega 56/64已经与我们见面,Vega Mobile已经用在APU和Intel的Kaby Lake-G中。而唯一称
[嵌入式]
基于arm的LM3S811微控制器与TH12864显示模块的技术应用
8位的51单片机长期占据着微控制器(MCU)的主流市场,但随着技术与需求的发展,32位微控制器应用增长率也在不断攀升。目前,基于arm内核的32位微处理器在市场上处于领导地位。 基于arm嵌入式处理器的片上系统解决方案可应用于企业应用、汽车系统,家庭网络和无线技术等市场领域。ARM CortexTM系列提供了一个标准的体系结构来满足以上各种技术的不同性能要求,基于ARM架构有3个分工明确的系列:A系列面向复杂的尖端应用程序,用于运行开放式的复杂操作系统;R系列针对实时系统;M系列专为低成本控制和微控制器应用开发。Cortex-M3是基于ARM架构的处理器,是专门为了在微控制器等对功耗和成本敏感的应用领域实现高系统性能而设计的,
[单片机]
基于<font color='red'>arm</font>的LM3S811微控制器与TH12864显示模块的技术应用
嵌入式Linux的动态电源管理技术
摘要 在SoC硬件对电源管理支持基础上,通过设计电源管理操作点、管理类、管理策略等结构,把系统创建的任务和具体电源管理硬件参数联系起来,为任务间精细电源管理提供包括应用层、操作系统层以及硬件层的框架结构。结合用户层制定的策略,实时、动态、低延迟管理系统动态功耗,调整CPU工作频率和电压、外部总线时钟频率、外部设备时钟/电源参数。实践证明,节能效果良好。关键词 嵌入式Linux 动态电源管理 智能终端 DPM 引 言 如何有效地管理嵌入式系统,尤其是移动终端的电源功耗,是一个很有价值的课题。动态电源管理DPM(Dynamic Power, Management)技术提供一种操作系统级别的电源管理能力,包含CPU工作频率和电压,外部总线
[电源管理]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved