zoukankan      html  css  js  c++  java
  • arm Linux 系统调用过程

      系统调用是操作系统提供给用户(应用程序)的一组接口,每个系统调用都有一个对应的系统调用函数来完成相应的工作。用户通过这个接口向操作系统申请服务,如访问硬件,管理进程等等。但是因为用户程序运行在用户空间,而系统调用运行在内核空间,因此用户程序不能直接调用系统调用函数,我们经常看到的比如fork、open、write 等等函数实际上并不是真正的系统调用函数,他们都只是c库,在这些函数里将执行一个软中断 swi 指令,产生一个软中断,使CPU 陷入内核态,接着在内核中进行一系列的判断,判断出是哪个系统调用,再转到真正的系统调用函数,完成相应的功能。下面举一个简单的例子说明从用户态调用一个“系统调用”,到内核处理的整个执行流程。

      用户态程序如下:

           void pk()

      {

        __asm__(

        "ldr  r7  =365 \n"

        "swi \n"

        :

        :

        :

        );

      }

      int main()

      {

          pk();

        retrun 0;

      }

      上面的代码中,我自己实现了一个新的系统调用,具体怎么做,后面再具体描述。pk()事实上就可以类比于平时我们在用户程序里调用的 open() 等函数,这个函数只做了一件简单的事:将系统调用号传给 r7 ,,然后产生一软中断。接着CPU陷入内核

      内核态:

      CPU相应这个软中断以后,PC指针会到相应的中断向量表中取指,中断向量表在内核代码中:arch/arm/kernel/entry-armv.S  中定义

    .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  #响应中断后pc指向这里
     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:

    当pc取到如上的指令后,会跳到 vector_swi 这个标号,这个标号在arch/arm/kernel/entry-commen.S 中定义。

     .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
     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

     get_thread_info tsk

     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 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)
     bic scno, scno, #0xff000000  @ 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
    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

    从上面可以看出,当CPU从中断向量表转到vector_swi 之后,完成了几件事情:1.取出系统调用号 2.根据系统调用号取出系统调用函数在系统调用表的基地址,得到一个系统调用函数的函数指针 3. 根据系统调用表的基地址和系统调用号,得到这个系统调用表里的项,每一个表项都是一个函数指针,把这个函数指针赋给PC , 则实现了跳转到系统调用函数。

    系统调用表定义在:arch/arm/kernel/Calls.S

    * 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)
    /* 10 */ CALL(sys_unlink)
      CALL(sys_execve_wrapper)
      CALL(sys_chdir)
      CALL(OBSOLETE(sys_time)) /* used by libc4 */
      CALL(sys_mknod)
    /* 15 */ CALL(sys_chmod)
      CALL(sys_lchown16)
      CALL(sys_ni_syscall)  /* was sys_break */
      CALL(sys_ni_syscall)  /* was sys_stat */
      CALL(sys_lseek)
    /* 20 */ CALL(sys_getpid)
      CALL(sys_mount)
      CALL(OBSOLETE(sys_oldumount)) /* used by libc4 */
      CALL(sys_setuid16)
      CALL(sys_getuid16)
    /* 25 */ CALL(OBSOLETE(sys_stime))
      CALL(sys_ptrace)
      CALL(OBSOLETE(sys_alarm)) /* used by libc4 */
      CALL(sys_ni_syscall)  /* was sys_fstat */
      CALL(sys_pause)
    /* 30 */ CALL(OBSOLETE(sys_utime)) /* used by libc4 */
      CALL(sys_ni_syscall)  /* was sys_stty */
      CALL(sys_ni_syscall)  /* was sys_getty */
      CALL(sys_access)
      CALL(sys_nice)
    /* 35 */ CALL(sys_ni_syscall)  /* was sys_ftime */
      CALL(sys_sync)
      CALL(sys_kill)
      CALL(sys_rename)
      CALL(sys_mkdir)
    /* 40 */ CALL(sys_rmdir)
      CALL(sys_dup)
      CALL(sys_pipe)
      CALL(sys_times)
      CALL(sys_ni_syscall)  /* was sys_prof */
    /* 45 */ CALL(sys_brk)
      CALL(sys_setgid16)
      CALL(sys_getgid16)
      CALL(sys_ni_syscall)  /* was sys_signal */
      CALL(sys_geteuid16)
    /* 50 */ CALL(sys_getegid16)
      CALL(sys_acct)
      CALL(sys_umount)
      CALL(sys_ni_syscall)  /* was sys_lock */
      CALL(sys_ioctl)
    /* 55 */ CALL(sys_fcntl)
      .......

      CALL(sys_eventfd2)
      CALL(sys_epoll_create1)
      CALL(sys_dup3)
      CALL(sys_pipe2)
    /* 360 */ CALL(sys_inotify_init1)
      CALL(sys_preadv)
      CALL(sys_pwritev)
      CALL(sys_rt_tgsigqueueinfo)
      CALL(sys_perf_event_open)
      CALL(sys_pk)     #我自己加的系统调用

     了解了一个系统调用的执行过程就可以试着添加一个自己的系统调用了:

    内核:

    1. 在内核代码实现一个系统调用函数,即 sys_xxx()函数,如我在 kernel/printk.c 中添加了

    void pk()

    {

      printk(KERN_WARNING"this is my first sys call !\n");

    }

    2. 添加系统调用号 在 arch/arm/include/asm/Unistd.h

    添加  #define __NR_pk    (__NR_SYSCALL_BASE+365)

    3. 添加调用函数指针列表 在arch/arm/keenel/Calls.S

    添加 CALL(sys_pk)

    4.  声明自己的系统调用函数 在include/linux/syscall.h

    添加asmlinkage long sys_pk()

    用户空间:

           void pk()

      {

        __asm__(

        "ldr  r7  =365 \n"

        "swi \n"

        :

        :

        :

        );

      }

      int main()

      {

          pk();

         retrun 0;

      }

    完成上面的编写以后就可以编译内核和应用程序了。

    将生成的文件在arm开发板上运行可以打印出: This is my first sys call!

    说明我添加的系统调用可以使用。

    至此,描述系统调用的实现机制和添加一个新的系统调用就完成了。

  • 相关阅读:
    C++封装SQLite实例<六>
    大哥给力点 我要发博客
    Android仿人人客户端(v5.7.1)——授权认证(用accessToken换取session_key、session_secret和userId)
    三阶魔方玩法总结
    二阶魔方玩法
    [置顶] C语言实验:等额本金还款法的计算
    活到老、学到老,我要做80岁还能写代码的奇葩!
    百度PHP电话面试之十问
    Java 面向对象编程思想类及其方法的调用,分解质因数,界面交互方法 int string 类型转换 陈光剑
    [置顶] C语言实验:输入任意一个日期的年、月、日的值,求出从公元1年1月1日到这一天总共有多少天,并求出这一天是星期几。
  • 原文地址:https://www.cnblogs.com/linzizhang/p/4443957.html
Copyright © 2011-2022 走看看