• Linux、Android系统调用从上层到底层的调用路径浅析


    参考:

    https://blog.csdn.net/liuhangtiant/article/details/85149369

    http://blog.sina.com.cn/s/blog_7943319e0101a5ds.html

    前言

    已经对系统调用比较熟悉了,但是没有脚踏实地地跟过系统调用,如何实现上层到底层具体是如何调用的。

    所以,本文会以chmod系统调用函数为例,对此进行分析。平台:SDM630,Android Q

    上层(daemon)系统调用的执行路径

    我们假如编译出一个可执行文件,其中main函数中执行了系统调用为chmod函数。

    int main(){
        chmod("/data/test_ljj.txt",0444);    //假设路径文件已存在
        return 0;
    }

    首先会调用到bionic C库中chmod的api接口:
    bionicc/libc/bionic/chmod.cpp:

    int chmod(const char* path, mode_t mode) {
      return fchmodat(AT_FDCWD, path, mode, 0);
    }

    内部又调用到了fchmodat函数,也是在bionic C库中的接口:
    bionicc/libc/bionic/fchmodat.cpp:

    int fchmodat(int dirfd, const char* pathname, mode_t mode, int flags) {
      if ((flags & ~AT_SYMLINK_NOFOLLOW) != 0) {
        errno = EINVAL;
        return -1;
      }
    
      if (flags & AT_SYMLINK_NOFOLLOW) {
        // Emulate AT_SYMLINK_NOFOLLOW using the mechanism described
        // at https://sourceware.org/bugzilla/show_bug.cgi?id=14578
        // comment #10
    
        int fd = openat(dirfd, pathname, O_PATH | O_NOFOLLOW | O_CLOEXEC);
        if (fd == -1) {
          return -1; // returns errno from openat
        }
    
        // POSIX requires that ENOTSUP be returned when the system
        // doesn't support setting the mode of a symbolic link.
        // This is true for all Linux kernels.
        // We rely on the O_PATH compatibility layer added in the
        // fchmod() function to get errno correct.
        int result = fchmod(fd, mode);
        ErrnoRestorer errno_restorer; // don't let close() clobber errno
        close(fd);
        return result;
      }
    
      return ___fchmodat(dirfd, pathname, mode);
    }

    根据之前函数,可以确认其中的flag参数为0,那么不会走if中的流程,而是直接调用___fchmodat函数。

    异常入口

    在./arch-arm64/syscalls/___fchmodat.S 中,实现了___fchmodat的汇编代码。

    ENTRY(___fchmodat)
        mov     x8, __NR_fchmodat
        svc     #0
    
        cmn     x0, #(MAX_ERRNO + 1)
        cneg    x0, x0, hi
        b.hi    __set_errno_internal
    
        ret
    END(___fchmodat)

    通过设置x8寄存器的值(值为系统调用号),并且使用svc #0(同步异常)来从用户态(el0)进入内核态(el1)。

    在./kernel//msm-4.4/arch/arm64/kernel/entry.S中,定义了异常入口:

    /*
     * Exception vectors.
     */
        .pushsection ".entry.text", "ax"
    
        .align    11
    ENTRY(vectors)
        kernel_ventry    1, sync_invalid            // Synchronous EL1t
        kernel_ventry    1, irq_invalid            // IRQ EL1t
        kernel_ventry    1, fiq_invalid            // FIQ EL1t
        kernel_ventry    1, error_invalid        // Error EL1t
    
        kernel_ventry    1, sync                // Synchronous EL1h
        kernel_ventry    1, irq                // IRQ EL1h
        kernel_ventry    1, fiq_invalid            // FIQ EL1h
        kernel_ventry    1, error_invalid        // Error EL1h
    
        kernel_ventry    0, sync                // Synchronous 64-bit EL0 -----这里
        kernel_ventry    0, irq                // IRQ 64-bit EL0
        kernel_ventry    0, fiq_invalid            // FIQ 64-bit EL0
        kernel_ventry    0, error_invalid        // Error 64-bit EL0
    
    #ifdef CONFIG_COMPAT
        kernel_ventry    0, sync_compat, 32        // Synchronous 32-bit EL0
        kernel_ventry    0, irq_compat, 32        // IRQ 32-bit EL0
        kernel_ventry    0, fiq_invalid_compat, 32    // FIQ 32-bit EL0
        kernel_ventry    0, error_invalid_compat, 32    // Error 32-bit EL0
    #else
        kernel_ventry    0, sync_invalid, 32        // Synchronous 32-bit EL0
        kernel_ventry    0, irq_invalid, 32        // IRQ 32-bit EL0
        kernel_ventry    0, fiq_invalid, 32        // FIQ 32-bit EL0
        kernel_ventry    0, error_invalid, 32        // Error 32-bit EL0
    #endif
    END(vectors)

    el0_sync的对应段如下,其中会读取ESR寄存器,获取异常原因,而我们当前应该走到跳转sl0_svc:

    /*
     * EL0 mode handlers.
     */
        .align    6
    el0_sync:
        kernel_entry 0
        mrs    x25, esr_el1            // read the syndrome register
        lsr    x24, x25, #ESR_ELx_EC_SHIFT    // exception class
        cmp    x24, #ESR_ELx_EC_SVC64        // SVC in 64-bit state
        b.eq    el0_svc
        cmp    x24, #ESR_ELx_EC_DABT_LOW    // data abort in EL0
        b.eq    el0_da
        cmp    x24, #ESR_ELx_EC_IABT_LOW    // instruction abort in EL0
        b.eq    el0_ia
        cmp    x24, #ESR_ELx_EC_FP_ASIMD    // FP/ASIMD access
        b.eq    el0_fpsimd_acc
        cmp    x24, #ESR_ELx_EC_FP_EXC64    // FP/ASIMD exception
        b.eq    el0_fpsimd_exc
        cmp    x24, #ESR_ELx_EC_SYS64        // configurable trap
        b.eq    el0_sys
        cmp    x24, #ESR_ELx_EC_SP_ALIGN    // stack alignment exception
        b.eq    el0_sp_pc
        cmp    x24, #ESR_ELx_EC_PC_ALIGN    // pc alignment exception
        b.eq    el0_sp_pc
        cmp    x24, #ESR_ELx_EC_UNKNOWN    // unknown exception in EL0
        b.eq    el0_undef
        cmp    x24, #ESR_ELx_EC_BREAKPT_LOW    // debug exception in EL0
        b.ge    el0_dbg
        b    el0_inv
    /*
     * SVC handler.
     */
        .align    6
    el0_svc:
        adrp    stbl, sys_call_table        // load syscall table pointer
        uxtw    scno, w8            // syscall number in w8
        mov    sc_nr, #__NR_syscalls

    系统调用的定义、与系统调用号的匹配

    ./kernel/msm-4.4/arch/arm64/kernel/sys.c中,找到__NR_syscalls的定义:

    #undef __SYSCALL
    #define __SYSCALL(nr, sym)    [nr] = sym,
    
    /*
     * The sys_call_table array must be 4K aligned to be accessible from
     * kernel/entry.S.
     */
    void * const sys_call_table[__NR_syscalls] __aligned(4096) = {
        [0 ... __NR_syscalls - 1] = sys_ni_syscall,
    #include <asm/unistd.h>
    };

    其中详细的定义从上面看到是在 #include <asm/unistd.h>。

    那么对应在./kernel/msm-4.4/arch/arm64/include/asm/unistd.h

    #ifndef __COMPAT_SYSCALL_NR
    #include <uapi/asm/unistd.h>
    #endif
    
    #define NR_syscalls (__NR_syscalls)

    然后找到./kernel/msm-4.4/arch/arm64/include/uapi/asm/unistd.h,里面就一句话:

    #include <asm-generic/unistd.h>

    然后再找到./kernel/msm-4.4/arch/arm64/include/asm-generic/unistd.h,其中还包了一层include。

    #include <uapi/asm-generic/unistd.h>
    #include <linux/export.h>
    
    /*
     * These are required system calls, we should
     * invert the logic eventually and let them
     * be selected by default.
     */
    #if __BITS_PER_LONG == 32
    #define __ARCH_WANT_STAT64
    #define __ARCH_WANT_SYS_LLSEEK
    #endif

    最终,我们终于找到了。在./kernel/msm-4.4/arch/arm64/include/uapi/asm-generic/unistd.h中,定义了系统调用号与系统调用的对应关系(系统调用表)。

    ...
    #define __NR_fchdir 50
    __SYSCALL(__NR_fchdir, sys_fchdir)
    #define __NR_chroot 51
    __SYSCALL(__NR_chroot, sys_chroot)
    #define __NR_fchmod 52
    __SYSCALL(__NR_fchmod, sys_fchmod)        
    #define __NR_fchmodat 53
    __SYSCALL(__NR_fchmodat, sys_fchmodat)     ------这里
    #define __NR_fchownat 54
    __SYSCALL(__NR_fchownat, sys_fchownat)
    #define __NR_fchown 55
    __SYSCALL(__NR_fchown, sys_fchown)
    #define __NR_openat 56
    __SC_COMP(__NR_openat, sys_openat, compat_sys_openat)
    #define __NR_close 57
    __SYSCALL(__NR_close, sys_close)
    ...

    最后,系统会用x8的寄存器的值,作为索引,而找到对应系统调用号的系统调用接口函数。在当前我们的例子中,系统调用号是 53,内核中系统调用接口函数为:sys_fchmodat 。

    系统调用的内核接口函数

    从上面可以知道,最终调用到了sys_fchmodat函数。

    在./kernel/msm-4.4/include/linux/syscalls.h中,有所有系统调用的声明:

    ...
    asmlinkage long sys_faccessat(int dfd, const char __user *filename, int mode);
    asmlinkage long sys_fchmodat(int dfd, const char __user * filename,
                     umode_t mode);                                             //------这里
    asmlinkage long sys_fchownat(int dfd, const char __user *filename, uid_t user,
                     gid_t group, int flag);
    asmlinkage long sys_openat(int dfd, const char __user *filename, int flags,
                   umode_t mode);
    ...

    而对应的函数定义在./kernel/msm-4.4/fs/open.c中:

    SYSCALL_DEFINE3(fchmodat, int, dfd, const char __user *, filename, umode_t, mode)
    {
        struct path path;
        int error;
        unsigned int lookup_flags = LOOKUP_FOLLOW;
    retry:
        error = user_path_at(dfd, filename, lookup_flags, &path);
        if (!error) {
            error = chmod_common(&path, mode);
            path_put(&path);
            if (retry_estale(error, lookup_flags)) {
                lookup_flags |= LOOKUP_REVAL;
                goto retry;
            }
        }
        return error;
    }

    我们来详细解释一下,为什么这个就是函数定义。

    还是在./kernel/msm-4.4/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__)
    
    
    #define SYSCALL_DEFINEx(x, sname, ...)                
        SYSCALL_METADATA(sname, x, __VA_ARGS__)            
        __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
    
    
    #define __SYSCALL_DEFINEx(x, name, ...)                    
        asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))    
            __attribute__((alias(__stringify(SyS##name))));        
        static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__));    
        asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__));    
        asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__))    
        {                                
            long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__));    
            __MAP(x,__SC_TEST,__VA_ARGS__);                
            __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__));    
            return ret;                        
        }                                
        static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__))

    首先,根据以下宏定义:

    #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

    得到:

    SYSCALL_DEFINEx(3, _##fchmodat, __VA_ARGS__)

    然后,再根据宏定义得到:

    __SYSCALL_DEFINEx(3, _##fchmodat, __VA_ARGS__)

    又因宏定义如下:

    #define __SYSCALL_DEFINEx(x, name, ...)                    
        asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))

    整理后得到:

      asmlinage long sys_fchmodat(__MAP(3,__SC_DECL,__VA_ARGS__))

    而__MAP(3,...)是一个嵌套宏定义:

    #define __MAP0(m,...)
    #define __MAP1(m,t,a) m(t,a)
    #define __MAP2(m,t,a,...) m(t,a), __MAP1(m,__VA_ARGS__)
    #define __MAP3(m,t,a,...) m(t,a), __MAP2(m,__VA_ARGS__)
    #define __MAP4(m,t,a,...) m(t,a), __MAP3(m,__VA_ARGS__)
    #define __MAP5(m,t,a,...) m(t,a), __MAP4(m,__VA_ARGS__)
    #define __MAP6(m,t,a,...) m(t,a), __MAP5(m,__VA_ARGS__)
    #define __MAP(n,...) __MAP##n(__VA_ARGS__)
    
    #define __SC_DECL(t, a)    t a

    最终化解开,就是:t3 a3,t2 a2,t1,a1。也就是对应将

    int, dfd, const char __user *, filename, umode_t, mode

    整合为:

    int dfd, const char __user * filename, umode_t mod

    最终,结合起来就是:

    asmlinage long sys_fchmodat(int dfd, const char __user * filename, umode_t mod)

    所以,

    1、看到函数名是:SYSCALL_DEFINE3,这个前面是一个固定格式,后来有一个数字:3。而这个3代表这个系统调用函数有3个参数。(如果是2个参数,那么就是SYSCALL_DEFINE2;4个参数,用SYSCALL_DEFINE4,以此类推。)

    2、括号中第一个参数“fchmodat”,作为字符连接来生成函数名:sys_XXXX。

    3、之后的参数,会通过嵌套宏定义,生成具体函数的参数类型,以及对应的参数名。

    结束

    以上以chmod系统调用函数为例,讲述了从上层到底层的整个调用路径。

    Keep learning~ Keep blogging~

  • 相关阅读:
    《自我介绍》
    《结对-结对编项目作业名称-开发环境搭建过程》
    提取图形中指定颜色的所有像素_opencv/c++
    图形锐化_opencv/C++
    Opencv2.4.13 与Visual Studio2013 环境搭建配置
    C++基础——C面向过程与C++面向对象编程01_圆面积求解
    2017年2月26日
    基于GDAL的遥感影像显示(C#版)
    GDAL C# 开发环境配置
    shp文件的读取
  • 原文地址:https://www.cnblogs.com/lingjiajun/p/12134034.html
Copyright © 2020-2023  润新知