IDT、SYSCALL 与 FRED:三条内核入口,同一份 pt_regs?
中断走 IDT、syscall 走 MSR——这两句在默认路径上大体成立;FRED 打开之后,它们就不再是两套并行模型了。
讨论 x86-64 Linux 怎么进内核时,常听到四句:
- 硬件中断要查 IDT
- syscall 直接用 MSR
- 进内核后都要构建
pt_regs - FRED 把上面这些都简化了
它们在非 FRED路径上部分成立,但不能混为一谈,更不能直接推广到 FRED。下面先给出可正确复述的限定版,再对照 Intel SDM、FRED 规范与当前 Linux arch/x86 展开。
一、四句常见说法:对不对?
1.1 可正确复述的四句(带出处)
若要保证说法正确,宜写成:
-
硬件中断 / CPU 异常:默认走 IDT;FRED 启用后走 FRED event delivery,不再查 IDT。
「查 IDT」指 CPU 按向量读一项门描述符(SDM Vol 3A 约 §6.14)1,不是内核扫整张表。Linux 在启用 FRED 时调用idt_invalidate()清空 IDTR,并在注释中写明 Any further IDT use is a bug(fred.c)。之后由软件按 type × vector 两级分发(内核文档 Software based event dispatching)2,实现见fred_entry_from_user()。 -
64 位
syscall:默认经STAR/LSTAR/FMASK入核、不进 IDT;FRED 启用后同一条指令改由 FRED 投递,再落到do_syscall_64。
非 FRED 时:SYSCALL不查 IDT,入口 RIP 来自IA32_LSTAR(SDM 约 §5.8.8;指令级语义见3)。Linux 在idt_syscall_init()把MSR_LSTAR写成entry_SYSCALL_64。FRED 规范 §3 / §7.4:启用 FRED 后,SYSCALL/SYSENTER改用 FRED event delivery,返回应走ERETU而非SYSRET4。Linux 在fred_other()识别FRED_SYSCALL后调用do_syscall_64。另:int $0x80等仍属中断类路径,勿与 64 位syscall混谈。 - C 层几乎都拿
pt_regs *,但帧怎么来的不同。- IDT:硬件压 IRET 帧,再由入口汇编补全(
error_entry/PUSH_AND_CLEAR_REGS等)。 - 传统 SYSCALL:硬件不压完整栈帧;
entry_SYSCALL_64手工 push 用户态部分再PUSH_AND_CLEAR_REGS。 - FRED:硬件建立 FRED 栈帧(自
orig_ax起,见ptrace.h注释);FRED_ENTER再补通用寄存器。
结果同为struct pt_regs *,构建过程不可互换叙述。
- IDT:硬件压 IRET 帧,再由入口汇编补全(
- FRED 统一的是事件投递与返回,不是删掉
pt_regs、MSR 或软件分发表。
FRED 规范 §3:凡原会走 IDT 的事件改走 FRED event delivery;SYSCALL/SYSENTER亦并入;返回用ERETS/ERETU4。内核文档强调两级软件分发与完整上下文,而非「去掉所有表」2。Linux 仍保留扩展后的pt_regs、更多 FRED MSR(cpu_init_fred_exceptions()),以及sysvec_table等软件表。引导参数fred=on|off(on 为默认)见5。
压成一句:
非 FRED:中断/异常查 IDT,64 位
syscall走 MSR,两边都落到(不同构建方式的)pt_regs。** FRED:**统一投递与返回——IDT 作废、SYSCALL也进 FRED;pt_regs仍在,MSR 与软件分发仍在。
1.2 常见说法 ↔ 路径对照
| # | 常见说法 | 传统路径(IDT + SYSCALL) | FRED 路径 |
|---|---|---|---|
| 1 | 硬件中断要查 IDT | 外部 IRQ 与 CPU 异常由 CPU 按向量号查 IDT 投递 | idt_invalidate() 后不再走 IDT;改 FRED event delivery,软件按 type/vector 分发 |
| 2 | syscall 直接用 MSR | 64 位 syscall 用 MSR_LSTAR/STAR/FMASK,不进 IDT |
SYSCALL 变为 FRED 事件(EVENT_TYPE_OTHER),经 FRED 入口到 do_syscall_64() |
| 3 | 进内核后都要构建 pt_regs |
C 层 handler 几乎都拿 struct pt_regs * |
仍有 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
2.1 硬件中断 / CPU 异常 → IDT
按 SDM Vol 3A 对 64-bit 中断投递的描述,大致是:
- 中断控制器(APIC 等)给出向量号;
- CPU 用 IDTR → IDT[vector] 读门描述符;
- 硬件压 IRET 帧(SS、RSP、RFLAGS、CS、RIP;异常可能还有 error code);
- 若门描述符 IST ≠ 0,切换到 TSS 中对应 IST 栈。
「查 IDT」是 CPU 按向量索引一项门描述符,不是内核遍历整张表。
外部 IRQ 在 Linux 里的软件链可以写成:
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)(arch/x86/kernel/irq.c)
参见:idtentry.h · entry_64.S · irq.c
外部 IRQ 与 #PF/#DE 等共用 IDT 机制,但 stub 宏不同(idtentry_irq vs idtentry)。用户态触发的 IRQ/异常,常经 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_regs 由 entry_SYSCALL_64 软件搭建。这不是「MSR 一步到位进 C 函数」。
其他系统调用入口仍走 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 慢路径 |
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_ENTER → PUSH_AND_CLEAR_REGS |
常见误读:
- 「syscall 像中断一样由硬件压 IRET 帧」→ SYSCALL 不压完整帧。
- 「进了内核就有完整
pt_regs」→ 是入口汇编 +calling.h宏搭出来的,不是 C 结构体被硬件 memcpy。 - 「IRQ 向量在某个专用寄存器」→ stub 用
push imm8占位orig_ax,C 层再解释为向量号。
struct pt_regs 布局见 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:统一了什么,没删掉什么
FRED(Flexible Return and Event Delivery)用事件投递 + ERETS/ERETU 替代传统 IDT + IRET 模型42。规范写明设计目标包括:用更低延迟的转移替换 IDT 投递与 IRET;事件投递原子建立完整 supervisor 上下文、返回时建立完整 user 上下文。
启用 FRED 后,凡原先会走 IDT 的事件改走 FRED event delivery;SYSCALL / SYSENTER 的既有操作也被替换为 FRED 投递。返回侧用 ERETU(回 ring 3)与 ERETS(仍在 ring 0)4。
4.1 Linux 如何启用
- Kconfig:
CONFIG_X86_FRED(仅 x86_64) - 引导参数:
fred=on|off;文档写明 on 为默认(CPU 支持时启用),fred=off关闭5 - 启用后:
cpu_init_fred_exceptions()写 FRED MSR → 置CR4.FRED→idt_invalidate()
// 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 事件分发:替代 IDT 查表 + 部分 MSR 入口
入口由 MSR_IA32_FRED_CONFIG 指向 asm_fred_entrypoint_user / _kernel。用户态入口:
// arch/x86/entry/entry_64_fred.S:18-45
.macro FRED_ENTER
UNWIND_HINT_END_OF_STACK
ANNOTATE_NOENDBR
PUSH_AND_CLEAR_REGS
movq %rsp, %rdi /* %rdi -> pt_regs */
.endm
...
SYM_CODE_START_NOALIGN(asm_fred_entrypoint_user)
FRED_ENTER
call fred_entry_from_user
...
1: ERETU
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_extint → sysvec_table[] / common_interrupt |
EVENT_TYPE_HWEXC |
硬件异常 | fred_hwexc → exc_* |
EVENT_TYPE_NMI |
NMI | fred_exc_nmi |
EVENT_TYPE_OTHER |
含 SYSCALL | fred_other → do_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;
}
...
}
FRED 规范对 SYSCALL 的修改写得很直白:当 CR4.FRED = 1 时,用 FRED event delivery 替换既有 SYSCALL 操作;返回应使用 ERETU 而非 SYSRET(FRED 下 SYSRET 为 #UD)4。
因此 FRED 下:
- 外部 IRQ:不再由 CPU 查 IDT,但仍按向量软件 dispatch(
sysvec_table/common_interrupt) - 64 位 SYSCALL:不再
LSTAR → entry_SYSCALL_64,改 FRED →fred_other→do_syscall_64 - IST:不再 IDT.IST + TSS.ist[],改
FRED_RSP0–3+FRED_STKLVLS
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.3 FRED 并没有「简化掉」的东西
| 机制 | FRED 后 |
|---|---|
pt_regs |
仍在;布局扩展(fred_cs/fred_ss,另有 fred_info.edata)——不是「不用 pt_regs」 |
| MSR | 更多(FRED_CONFIG、FRED_RSP*、FRED_STKLVLS) |
| 栈级别 / 独立栈 | IST 模型 → FRED stack level 模型 |
| 返回 | ERETS / ERETU 替代 IRET / SYSRET |
| SWAPGS | FRED 启用时非法(#UD);改 LKGS / 硬件 GS 交换42 |
FRED 的价值是统一事件投递与返回语义(更低延迟、原子上下文、edata 替代 CR2/DR6 瞬态等),不是删除所有内核数据结构。C 入口如 fred_entry_from_user(struct pt_regs *regs)、do_syscall_64(regs, …) 照旧吃 pt_regs。
4.4 硬件统一了什么;软件仍要做什么
投递与返回这一层是硬件替代;内核仍要做 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 |
内核侧仍须软件完成:
- 两级分发:硬件只送到
asm_fred_entrypoint_*;再按fred_ss.type→fred_ss.vector(如fred_other→do_syscall_64) pt_regs/ 通用寄存器补全:硬件给 FRED 帧;FRED_ENTER等再补齐- IDT 表本身:
idt_invalidate()后表不再参与投递——不是换一张 IDT,而是硬件换机制
一句话:FRED 用硬件统一「怎么进 ring 0 / 怎么回」(含原 IDT 事件 + SYSCALL/SYSENTER,以及返回与栈级别/GS);「进核后调哪个 handler」仍是内核软件表。
4.5 TSS.RSP0 / IST:只属于 IDT 路径
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」。
五、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 |
#PF 读 CR2、调试相关读 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 并没有「省掉」的
pt_regs仍在(布局扩展)- 软件两级分发(type → vector)——原先 IDT「向量 → 门里的 RIP」有一部分改由软件做
- IRQ / 异常 / syscall 进各自 handler 仍在
- 配置更重:更多 FRED MSR
5.4 延迟:对标谁、别指望什么
规范与内核文档写明的目标是:用更低延迟的转移替换 IDT event delivery 和 IRET42。
| 对比对象 | 含义 |
|---|---|
| 相对 IDT + IRET | 预期赢家:少 IDT 门访存、用更轻的返回原语替代 IRET、少 SWAPGS/ESPFIX/CR2/DR6/NMI 边角软件。具体周期数依赖微架构与场景,无统一公开数字。 |
相对传统 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_other → do_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.1、§4.4–§5.4 对照,下列压缩说法仍易踩坑:
- 「硬件中断 = 软件扫 IDT 表」 → CPU 按向量索引查一项门描述符1。
- 「syscall 完全靠 MSR,与中断无关」 → 传统 64 位对;
int 0x80仍走 IDT;FRED 下 syscall 是 FRED 事件4。 - 「两种方式进内核后 pt_regs 构建相同」 → 结果都是
pt_regs *,过程不同(HW IRET vs 全软件 push vs FRED 帧)。 - 「FRED 让 IDT、MSR、pt_regs 都消失」 / 「FRED 不用 pt_regs」 → 仅统一投递;
pt_regs仍在,MSR 更多,IDT 作废但软件 dispatch 表(sysvec_table)仍在2。 - 「TSS.RSP0 / IST 也管 SYSCALL」 → 否;那是 IDT 切栈。传统
syscall不硬件切RSP0/IST(§4.5)。 - 「FRED 一定比旧 SYSCALL+SYSRET 更快」 → 规范主要对标 IDT+IRET;syscall 并入统一模型,延迟须分开评价(§5.4)。
- 「生产环境已在用 FRED」 → 需 CPU 支持 +
CONFIG_X86_FRED;主线默认在支持时启用,也可用fred=off关闭5。多数发行版与实机是否暴露该 feature,仍取决于硬件与发行配置。
References
-
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 -
Linux 内核文档,
Documentation/arch/x86/x86_64/fred.rst— Software based event dispatching:先按 event type、再按 vector 的 two-level dispatch;以及完整 supervisor/user context、LKGS / SWAPGS ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 -
x86 Instruction Reference — SYSCALL —
RCX/R11、LSTAR、FMASK、不保存RSP等指令级语义 ↩ ↩2 -
Intel,Flexible Return and Event Delivery (FRED) Specification,Document Number 346446-004US,Revision 4.0(May 2023)。§3 Overview:「Any event that would normally cause IDT event delivery … The SYSCALL and SYSENTER instructions will also use FRED event delivery」;§7.4 规定
CR4.FRED=1时SYSCALL改走 FRED,返回应使用ERETU↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 -
Linux 内核参数文档,
Documentation/admin-guide/kernel-parameters.txt(fred=条目)— Format{ on | off };on 为默认 ↩ ↩2 ↩3
系列:Linux 内核
- IDT 与 SYSCALL:差异、演化、Linux 实现与性能
- Linux内核调度的时钟心跳:定时器中断、抢占与实时性的权衡
- 当Linux内核不再「迁就」PostgreSQL:一次抢占模型变更引发的性能风暴
- x86架构下的中断与异常处理:从IDT到FRED的演进之路
- IDT、SYSCALL 与 FRED:三条内核入口,同一份 pt_regs?