4. 系统调用入口

在 x86_64 架构中,系统调用是用户态程序请求内核服务的核心通道。与传统的 int 0x80 中断门不同,现代 x86_64 Linux 内核使用 SYSCALL/SYSRET 指令对来实现高性能的系统调用入口。本章节将深入剖析 entry_SYSCALL_64 汇编入口的完整流程,以及通过 MSR(Model-Specific Register)寄存器配置系统调用环境的关键细节。

4.1 系统调用硬件加速:SYSCALL/SYSRET 指令

在 x86_64 模式下,SYSCALL 指令提供了比 int 0x80 更快的系统调用路径。其核心优势在于硬件自动完成关键寄存器的保存与切换,无需软件模拟中断处理过程。

特性 int 0x80(传统方式) SYSCALL(快速方式)
指令开销 较高(中断门查找、堆栈切换) 极低(硬件微码优化)
寄存器保存 软件手动保存 硬件自动保存 RIP 到 RCX,RFLAGS 到 R11
堆栈切换 通过 TSS 加载 RSP0 从 STAR MSR 加载 RSP(需软件配合)
权限级切换 通过门描述符 DPL 硬件自动提升 CPL 至 0
入口地址 IDT 中的中断处理函数 从 LSTAR MSR 读取
性能关键点: SYSCALL 指令在硬件层面避免了中断优先级检查、IDT 查找和 TSS 堆栈加载的复杂流程,使得系统调用延迟从微秒级降低到纳秒级。这是现代高性能内核的基础。

4.2 MSR 寄存器配置:系统调用的硬件环境

MSR(Model-Specific Register)是 x86 CPU 提供的一组特殊寄存器,用于配置 CPU 的特定功能。对于系统调用,内核必须正确配置以下三个关键 MSR:

  • STAR (0xC0000081): 配置系统调用的段选择子。高 32 位存放 SYSRET 使用的用户态段(CS 和 SS),低 32 位存放 SYSCALL 进入内核态时使用的段。
  • LSTAR (0xC0000082): 存放系统调用入口函数的 64 位虚拟地址。当用户态执行 SYSCALL 时,CPU 自动将 RIP 设置为该地址,即 entry_SYSCALL_64。
  • SFMASK (0xC0000084): 用于屏蔽进入内核态时的 RFLAGS 寄存器中的某些标志位(如 IF 中断标志),防止用户态干扰内核的中断状态。
内核初始化配置(arch/x86/kernel/cpu/common.c):
wrmsrl(MSR_STAR,  ((u64)__USER32_CS << 48) | ((u64)__KERNEL_CS << 32));
wrmsrl(MSR_LSTAR, (unsigned long) entry_SYSCALL_64);
wrmsrl(MSR_SFMASK, X86_EFLAGS_TF | X86_EFLAGS_DF | X86_EFLAGS_IF | X86_EFLAGS_AC);
避坑指南: SFMASK 的配置至关重要。如果忘记屏蔽 IF 标志,用户态可以通过 STI 指令设置中断标志,然后执行 SYSCALL,导致内核在关中断状态下执行系统调用,引发不可预知的中断竞争问题。

4.3 entry_SYSCALL_64 汇编入口深度解析

entry_SYSCALL_64 是 x86_64 系统调用的统一入口点,位于 arch/x86/entry/entry_64.S。其执行流程可概括为以下步骤:

  1. 保存用户态寄存器: 将用户态的 RSP、RCX(用户态 RIP)、R11(用户态 RFLAGS)以及所有通用寄存器压入内核栈。
  2. 切换到内核栈: 从 PER_CPU_VAR(cpu_tss_rw + TSS_sp0) 加载内核栈指针 RSP。
  3. 构建 pt_regs 结构: 在栈上形成一个 struct pt_regs 结构,包含所有保存的寄存器,供后续 C 代码使用。
  4. 系统调用号与参数检查: 验证 RAX(系统调用号)是否在有效范围内,并处理可能的追踪(ptrace)事件。
  5. 调用系统调用处理函数: 根据系统调用号跳转到 sys_call_table 中对应的内核函数。
  6. 返回路径: 执行 SYSRET 指令,从 RCX 恢复用户态 RIP,从 R11 恢复用户态 RFLAGS,并切换回用户态栈。
核心汇编片段(简化):
ENTRY(entry_SYSCALL_64)
    swapgs                     /* 交换 GS 基址,访问内核 percpu 数据 */
    movq    %rsp, %gs:CPU_ENTRY_AREA_tss + TSS_sp2  /* 保存用户 RSP */
    movq    %gs:CPU_ENTRY_AREA_tss + TSS_sp0, %rsp  /* 加载内核 RSP */

    /* 在栈上构建 pt_regs */
    pushq   $__USER_DS         /* SS */
    pushq   %gs:CPU_ENTRY_AREA_tss + TSS_sp2  /* 用户 RSP */
    pushq   %r11               /* RFLAGS */
    pushq   $__USER_CS         /* CS */
    pushq   %rcx               /* RIP */
    pushq   %rax               /* 系统调用号 */
    /* ... 保存其他通用寄存器 ... */

    call    do_syscall_64      /* 调用 C 层处理函数 */

    /* 返回路径:恢复寄存器并执行 SYSRET */
    /* ... 恢复寄存器 ... */
    popq    %r11               /* 恢复 RFLAGS */
    popq    %rcx               /* 恢复 RIP */
    popq    %rsp               /* 恢复用户 RSP */
    sysretq                    /* 返回用户态 */
END(entry_SYSCALL_64)

4.4 系统调用入口流程 SVG 架构图

下图展示了从用户态 SYSCALL 指令触发到内核 entry_SYSCALL_64 处理,再到返回用户态的完整数据流与寄存器变化。

x86_64 系统调用入口流程:从 SYSCALL 到 entry_SYSCALL_64 用户态程序 执行 SYSCALL 指令 CPU 硬件微码 RIP → RCX, RFLAGS → R11 MSR 寄存器 LSTAR → 入口地址 触发 读取 entry_SYSCALL_64 入口 swapgs, 切换内核栈, 构建 pt_regs 跳转到 LSTAR 地址 sys_call_table 查找 根据 RAX 索引调用具体内核函数 调用 do_syscall_64 内核函数执行 完成系统调用功能,返回值存入 RAX SYSRET 返回用户态 RCX → RIP, R11 → RFLAGS, 恢复 RSP 关键寄存器变化 • RAX: 系统调用号/返回值 • RCX: 用户态 RIP • R11: 用户态 RFLAGS

4.5 系统调用号与参数传递约定

在 x86_64 架构中,系统调用的参数传递完全通过寄存器完成,避免了内存访问的开销。具体约定如下:

寄存器 用途 说明
RAX 系统调用号 标识要执行的内核函数(如 __NR_read = 0)
RDI 第一个参数 通常为文件描述符或指针
RSI 第二个参数 缓冲区地址或标志位
RDX 第三个参数 长度或偏移量
R10 第四个参数 替代 RCX(因为 RCX 被 SYSCALL 占用)
R8 第五个参数 扩展参数
R9 第六个参数 扩展参数
RAX(返回) 返回值 成功返回 0 或正数,失败返回负错误码
设计考量: 使用 R10 而非 RCX 传递第四个参数,是因为 SYSCALL 指令会隐式地将 RCX 覆盖为返回地址。如果使用 RCX 传参,该参数将丢失。这是 x86_64 ABI 与系统调用约定之间的一个关键适配点。

4.6 上下文切换的优化:从入口开始

系统调用入口是上下文切换的起点。在 entry_SYSCALL_64 中,内核已经实施了多项优化:

  • 最小化寄存器保存: 只保存调用者保存寄存器(caller-saved registers),被调用者保存寄存器(callee-saved registers)由内核函数自行保存,避免不必要的内存写入。
  • 使用 swapgs 而非 mov: swapgs 指令仅交换 GS 基址寄存器的影子副本,比通过内存加载 TSS 更快速。
  • 避免中断嵌套: 在入口处通过 SFMASK 自动屏蔽中断,直到内核显式开启中断,减少了中断嵌套带来的栈溢出风险。
  • percpu 栈缓存: 内核栈通过 percpu 变量管理,每个 CPU 核心拥有独立的栈,避免了锁竞争。
优化效果数据(基于 5.10 内核测试):
系统调用延迟(getpid):
- int 0x80 路径:约 150-200 ns
- SYSCALL 路径:约 50-80 ns
- 优化后(使用 vDSO):约 10-20 ns

通过本章的学习,你应该已经掌握了 x86_64 架构下系统调用入口的完整硬件与软件协同机制。从 MSR 的初始化配置,到 entry_SYSCALL_64 的汇编细节,再到参数传递与优化策略,这些知识是理解后续上下文切换优化的基础。