zoukankan      html  css  js  c++  java
  • ARM-Linux系统调用流程

    转载自:http://my.oschina.net/raybin/blog/100379  感谢作者的分析,让我有种醍醐灌顶的感觉,谢谢

       旧式x86平台上的系统调用由int 0x80中断实现,后来对于新式CPU,Linux使用了sysenter方式。
        在ARM平台上,使用了swi中断来实现系统调用的跳转。
        swi指令用于产生软件中断,从而实现从用户模式变换到管理模式,CPSR(Current Program Status Register,程序状态寄存器,包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位)保存到管理模式的SPSR(Saved Program Status Register,程序状态保存寄存器,用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态),执行转移到SWI向量,在其他模式下也可使用SWI指令,处理器同样地切换到管理模式。
        指令格式如下:
        SWI {cond}  immed_24
        其中:
        immed_24  24位立即数,值为从0——16777215之间的整数。
    使用SWI指令时,通常使用一下两种方法进行参数传递,SWI异常处理程序可以提供相关的服务,这两种方法均是用户软件协定。SWI异常中断处理程序要通过读取引起软件中断的SWI指令,以取得24为立即数。
        1)指令中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位)。
        在arch/arm/include/asm 目录下unistd.h文件中,在Linux内核中,每个系统调用都具有唯一的一个系统调用功能号,这些功能号的定义就在此文件中,在这文件可以看到很多类似这样的定义:
        #define __NR_write (__NR_SYSCALL_BASE+  4)
        这是系统调用write的定义,功能号是__NR_SYSCALL_BASE +4,定义为符号__NR_write。
        由于采用了不同的二进制接口,所以__NR_SYSCALL_BASE +4的定义会有所不同,在文件中可以找到定义:

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #ifndef __ASM_ARM_UNISTD_H
    #define __ASM_ARM_UNISTD_H
     
    #define __NR_OABI_SYSCALL_BASE  0x900000
     
    #if defined(__thumb__) || defined(__ARM_EABI__)
    #define __NR_SYSCALL_BASE   0
    #else
    #define __NR_SYSCALL_BASE   __NR_OABI_SYSCALL_BASE
    #endif
       注意那个EABI, EABI是什么东西呢?ABI,Application Binary Interface,应用二进制接口。在较新的EABI规范中,是将系统调用号压入寄存器r7中,而在老的OABI中则是执行的swi 中断号的方式,也就是说原来的调用方式(Old ABI)是通过跟随在swi指令中的调用号来进行的。 
        这里主要是对调用号的索取定义了不同的方式。 
        而这些系统调用号所对应的系统调用列表,定义在arch/arm/kernel目录下的calls.S。 
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /* 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)
    /*………省略……….*/
       在源码中,我们可以找到诸如sys_write的函数声明: 
        asmlinkage long sys_write (unsigned int fd, const char __user *buf,size_t count); 
        asmlinkage是gcc标签,表明函数读取传递而来的参数位于栈中,具体的作用可以参考链接:http://blog.chinaunix.net/uid-20585891-id-1919646.html 
        但具体的实现代码则用宏来定义。 
        Linux已经为每一个系统调用都设定了唯一的一个系统调用功能号,当执行系统调用时,会按照系统调用号来索引系统调用,用的是几个带参数的宏来实现,位于syscalls.h文件中,可以看到: 
    ?
    1
    2
    3
    4
    5
    6
    7
    #define SYSCALL_DEFINE0(name)      asmlinkage long sys_##name(void)
    #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__)
        这里DEFINEn表示的是参数的个数,有参数的系统调用最终都是指向这么一个宏: 
    ?
    1
    #define SYSCALL_DEFINEx(x, sname, ...)  __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
        翻看__SYSCALL_DEFINEx的定义: 
    ?
    1
    2
    #define __SYSCALL_DEFINEx(x, name, ...)                
        asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))
        翻查代码,可以找到: 


    ?
    1
    2
    SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
            size_t, count)
        展开此宏,便可以得到“ asmlinkage long sys_write (unsigned int fd, const char __user *buf,size_t count);”的声明形式。 
        PS:~~不明白为何要使用这样的形式来说明定义,不明意图郁闷ing~~ 
        PS:__SC_DECLX的宏定义如下(/include/linux/syscalls.h),__VA_ARGS__是个可变参数宏: 


    ?
    1
    2
    3
    4
    5
    6
    #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__)
        那么系统是如何找到该函数的。 
        前面提交到系统调用号,这是一个偏移量,来匹配系统调用表的各项入口,在calls.S文件中有对各项入口的声明,而对于系统调用表的定义,在文件arch/arm/kernel/entry-armv.S中。 
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /*
     * 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, #object
    ENTRY(sys_call_table)
    #include "calls.S"
    #undef ABI
    #undef OBSOLETE
        还有 
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /*
     * Let's declare a second syscall table for old ABI binaries
     * using the compatibility syscall entries.
     */
    #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
        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寄存器。
        接下来查找的过程。
        ARM-Linux内核启动时,通过start_kernel(/init/main.c)->setup_arch(/arch/arm/kernel/setup.c)->paging_init(/arch/arm/mm/nommu.c)->early_trap_init(/arch/arm/kernel/traps.c),初始化中断异常向量表:

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    void __init early_trap_init(void *vectors_base)
    {
        unsigned long vectors = (unsigned long)vectors_base;
        extern char __stubs_start[], __stubs_end[];
        extern char __vectors_start[], __vectors_end[];
        extern char __kuser_helper_start[], __kuser_helper_end[];
        int kuser_sz = __kuser_helper_end - __kuser_helper_start;
     
        vectors_page = vectors_base;
     
        /*
         * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
         * into the vector page, mapped at 0xffff0000, and ensure these
         * are visible to the instruction stream.
         */
        memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
        memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
        memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
     
        /*
         * Do processor specific fixups for the kuser helpers
         */
        kuser_get_tls_init(vectors);
     
        /*
         * Copy signal return handlers into the vector page, and
         * set sigreturn to be a pointer to these.
         */
        memcpy((void *)(vectors + KERN_SIGRETURN_CODE - CONFIG_VECTORS_BASE),
               sigreturn_codes, sizeof(sigreturn_codes));
        memcpy((void *)(vectors + KERN_RESTART_CODE - CONFIG_VECTORS_BASE),
               syscall_restart_code, sizeof(syscall_restart_code));
     
        flush_icache_range(vectors, vectors + PAGE_SIZE);
        modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
    }
        paging_init函数中调用传递的参数是:early_trap_init((void *)CONFIG_VECTORS_BASE); 
        early_trap_init主要完成将中断向量表(__vectors_start, __vectors_end)和中断入口函数表(__stubs_start, __stubs_end)的相关代码copy到内存0xffff0000或者0x00000000处。 
        这个函数把定义在 arch/arm/kernel/entry-armv.S 中的异常向量表和异常处理程序的 stub 进行 
    重定位:异常向量表拷贝到 0xFFFF_0000,异常向量处理程序的 stub 拷贝到 0xFFFF_0200。 
    然后调用 modify_domain()修改了异常向量表所占据的页面的访问权限,这使得用户态无法 
    访问该页,只有核心态才可以访问。 
        异常向量表,在文件arch/arm/kernel/entry-armv.S 中 : 
       
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    __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:
       填充后,向量表如下: 
         
       虚拟地址        异常              处理代码 
        0xffff0000      reset              swi SYS_ERROR0 
        0xffff0004      undefined        b __real_stubs_start + (vector_undefinstr - __stubs_start) 
        0xffff0008      软件中断          ldr pc, __real_stubs_start + (.LCvswi - __stubs_start) 
        0xffff000c      取指令异常       b __real_stubs_start + (vector_prefetch - __stubs_start) 
        0xffff0010      数据异常          b __real_stubs_start + (vector_data - __stubs_start) 
        0xffff0014      reserved         b __real_stubs_start + (vector_addrexcptn - __stubs_start) 
        0xffff0018      irq                 b __real_stubs_start + (vector_IRQ - __stubs_start) 
        0xffff001c      fiq                  b __real_stubs_start + (vector_FIQ - __stubs_start) 

        发生软中断swi时,会跳到.LCvswi + stubs_offset处执行, LCvswi定义如下:
    ?
    1
    2
    .LCvswi:
        .word   vector_swi
        最终会执行例程vector_swi来完成对系统调用的处理,翻看/arch/arm/kernel/entry-common.S下vector_swi的定义。
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    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 //endif "CONFIG_ARM_THUMB<span></span>"
     
    #ifdef CONFIG_CPU_ENDIAN_BE8
        rev r10, r10            @ little endian instruction
    #endif //endif "CONFIG_CPU_ENDIAN_BE8"
     
    #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 //endif "CONFIG_OABI_COMPAT"
     
    #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
            //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_COMP<span></span>AT)
        /*
         * 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 - r3
    1:
    #endif
     
        tst r10, #_TIF_SYSCALL_WORK     @ 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)

        然后转入到函数入口,执行系统调用。

    参考总结于:http://blog.csdn.net/xiyangfan/article/details/5701673

    http://blog.csdn.net/hongjiujing/article/details/6831192

    http://blog.chinaunix.net/uid-26316047-id-3402198.html

  • 相关阅读:
    C#代码常用技巧
    MVC
    json类型
    android 上传二进制文件的两种方式
    BroadcastReceiver 使用goAsync 执行异步操作
    android组件间通信又一种方式
    Android BLE基础框架使用详解
    Android BLE设备蓝牙通信框架BluetoothKit
    android studio ndk开发总结
    jni c基础总结
  • 原文地址:https://www.cnblogs.com/longbiao831/p/4556262.html
Copyright © 2011-2022 走看看