IDT、SYSCALL 与 FRED:三条内核入口,同一份 pt_regs?

最近在看 x86-64 怎么进内核,经常听到四句:硬件中断要查 IDT;syscall 直接用 MSR;进内核后都要构建 pt_regs;FRED 把上面这些都简化了。对照 SDM、FRED 规范和 Linux 源码以后,发现它们在默认路径上只对一半,不能直接套到 FRED 上。在这里把路径捋一下。


一、那四句话怎么说才靠谱

可以这样限定(后面各节展开源码和手册):

  1. 硬件中断 / CPU 异常:默认走 IDT;FRED 启用后走 FRED event delivery,不再查 IDT。
    「查 IDT」指 CPU 按向量读一项门描述符(SDM Vol 3A 约 §6.14)1,不是内核扫整张表。Linux 启用 FRED 时会 idt_invalidate(),注释写 Any further IDT use is a bugfred.c)。之后按 type × vector 两级软件分发2,见 fred_entry_from_user()

  2. 64 位 syscall:默认经 STAR/LSTAR/FMASK 入核、不进 IDT;FRED 启用后同一条指令改由 FRED 投递,再落到 do_syscall_64
    非 FRED 时入口 RIP 来自 IA32_LSTAR13。Linux 在 idt_syscall_init()MSR_LSTAR 写成 entry_SYSCALL_64。规范 §3 / §7.4:CR4.FRED=1SYSCALL/SYSENTER 改用 FRED event delivery,返回走 ERETU4。Linux 在 fred_other() 里识别 FRED_SYSCALL 后调 do_syscall_64int $0x80 仍属中断类路径,别和 64 位 syscall 混谈。

  3. C 层几乎都拿 pt_regs *,但帧怎么来的不同。
    IDT 靠硬件 IRET 帧再软件补全;传统 SYSCALL 几乎全靠 entry_SYSCALL_64 手工 push;FRED 靠硬件 FRED 帧 + FRED_ENTER。结果同为 struct pt_regs *,过程不能互换叙述。见 ptrace.h

  4. FRED 统一的是事件投递与返回,不是删掉 pt_regs、MSR 或软件分发表。
    规范 §3:原会走 IDT 的事件改走 FRED;SYSCALL/SYSENTER 亦并入;返回用 ERETS/ERETU4。Linux 仍保留扩展后的 pt_regs、更多 FRED MSR(cpu_init_fred_exceptions())、以及 sysvec_tablefred=on|offon 为默认)见5

压成一句:

非 FRED:中断/异常查 IDT,64 位 syscall 走 MSR,两边都落到(不同构建方式的)pt_regs。** FRED:**统一投递与返回——IDT 作废、SYSCALL 也进 FRED;pt_regs 仍在,MSR 与软件分发仍在。

对照表:

# 常见说法 传统路径(IDT + SYSCALL) FRED 路径
1 硬件中断要查 IDT CPU 按向量查 IDT 投递 idt_invalidate() 后改 FRED event delivery,软件按 type/vector 分发
2 syscall 直接用 MSR 64 位 syscallLSTAR/STAR/FMASK,不进 IDT 变为 FRED 事件(EVENT_TYPE_OTHER)→ do_syscall_64()
3 进内核后都要构建 pt_regs C 层几乎都拿 struct pt_regs * 仍有;帧含 fred_cs/fred_ss/edata 扩展
4 FRED 简化了上面所有 统一投递与返回;不是删掉 IDT、MSR、pt_regs
flowchart TB
    subgraph Traditional["传统路径"]
        IRQ1["外部 IRQ / 异常"] --> IDT["IDT 门描述符"]
        SYS1["用户 syscall"] --> MSR["LSTAR / STAR / FMASK"]
        IDT --> PT1["软件补全 pt_regs"]
        MSR --> PT1
    end

    subgraph FredPath["FRED 路径"]
        EVT["任意事件含 SYSCALL"] --> FRED["FRED event delivery"]
        FRED --> DISP["type × vector 软件分发"]
        DISP --> PT2["FRED 帧 + FRED_ENTER"]
    end

二、默认路径:中断查 IDT,syscall 走 MSR

2.1 硬件中断 / CPU 异常 → IDT

不开 FRED 时,外部中断或 CPU 异常大致是:

  1. 中断控制器(APIC 等)给出向量号;
  2. CPU 用 IDTR → IDT[vector] 读门描述符;
  3. 硬件压 IRET 帧(SS、RSP、RFLAGS、CS、RIP;异常可能还有 error code);
  4. 若门描述符 IST ≠ 0,切换到 TSS 中对应 IST 栈。

「查 IDT」是 CPU 按向量索引一项门描述符,不是内核遍历整张表。SDM Vol 3A 大约 §6.141

Linux 里外部 IRQ:

APIC/MSI → 向量 N
    └─ IDT[N] → irq_entries_start + offset(idtentry.h)
        └─ push 向量号(占 orig_ax 槽)→ asm_common_interrupt
            └─ error_entry → PUSH_AND_CLEAR_REGS
                └─ common_interrupt(regs, vector)(irq.c)

参见:idtentry.h · entry_64.S · irq.c

外部 IRQ 与 #PF/#DE 等共用 IDT 机制,但 stub 宏不同(idtentry_irq vs idtentry)。用户态进来的,常经 error_entry + sync_regs 落到线程内核栈上的标准 pt_regs 槽位。

2.2 64 位 SYSCALL → MSR(不进 IDT)

SYSCALL 不查 IDT。硬件把 RIP 换成 MSR_LSTAR、用 RCX/R11 保住返回契约、按 MSR_STAR 推导 CS/SS,并用 IA32_FMASK 掩 RFLAGS——不保存 RSP,也不压完整 IRET 帧13

Linux 在每 CPU 初始化时把 MSR_LSTAR 指到 entry_SYSCALL_64

// arch/x86/kernel/cpu/common.c:2268-2270
static inline void idt_syscall_init(void)
{
	wrmsrq(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);

入口汇编自己切栈、手工搭 pt_regs

// arch/x86/entry/entry_64.S:87-121
SYM_CODE_START(entry_SYSCALL_64)
	UNWIND_HINT_ENTRY
	ENDBR

	swapgs
	/* tss.sp2 is scratch space. */
	movq	%rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
	SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
	movq	PER_CPU_VAR(cpu_current_top_of_stack), %rsp
	...
	PUSH_AND_CLEAR_REGS rax=$-ENOSYS
	...
	call	do_syscall_64		/* returns with IRQs disabled */

调用链:

用户 syscall 指令
    └─ MSR_LSTAR → entry_SYSCALL_64(entry_64.S:87)
        └─ swapgs;用户 RSP → TSS.sp2;RSP ← cpu_current_top_of_stack
            └─ 手工 push 用户态部分 + PUSH_AND_CLEAR_REGS
                └─ do_syscall_64(regs, nr)(syscall_64.c)

可以看到,MSR 只负责跳到入口;完整 pt_regsentry_SYSCALL_64 软件搭建。这里的 TSS.sp2 只是 scratch,用来暂存用户 RSP——不是 IDT 那套「硬件按 TSS.RSP0 / IST 自动切栈」(见 §4.7)。

其他系统调用入口仍走 IDT:int $0x80(向量 0x80)、ia32 sysenter 等。

2.3 传统路径对照

维度 IDT(IRQ / 异常 / int 0x80) 64 位 SYSCALL
硬件查表 IDT[vector] MSR_LSTAR
硬件压栈 IRET 帧(± error code) 仅 RCX/R11 等,无完整 IRET 帧
软件压栈 error_entry + PUSH_AND_CLEAR_REGS entry_SYSCALL_64 手工 push + PUSH_AND_CLEAR_REGS
典型 C 入口 common_interrupt / exc_* do_syscall_64
返用户 多为 iretq sysretq 快路径或 iretq 慢路径

两边进 C 之前,常见路径都会走 PUSH_AND_CLEAR_REGScalling.h):IDT 多经 error_entry,SYSCALL 在 entry_SYSCALL_64 里直接调;走 IST 的 #DF/NMI 等也常经 paranoid_entry,里面同样调这个宏。宏主要是补通用寄存器并清掉;前面往往还有硬件压栈或手工 push,不是单靠它从零搭出整帧

关于 TSS.RSP0:不是「凡中断/int 都读一遍 sp0」。只有 用户态→ring 0 且门上 IST=0 时硬件才用它切栈;IST≠0 用 TSS.IST[];已在 ring 0 通常不切 RSP0。Linux 上 RSP0 还多半是 per-CPU entry trampoline,不是该 process 的线程内核栈(§4.7)。

sequenceDiagram
    participant U as 用户态
    participant CPU as CPU
    participant K as 内核入口

    Note over U,K: IDT 路径
    U->>CPU: 异常或外部 IRQ
    CPU->>CPU: 查 IDT 门, 压 IRET 帧
    CPU->>K: 跳门描述符入口
    K->>K: error_entry / PUSH_AND_CLEAR_REGS

    Note over U,K: SYSCALL 路径
    U->>CPU: syscall
    CPU->>CPU: 读 LSTAR 等 MSR, 不压栈帧
    CPU->>K: entry_SYSCALL_64
    K->>K: 切栈并手工构造 pt_regs

三、pt_regs:结果相同,构建方式不同

三条路径的 C ABI 目标一致:handler 收到 struct pt_regs *。但谁压什么不同:

路径 硬件贡献 软件贡献
IDT IRQ/异常 SS/RSP/RFLAGS/CS/RIP(+ error code) PUSH_AND_CLEAR_REGS;用户态常 sync_regs 到线程栈
SYSCALL 无 IRET 帧;RCX/R11 等由硬件改写 全程手工 push 用户态部分 + PUSH_AND_CLEAR_REGS
FRED FRED 栈帧(自 orig_ax 起,含 type/vector/edata) FRED_ENTERPUSH_AND_CLEAR_REGS

容易踩坑的说法:

一句话用途:pt_regs 在内核栈上保存一次进核时的寄存器现场,供中断/异常/系统调用处理,并在返回时恢复。 硬件不认识这个 C 结构体;给 handler 用的完整视图由内核整理,IDT/FRED 可有硬件贡献字节。

ptrace.h 注释写明:FRED 栈帧从 orig_ax 开始,且始终带 error code;cs/ss 槽在 FRED 下可解读为 fred_cs / fred_ss

// arch/x86/include/asm/ptrace.h:127-163
	/*
	 * orig_ax is used on entry for:
	 * - the syscall number (syscall, sysenter, int80)
	 * - error_code stored by the CPU on traps and exceptions
	 * - the interrupt number for device interrupts
	 *
	 * A FRED stack frame starts here:
	 *   1) It _always_ includes an error code;
	 *   ...
	 */
	unsigned long orig_ax;
	...
	union {
		u16		cs;
		u64		csx;
		struct fred_cs	fred_cs;
	};
	...
	union {
		u16		ss;
		u64		ssx;
		struct fred_ss	fred_ss;
	};

后面 fred_entry_from_user(struct pt_regs *regs)do_syscall_64(regs, …) 照旧吃这个指针。


四、FRED:统一了什么,没删掉什么

FRED(Flexible Return and Event Delivery)用事件投递 + ERETS/ERETU 替代传统 IDT + IRET42。规范目标包括:用更低延迟的转移替换 IDT 投递与 IRET;投递/返回时一次建全 supervisor/user 上下文。

启用后,凡原先会走 IDT 的事件改走 FRED event delivery;SYSCALL / SYSENTER 的既有操作也被替换为 FRED 投递。返回侧用 ERETU(回 ring 3)与 ERETS(仍在 ring 0)4

统一的是进核/回用户的硬件机制,不是「syscall 也去查 IDT」。 IDT 会被作废。

4.1 Linux 如何启用;开关在 CR4.FRED

两项不要混成「都是编译期参数」:

  类型 作用
CONFIG_X86_FRED 编译期 Kconfig(arch/x86/Kconfig 内核是否编进 FRED 代码;depends on X86_64
fred=on\|off 引导 cmdline5 开机是否启用;on 为默认。解析在 cpu_parse_early_param():仅 fred=offsetup_clear_cpu_cap(X86_FEATURE_FRED)on/默认无单独分支

真正打开硬件机制的是 CR4.FRED。启用顺序:写 FRED MSR → 置 CR4.FREDidt_invalidate()(作废 IDT,再走 IDT 视为 bug)。

BSP 上常见调用链:setup_arch()cpu_init_replace_early_idt()cpu_init_fred_exceptions();AP 在 cpu_init() 里再配一遍。MSR = Model Specific Register(型号相关寄存器),按编号用 RDMSR/WRMSR 访问。wrmsrq()asm/msr.h)是内核「写 64 位到某 MSR」的封装,底层就是 WRMSR

// arch/x86/kernel/fred.c:28-69
void cpu_init_fred_exceptions(void)
{
	...
	wrmsrq(MSR_IA32_FRED_CONFIG,
	       FRED_CONFIG_REDZONE |
	       FRED_CONFIG_INT_STKLVL(0) |
	       FRED_CONFIG_ENTRYPOINT(asm_fred_entrypoint_user));
	...
	cr4_set_bits(X86_CR4_FRED);
	/* Any further IDT use is a bug */
	idt_invalidate();

	/* Use int $0x80 for 32-bit system calls in FRED mode */
	setup_clear_cpu_cap(X86_FEATURE_SYSFAST32);
	setup_clear_cpu_cap(X86_FEATURE_SYSCALL32);
}

idt_invalidate() 把 IDTR 载成空表:

// arch/x86/kernel/idt.c:337-344
void idt_invalidate(void)
{
	static const struct desc_ptr idt = { .address = 0, .size = 0 };

	load_idt(&idt);
}

4.2 syscall / int 仍可用;换的是底层,不是禁指令

启用 FRED 以后,用户态照样可以执行 syscall / sysenter,以及 int n / int3 等——指令还能发,变的是 CPU 进核路径4

指令 FRED 打开后
syscall / sysenter 仍可执行 → FRED event delivery(不再走旧 LSTAR/SYSENTER 快路径)
int n / int3 仍可执行 → FRED event delivery(不再查 IDT;门上 DPL 检查改由软件按 type/vector 做)
SYSRET / SYSEXIT 执行会 #UD;内核出口必须改用 ERETU/ERETS

返回指令全称:ERETU = Event Return to User(回 ring 3);ERETS = Event Return to Supervisor(仍在 ring 0)。用户态一般不发 SYSRET换出口的是内核

用户态调用约定(64 位)基本不变:仍发 syscall,调用号/RAX、参数寄存器约定照旧,最后仍常进 do_syscall_64。变的是 CPU/内核进核路径与返回指令。32 位:Linux 会清掉快路径 capability,多走 int $0x80

4.3 FRED 寄存器:自有 MSR,并复用少量旧 MSR

规范:CR4.FRED = 0 才走 existing SYSCALL= 1 则 FRED event delivery4入口跳转不再靠 LSTAR,但 FRED 不是和传统 syscall「完全另一套、零重叠」——规范 §4.4 写明仍用若干已有 MSR4

syscall 投递而言:

MSR FRED 启用后
IA32_LSTAR 不参与入口跳转(入口在 FRED_CONFIG
IA32_FMASK 不参与旧那套 RFLAGS 掩码路径
IA32_STAR 仍用:投递 / ERETU 时装 CS、SS 时从 STAR 推导(§4.4)4
IA32_FRED_CONFIG 使用

FRED 配套状态(规范 §4.2–4.4;PDF 约第 13–15 页)4

状态 作用
CPUID (EAX=7,ECX=1):EAX bit 17 / 18 枚举 FRED / LKGS(LKGS 可独立于 FRED)
CR4.FRED(CR4 bit 32) 总开关
IA32_FRED_CONFIG(0x1D4) 入口页、CSL、red zone、ring0 可屏蔽中断栈级别等
IA32_FRED_RSP0RSP3(0x1CC–0x1CF) 四个 stack level 的栈指针(数字是栈级别,不是 ring)
IA32_FRED_STKLVLS(0x1D0) 向量 0–31(及 NMI)在 ring 0 至少升到哪一级栈
IA32_FRED_SSP1SSP3(0x1D1–0x1D3)+ IA32_PL0_SSP(作 SSP0) shadow stack(开 CET 时)
IA32_STAR 见上,装 CS/SS
IA32_KERNEL_GS_BASE ring3 投递 / ERETU 时交换 GS(替代软件 SWAPGS

与传统 syscall MSR 不是同一套:主用 FRED_* + CR4.FRED;重叠主要是 STAR(CS/SS) 和 GS 相关基址。内核启动时仍可能写过 LSTAR,但 FRED 已开的 CPU 上 syscall 进核不靠它跳

CPU 定入口:ring 3 事件 RIP = FRED_CONFIG & ~0xFFF;ring 0 事件 = 该页基址 +256。Linux 把页对齐入口放在 asm_fred_entrypoint_user,+256 是 asm_fred_entrypoint_kernel

4.4 事件分发:统一门口,处理在 C 里

跳进 asm_fred_entrypoint_user 之前,硬件已做完 FRED event delivery(切栈、压 FRED 帧、装 CS/SS/RIP、必要时换 GS)。入口只是软件侧的统一门口:

// arch/x86/entry/entry_64_fred.S:18-45
.macro FRED_ENTER
	PUSH_AND_CLEAR_REGS
	movq	%rsp, %rdi	/* %rdi -> pt_regs */
.endm

.macro FRED_EXIT
	POP_REGS
.endm

SYM_CODE_START_NOALIGN(asm_fred_entrypoint_user)
	FRED_ENTER
	call	fred_entry_from_user
	FRED_EXIT
1:	ERETU

这几行汇编是用户态事件的统一门口 + 出口不是把 int/syscall/异常业务全做完——业务在 fred_entry_from_user 及其调用的 handler 里。内核里再发生的事件走 asm_fred_entrypoint_kernel(同页 +256),出口是 ERETS

fred_entry_from_user()regs->fred_ss.type 做第一级分发:

// arch/x86/entry/entry_fred.c:238-267
__visible noinstr void fred_entry_from_user(struct pt_regs *regs)
{
	unsigned long error_code = regs->orig_ax;
	regs->orig_ax = -1;

	switch (regs->fred_ss.type) {
	case EVENT_TYPE_EXTINT:
		return fred_extint(regs);
	case EVENT_TYPE_NMI:
		...
	case EVENT_TYPE_HWEXC:
		return fred_hwexc(regs, error_code);
	case EVENT_TYPE_SWINT:
		return fred_intx(regs);
	...
	case EVENT_TYPE_OTHER:
		return fred_other(regs);
	default: break;
	}
	return fred_bad_type(regs, error_code);
}
事件类型 含义 Linux 处理
EVENT_TYPE_EXTINT 外部中断 fred_extintsysvec_table[] / common_interrupt
EVENT_TYPE_HWEXC 硬件异常 fred_hwexcexc_*
EVENT_TYPE_NMI NMI fred_exc_nmi
EVENT_TYPE_OTHER 含 SYSCALL fred_otherdo_syscall_64
软中断 / INT n 等 fred_intx / fred_swexc  

64 位 syscall 在 FRED 下的落点:

// arch/x86/entry/entry_fred.c:78-85
static __always_inline void fred_other(struct pt_regs *regs)
{
	if (likely(regs->fred_ss.vector == FRED_SYSCALL && regs->fred_ss.l)) {
		regs->orig_ax = regs->ax;
		regs->ax = -ENOSYS;
		do_syscall_64(regs, regs->orig_ax);
		return;
	}
	...
}

规范写得很直白:当 CR4.FRED = 1 时,用 FRED event delivery 替换既有 SYSCALL 操作;返回应使用 ERETU 而非 SYSRET(FRED 下 SYSRET#UD4

因此 FRED 下:

flowchart TD
    A["事件发生"] --> B["FRED event delivery"]
    B --> C["asm_fred_entrypoint_user"]
    C --> D["fred_entry_from_user"]
    D --> E{"fred_ss.type"}
    E -->|EXTINT| F["fred_extint"]
    E -->|HWEXC| G["fred_hwexc"]
    E -->|OTHER| H["fred_other"]
    E -->|NMI 等| I["对应 handler"]
    H -->|FRED_SYSCALL| J["do_syscall_64"]
    F --> K["sysvec_table 或 common_interrupt"]

内核文档把这套分发概括为两级:先按事件类型,再按向量2

4.5 FRED 并没有「简化掉」的东西

机制 FRED 后
pt_regs 仍在;布局扩展(fred_cs/fred_ss,另有 fred_info.edata
MSR 更多(FRED_CONFIGFRED_RSP*FRED_STKLVLS
栈级别 / 独立栈 IST 模型 → FRED stack level 模型
返回 ERETS / ERETU 替代 IRET / SYSRET
SWAPGS FRED 启用时非法(#UD);改 LKGS / 硬件 GS 交换42

价值在统一投递与返回语义(更低延迟、原子上下文、edata 替代 CR2/DR6 瞬态等),不是删除所有内核数据结构。

4.6 硬件统一了什么;软件仍要做什么

投递与返回这一层是硬件替代;内核仍要做 type × vector 软件分发

原先(分轨) FRED 启用后(硬件)
IDT event delivery(中断/异常/INT n 等查门) FRED event delivery:不读 IDT,按 FRED_CONFIG 等 MSR 建上下文与栈帧
SYSCALL / SYSENTER 各自的 MSR 快路径 同一条 FRED event delivery(规范 §3 / §7.4)4
IRET / SYSRET / SYSEXIT 返回 ERETU(回 ring 3)/ ERETS(仍在 ring 0);FRED 下 SYSRET/SYSEXIT/SWAPGS 等为 #UD
IST(IDT 门 IST + TSS.ist[]) stack level 0–3 + FRED_RSP0–3 / FRED_STKLVLS
入核后常靠 SWAPGS 切 GS 投递 / ERETU 时硬件交换 GS 基址;属性用 LKGS

内核侧仍须软件完成:

一句话:FRED 用硬件统一「怎么进 ring 0 / 怎么回」(含原 IDT 事件 + SYSCALL/SYSENTER);「进核后调哪个 handler」仍是内核软件表

不是任务切换。 FRED 管的是事件进核/返回(ring 切换),不是 schedule / switch_to 那种进程、线程上下文切换。调度路径照旧。

也可以说:进核之后相对统一(pt_regs → handler → 返回约定);复杂度主要在切换过程(IDT/RSP0/IST vs MSR vs FRED 投递与栈级别)。FRED 收的是后一层。

4.7 TSS.RSP0 / IST:只属于 IDT 路径;#DF 仍建 pt_regs

TSS.RSP0 / IST 是 IDT(中断/异常/INT n)投递时的硬件切栈机制,传统 64 位 syscall 不走

  IDT 路径 64 位 SYSCALL
是否查 IDT
硬件切栈 用户→ring 0 且门 IST=0 → TSS.RSP0;IST≠0 → TSS.IST[] 按门做 RSP0/IST;硬件不保存、不切换 RSP
Linux 做法 常经 entry trampoline / sync_regs 等到线程栈 entry_SYSCALL_64:用户 RSP 可暂存 TSS.sp2(scratch),再接到 cpu_current_top_of_stack

SYSCALL 入口里出现的 TSS 字段(如 sp2)只是内核拿 TSS 当 scratch,不是 IDT 那套「硬件按 RSP0/IST 自动切栈」。FRED 启用后,切栈改由 FRED stack level + FRED_RSP*,与旧 IST/RSP0 模型并列替代,而非「给 SYSCALL 补上 RSP0」。

IST 地址存在 TSS.IST[]:门上 IST 字段选槽,硬件从那里取栈顶。#DF 等即使用 IST、且常已在内核,仍会在应急栈上建 pt_regs(常见经 paranoid_entryPUSH_AND_CLEAR_REGS),不是「内核里 fault 就跳过 pt_regs」。


五、FRED 省了哪些步骤;快在哪里

FRED 不是把 syscall / IDT 路径整段删掉,而是把「进核 / 回用户」换成另一套硬件原语。省下的是旧机制里若干段,不是 do_syscall_64 / common_interrupt 之后的内核业务。

5.1 相对 IDT:少做 / 换掉的

旧步骤 FRED 怎么处理
读 IDTR、按向量取 IDT 门(内存查表) 不查 IDT;按已配好的 FRED_CONFIG 等进入口
门上的类型 / DPL 等硬件检查 投递侧不做这套;INT n 等需 软件按 type/vector 自己拦4
TSS.RSP0 / IST 切栈 改用 stack level + FRED_RSP*
IRET 帧(SS/RSP/RFLAGS/CS/RIP ± error code) FRED 帧(含 type/vector/edata 等,且始终带 error code 槽)
入核后常要 SWAPGS 投递 / ERETU硬件交换 GS 基址
IRET 返回(语义重,且与无条件放开 NMI 等纠缠) ERETU / ERETS
#PFCR2、调试相关读 DR6 的瞬时窗口 附加信息进帧上的 edata2

5.2 相对传统 SYSCALL:少做 / 换掉的

旧步骤 FRED 怎么处理
另一套 LSTAR/STAR/FMASK 快路径(与 IDT 并行 SYSCALL/SYSENTER 并入 FRED event delivery
硬件 几乎不压栈,入口里 整段手工搭 pt_regs 硬件先建 FRED 帧;软件主要补通用寄存器(FRED_ENTER
入/出核 swapgs + 自己切栈 GS / 栈级别多由 FRED 硬件处理
快路径 SYSRET,不行再退 IRET 统一用 ERETU(FRED 下 SYSRET#UD

do_syscall_64 本身还在,只是前面从 entry_SYSCALL_64 换成 fred_other → 同一条 C 分发。

5.3 并没有「省掉」的

5.4 延迟:对标谁、别指望什么

规范与内核文档写明的目标是:用更低延迟的转移替换 IDT event deliveryIRET42

对比对象 含义
相对 IDT + IRET 预期赢家:少 IDT 门访存、用更轻的返回原语替代 IRET、少 SWAPGS/ESPFIX/CR2/DR6/NMI 边角软件。不查 IDT 是确定的;「因此一定更快」是设计意图,不是保证的定量结论。具体周期数依赖微架构与场景。
相对传统 SYSCALL+SYSRET 不是主打「再砍一刀 syscall」。旧 syscall 本就不走 IDT;FRED 把 SYSCALL 收进统一投递,换来一致性和更完整硬件帧。纯 64 位 syscall 热路径上,旧 SYSRET 往往仍很强。
flowchart LR
    subgraph Win["FRED 延迟目标主要对标"]
        IDT["IDT 查门"] --> IRET["IRET 返回"]
    end

    subgraph Unify["统一进同一模型"]
        SYS["SYSCALL / SYSENTER"]
        IRQ["IRQ / 异常"]
    end

    IDT -.->|替换| FRED["FRED delivery + ERETU/ERETS"]
    IRET -.->|替换| FRED
    SYS --> FRED
    IRQ --> FRED

一句话:快在「替代 IDT 查门 + 替代笨重 IRET(及连带 SWAPGS/IST/边角软件)」这类通用事件进/出;syscall 并入同一硬件投递,主要换一致性和完整性,延迟要和旧 SYSRET 路径分开评价。


六、三路总览

类型 传统:怎么进内核 传统:pt_regs FRED:怎么进内核 FRED:pt_regs
外部 IRQ IDT[vector] → irq stub HW IRET 帧 + SW 补全 FRED EXTINT → fred_extint HW FRED 帧 + FRED_ENTER
CPU 异常 #PF 等 IDT → asm_exc_* 同上 FRED HWEXC → fred_hwexc 同上;fault 地址用 fred_event_data(regs)
64 位 syscall MSR_LSTAR → entry_SYSCALL_64 全软件 push FRED OTHER → fred_otherdo_syscall_64 FRED 帧 + FRED_ENTER
int $0x80 IDT 向量 0x80 IDT 路径 FRED 软件中断分支 依 FRED 实现
#DF / NMI IDT + IST IST 栈 FRED stack level 3/2 等 FRED_RSP*

七、容易踩的坑

  1. 「硬件中断 = 软件扫 IDT 表」 → CPU 按向量索引查一项门描述符1
  2. 「syscall 完全靠 MSR,与中断无关」 → 传统 64 位对;int 0x80 仍走 IDT;FRED 下 syscall 是 FRED 事件4
  3. 「两种方式进内核后 pt_regs 构建相同」 → 结果都是 pt_regs *,过程不同(HW IRET vs 全软件 push vs FRED 帧)。
  4. 「FRED 让 IDT、MSR、pt_regs 都消失」 / 「FRED 不用 pt_regs」 → 仅统一投递;pt_regs 仍在;启用后会 idt_invalidate()(§4.1)。
  5. 「TSS.RSP0 / IST 也管 SYSCALL」 / 「RSP0 = 该进程内核栈」 → 否;传统 syscall 不用 RSP0/IST;Linux 上 RSP0 常是 entry 栈(§4.7)。
  6. 「IST / #DF 不用 pt_regs」 → 仍建;IST 只换栈(§3、§4.7)。
  7. 「FRED 打开后就不能发 syscall/int → 指令仍可用;出口改 ERETUSYSRET/SYSEXIT#UD(§4.2)。
  8. 「FRED 下完全不用旧 MSR」LSTAR/FMASK 不参与跳转/旧掩码STAR 仍用于 CS/SS;另有整套 FRED MSR(§4.3)。
  9. 「FRED 统一了任务切换」 → 否;统一的是事件进核/返回,不是 schedule(§4.6)。
  10. 「FRED 一定比旧路径更快」 → 相对 IDT+IRET 是设计目标;相对 SYSRET 须分开评价(§5.4)。
  11. CONFIG_X86_FREDfred= 都是编译选项」 → 前者编译期,后者引导参数(§4.1)。
  12. 「生产环境已在用 FRED」 → 需 CPU 支持 + 配置;可用 fred=off 关闭5

后续如果单独跟一遍 FRED 栈级别和 KVM 路径,再写一篇。


References

  1. Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A — System Programming Guide, Part 1(约 §5.8.8 SYSCALL/SYSRET、§6.14 Interrupt 64-Bit Mode)。官方入口:https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html  2 3 4 5

  2. Linux 内核文档,Documentation/arch/x86/x86_64/fred.rst — two-level dispatch;完整上下文;LKGS / SWAPGS  2 3 4 5 6

  3. x86 Instruction Reference — SYSCALLRCX/R11LSTARFMASK、不保存 RSP 等指令级语义  2

  4. Intel,Flexible Return and Event Delivery (FRED) Specification,346446-004US Rev 4.0(May 2023)。§3 Overview;§4.2–4.4(约第 13–15 页:CR4.FRED、新 MSR、复用 STAR/KERNEL_GS_BASE);§5 投递入口 RIP;§7.4:CR4.FRED=1SYSCALL 改 FRED,返回用 ERETUSYSRET/SYSEXIT#UD)  2 3 4 5 6 7 8 9 10 11 12 13 14 15

  5. Linux,Documentation/admin-guide/kernel-parameters.txtfred=on|offon 为默认。解析:cpu_parse_early_param()  2 3