IST(Interrupt Stack Table,中断栈表) 📅 2026/8/4 17:40:00 当嵌套中断发生时如果当前CPU正在运行中断处理程序而另一个更高优先级的中断到来CPU需要使用独立的栈来保存上下文避免破坏当前正在执行的处理程序的栈帧。ISTInterrupt Stack Table中断栈表正是x86_64架构为解决这一问题提供的硬件机制。一、为什么需要IST1. 普通中断栈的问题如果所有中断都使用同一个内核栈如TSS.RSP0当嵌套中断发生时时间线 1. 中断A触发 → CPU切换到TSS.RSP0压栈保存上下文 2. 中断A处理程序正在执行 3. 中断B更高优先级触发 → CPU再次使用TSS.RSP0 4. 中断B的压栈会覆盖中断A的上下文 → 栈被破坏 5. 中断A返回时RIP/RFLAGS被覆盖 → 系统崩溃2. IST的解决方案每个CPU核心有一个TSS任务状态段其中包含7个IST入口IST1~IST7每个入口是一个独立的栈指针64位地址。当某个中断门Interrupt Gate被配置为使用特定的IST时CPU在处理该中断时会自动切换到对应的IST栈从而与普通内核栈和其他中断隔离。二、IST的硬件结构TSS中的IST字段x86_64struct tss_struct { uint32_t reserved1; uint64_t rsp0; // 普通内核栈ring 0 uint64_t rsp1; // ring 1 栈一般不用 uint64_t rsp2; // ring 2 栈一般不用 uint64_t reserved2; uint64_t ist1; // IST1 栈指针 uint64_t ist2; // IST2 栈指针 uint64_t ist3; // IST3 栈指针 uint64_t ist4; // IST4 栈指针 uint64_t ist5; // IST5 栈指针 uint64_t ist6; // IST6 栈指针 uint64_t ist7; // IST7 栈指针 // ... 其他字段 };IDT门描述符中的IST字段每个中断门Interrupt Gate描述符中有一个3位的IST字段bits 32-34IDT Gate Descriptor (16字节x86_64): ------------------------------------ | offset 63..48 | IST (3 bits) | - 指定使用哪个IST ------------------------------------ | offset 47..32 | P | DPL | 类型 | ------------------------------------ | offset 31..16 | 段选择子 (CS) | ------------------------------------ | offset 15..0 | 保留 | ------------------------------------IST 0使用TSS.RSP0普通内核栈IST 1~7分别使用TSS.ist1~TSS.ist7三、IST的工作流程以嵌套中断为例场景NMI不可屏蔽中断嵌套在普通中断中初始状态 - 当前运行在用户态 (ring 3) - TSS.RSP0 内核栈A - TSS.ist1 NMI专用栈 (独立8KB) - IDT中的NMI门 IST字段 1步骤1普通中断如定时器触发1. CPU从用户态切换到内核态 2. 使用 TSS.RSP0 → 切换到内核栈A 3. 压栈SS, RSP用户态, RFLAGS, CS, RIP用户态 4. 执行中断处理程序步骤2NMI中断嵌套进来1. NMI触发更高优先级 2. CPU检查NMI门描述符IST字段 1 3. CPU读取 TSS.ist1 → 切换到NMI专用栈B而不是内核栈A 4. 在NMI栈B上压栈SS, RSP内核栈A, RFLAGS, CS, RIP 5. 执行NMI处理程序 6. NMI处理完成 → iretq → 从NMI栈B弹出上下文 7. 恢复RSP指向内核栈A 8. 继续执行原来的定时器中断处理程序关键点NMI使用了独立的IST栈因此不会破坏定时器中断的栈帧嵌套层次越深每次切换到一个新的IST栈如果配置了多个IST如果中断门配置了ISTCPU完全忽略当前的RSP直接使用IST指定的栈四、IST的实际应用场景中断类型典型IST配置原因NMI不可屏蔽中断IST1NMI随时可能发生必须使用独立栈防止破坏其他中断栈#DF双重故障IST2双重故障通常意味着栈已损坏需要独立栈来记录错误#MC机器检查IST3硬件错误可能需要独立栈来保存错误状态#DB调试异常可选IST4调试器需要隔离环境普通IRQIST0使用RSP0大多数中断共享普通内核栈Linux内核中的配置// Linux arch/x86/include/asm/irq_vectors.h #define NMI_VECTOR 0x02 #define MCE_VECTOR 0x12 #define DB_VECTOR 0x01 // Linux arch/x86/kernel/entry_64.S // NMI使用 IST1 idtentry NMI do_nmi asm_nmi has_error_code0 ist1 // #DF使用 IST2 idtentry DOUBLEFAULT do_double_fault asm_double_fault has_error_code1 ist2 // #MC使用 IST3 idtentry MCE do_mce asm_mce has_error_code0 ist3五、IST的局限与注意事项1. IST栈的大小固定每个IST栈通常分配8KB或16KB。如果嵌套中断过多极端情况仍然可能栈溢出。// Linux 中 IST 栈大小定义 #define IST_STACK_SIZE (PAGE_SIZE * 2) // 通常 8KB2. 仅适用于x86_64长模式IST是x86_64长模式特有的功能在32位保护模式下不存在32位使用TSS的esp0单栈机制。3. 配置错误会引发#GP如果在IDT中设置IST7但TSS中ist7未正确初始化触发该中断时会引发通用保护故障#GP。4. 需配合SWAPGS使用在内核入口处通常需要执行SWAPGS交换GS基址来访问内核数据结构IST栈切换本身不会自动处理GS需要手动管理。六、完整中断栈切换流程含IST中断触发 ↓ CPU从IDT读取门描述符 ↓ 检查门描述符中的 IST 字段 ↓ ------------------------------------ | IST ! 0 | IST 0 | ------------------------------------ ↓ ↓ 从 TSS.istN 加载 RSP 从 TSS.RSP0 加载 RSP ↓ ↓ 切换到IST专用栈 切换到普通内核栈 ↓ ↓ 压栈SS, RSP, RFLAGS, CS, RIP ↓ 执行中断处理程序 ↓ iretq弹出上下文恢复RSP七、实际代码示例定义IST栈section .data align 16 nmi_stack_bottom: times 8192 db 0 ; 8KB NMI专用栈 nmi_stack_top: section .text extern tss_struct ; 初始化TSS中的ist1 mov rax, nmi_stack_top mov [tss_struct TSS_IST1], raxIDT条目设置IST1; 设置NMI中断门向量2IST1 mov rax, (nmi_handler 0xFFFFFFFF) ; 低32位偏移 mov [idt 2*16 0], ax mov [idt 2*16 2], KERNEL_CS ; 段选择子 mov word [idt 2*16 4], (1 9) | 0x8E00 ; IST1, 中断门 shr rax, 16 mov [idt 2*16 6], ax shr rax, 16 mov [idt 2*16 8], eax八、总结问题答案为什么需要IST防止嵌套中断破坏正在执行的中断栈帧IST在哪里配置TSS任务状态段中的ist1~ist7字段如何指定使用ISTIDT门描述符中的3位IST字段1~7IST0表示什么使用TSS.RSP0普通内核栈谁使用ISTNMI、#DF、#MC等关键异常普通IRQ通常不用IST与RSP0的区别IST是专用栈RSP0是普通内核栈IST用于关键/嵌套场景