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~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM