Linux系统调用的整体流程为:
1、应用程序【用户态】通过syscall或glibc进行内核功能调用,这一部分在glibc源码中进行的
2、CPU收到syscall,Linux内核响应syscall调用【内核态】,这一部分在linux源码中进行的
3、返回结果到应用程序【用户态】
本节处理第二部分:
二、linux内核部分
1、在make时,会通过syscall_64.tbl生成syscalls_64.h,然后包含到syscall_64.c,进行调用号与函数之间的绑定。
arch/x86/entry/syscalls/syscall_64.tbl
arch/x86/include/generated/asm/syscalls_64.h
arch/x86/entry/syscall_64.c
1.1、以sys_openat为例,在syscall_64.tbl中为
257 common openat sys_openat
441 common get_cpus sys_get_cpus
1.2、make后,在生成的syscalls_64.h中为
__SYSCALL_COMMON(257, sys_openat)
1.3 在syscall_64.c中,展开__SYSCALL_COMMON
#define __SYSCALL_COMMON(nr, sym) __SYSCALL_64(nr, sym)
//展开就是
__SYSCALL_64(257, sys_openat)
1.4、在syscall_64.c中,第一次展开__SYSCALL_64
#define __SYSCALL_64(nr, sym) extern long __x64_##sym(const struct pt_regs *);
#include <asm/syscalls_64.h>
#undef __SYSCALL_64
//展开就是
extern long __x64_sys_openat(const struct pt_regs *);
//也就是每个__SYSCALL_64都展开成了一个外部函数
1.5、在syscall_64.c中,第二次展开__SYSCALL_64
#define __SYSCALL_64(nr, sym) [nr] = __x64_##sym,
asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
[0 ... __NR_syscall_max] = &__x64_sys_ni_syscall,
#include <asm/syscalls_64.h>
};
//展开其实就是指向了外部函数
[257]=__x64_sys_openat,
//全部展开结果,都会被包含到sys_call_table中,从而完成了调用号与函数之间的绑定。
2、当产生系统调用时
2.1、应用直接syscall或通过glibc产生了syscall
2.2、cpu会产生类似于中断的效果,开始到entry_SYSCALL_64执行
//文件路径arch/x86/entry/entry_64.S
SYM_CODE_START(entry_SYSCALL_64)
//省略代码
call do_syscall_64
SYM_CODE_END(entry_SYSCALL_64)
//文件路径arch/x86/entry/entry_64.S,32位兼容模式,过程与64位类似
SYM_CODE_START(entry_SYSCALL_compat)
call do_fast_syscall_32
SYM_CODE_END(entry_SYSCALL_compat)
2.3、调用do_syscall_64
#ifdef CONFIG_X86_64
__visible noinstr void do_syscall_64(unsigned long nr, struct pt_regs *regs)
{
nr = syscall_enter_from_user_mode(regs, nr);
instrumentation_begin();
if (likely(nr < NR_syscalls)) {
nr = array_index_nospec(nr, NR_syscalls);
regs->ax = sys_call_table[nr](regs);
}
instrumentation_end();
syscall_exit_to_user_mode(regs);
}
#endif
2.4、根据sys_call_table调用对应的功能函数
sys_call_table[nr](regs)
如果我们传入257,就会调用__x64_sys_openat
如果我们传入441,就会调用__x64_sys_get_cpus
2.5、但咱们实际写的函数sys_get_cpus,好像和实际调用函数__x64_sys_get_cpus,差了一个__x64,这需要一个wrapper
arch\x86\include\asm\syscall_wrapper.h
#define SYSCALL_DEFINE0(sname) \
SYSCALL_METADATA(_##sname, 0); \
static long __do_sys_##sname(const struct pt_regs *__unused); \
__X64_SYS_STUB0(sname) \
__IA32_SYS_STUB0(sname) \
static long __do_sys_##sname(const struct pt_regs *__unused)
#define __X64_SYS_STUB0(name) \
__SYS_STUB0(x64, sys_##name)
#define __SYS_STUB0(abi, name) \
long __##abi##_##name(const struct pt_regs *regs); \
ALLOW_ERROR_INJECTION(__##abi##_##name, ERRNO); \
long __##abi##_##name(const struct pt_regs *regs) \
__alias(__do_##name);
SYSCALL_DEFINE0(get_cpus),会展开成为
__X64_SYS_STUB0(get_cpus)
//然后
__SYS_STUB0(x64, sys_get_cpus)
//然后
long __x64_sys_get_cpus(const struct pt_regs *regs);
这样前后就对上了,glibc和linux内核就通了。