參考:
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~