Content #
x86_64 下的 sysdep.h 文件。
/* The Linux/x86-64 kernel expects the system call parameters in
registers according to the following table:
syscall number rax
arg 1 rdi
arg 2 rsi
arg 3 rdx
arg 4 r10
arg 5 r8
arg 6 r9
......
*/
#define DO_CALL(syscall_name, args) \
lea SYS_ify (syscall_name), %rax; \
syscall
将系统调用名称转换为系统调用号,放到寄存器 rax。这里是真正进行调用,不是用中断了,而是改用 syscall 指令了。并且,通过注释我们也可以知道,传递参数的寄存器也变了。
syscall 指令还使用了一种特殊的寄存器,我们叫特殊模块寄存器(Model Specific Registers,简称 MSR)。这种寄存器是 CPU 为了完成某些特殊控制功能为目的的寄存器,其中就有系统调用。
在系统初始化的时候,trap_init 除了初始化上面的中断模式,这里面还会调用 cpu_init->syscall_init。这里面有这样的代码:
wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);
rdmsr 和 wrmsr 是用来读写特殊模块寄存器的。MSR_LSTAR 就是这样一个特殊的寄存器,当 syscall 指令调用的时候,会从这个寄存器里面拿出函数地址来调用,也就是调用 entry_SYSCALL_64。
在 arch/x86/entry/entry_64.S 中定义了 entry_SYSCALL_64。
ENTRY(entry_SYSCALL_64)
/* Construct struct pt_regs on stack */
pushq $__USER_DS /* pt_regs->ss */
pushq PER_CPU_VAR(rsp_scratch) /* pt_regs->sp */
pushq %r11 /* pt_regs->flags */
pushq $__USER_CS /* pt_regs->cs */
pushq %rcx /* pt_regs->ip */
pushq %rax /* pt_regs->orig_ax */
pushq %rdi /* pt_regs->di */
pushq %rsi /* pt_regs->si */
pushq %rdx /* pt_regs->dx */
pushq %rcx /* pt_regs->cx */
pushq $-ENOSYS /* pt_regs->ax */
pushq %r8 /* pt_regs->r8 */
pushq %r9 /* pt_regs->r9 */
pushq %r10 /* pt_regs->r10 */
pushq %r11 /* pt_regs->r11 */
sub $(6*8), %rsp /* pt_regs->bp, bx, r12-15 not saved */
movq PER_CPU_VAR(current_task), %r11
testl $_TIF_WORK_SYSCALL_ENTRY|_TIF_ALLWORK_MASK, TASK_TI_flags(%r11)
jnz entry_SYSCALL64_slow_path
......
entry_SYSCALL64_slow_path:
/* IRQs are off. */
SAVE_EXTRA_REGS
movq %rsp, %rdi
call do_syscall_64 /* returns with IRQs disabled */
return_from_SYSCALL_64:
RESTORE_EXTRA_REGS
TRACE_IRQS_IRETQ
movq RCX(%rsp), %rcx
movq RIP(%rsp), %r11
movq R11(%rsp), %r11
......
syscall_return_via_sysret:
/* rcx and r11 are already restored (see code above) */
RESTORE_C_REGS_EXCEPT_RCX_R11
movq RSP(%rsp), %rsp
USERGS_SYSRET64
这里先保存了很多寄存器到 pt_regs 结构里面,例如用户态的代码段、数据段、保存参数的寄存器,然后调用 entry_SYSCALL64_slow_pat->do_syscall_64。
__visible void do_syscall_64(struct pt_regs *regs)
{
struct thread_info *ti = current_thread_info();
unsigned long nr = regs->orig_ax;
......
if (likely((nr & __SYSCALL_MASK) < NR_syscalls)) {
regs->ax = sys_call_table[nr & __SYSCALL_MASK](
regs->di, regs->si, regs->dx,
regs->r10, regs->r8, regs->r9);
}
syscall_return_slowpath(regs);
}
在 do_syscall_64 里面,从 rax 里面拿出系统调用号,然后根据系统调用号,在系统调用表 sys_call_table 中找到相应的函数进行调用,并将寄存器中保存的参数取出来,作为函数参数。
所以,无论是 32 位,还是 64 位,都会到系统调用表 sys_call_table 这里来。
我们看 64 位的系统调用返回的时候,执行的是 USERGS_SYSRET64。定义如下:
#define USERGS_SYSRET64 \
swapgs; \
sysretq;
这里,返回用户态的指令变成了 sysretq。
