ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux x86_64 中断描述符表(IDT)完全解析:向量、门描述符、错误码与 IST 机制

Linux x86_64 中断描述符表(IDT)完全解析:向量、门描述符、错误码与 IST 机制 文档教程操作系统【免费下载链接】linux-insidesA book-in-progress about the Linux kernel and its insides.项目地址https://gitcode.com/gh_mirrors/li/linux-insides点击查看免费下载中断描述符表Interrupt Descriptor TableIDT是 x86_64 架构中连接中断/异常向量号与处理器入口的关键数据结构也是 Linux 内核中断子系统一切工作的起点。本文以 linux-insides 仓库 X86/linux-x86-2.md 为骨架结合 Interrupts 章节与 Initialization 章节的源码级讲解系统梳理中断与异常的三大来源、异常分类、向量号分配、两类错误码格式、三种门描述符的 128 位布局以及 Linux 内核中对应的 C 结构体与 Interrupt Stack TableIST机制。读完后你将能够独立读懂arch/x86/include/asm/traps.h与arch/x86/include/asm/desc_defs.h中的核心定义理解中断从触发到处理器切换到处理程序入口的完整硬件流程。中断与异常的三大来源在 x86 体系下所有打断处理器正常执行流的事件可以归结为三个一般性来源异常Exceptions同步synchronous事件由处理器在执行指令时自行产生例如除零、缺页。它们与当前指令的执行严格同步软件中断Software interrupts同步事件由软件通过int n等指令主动触发历史上常用于系统调用入口外部中断External interrupts异步asynchronous事件由外部硬件设备如网卡、键盘控制器通过 Local APIC 或 I/O APIC 送达处理器与当前执行的指令流没有时序关联。从同步性角度理解异常和软件中断都发生在指令流中的确定位置处理器能够精确定位是哪条指令而外部中断可以在任意时刻到来处理器无法把它归因于某一条正在执行的指令。这一区分决定了后续异常分类与处理方式的不同。异常的分类Fault、Trap 与 Abort异常根据其可恢复性与报告时机被精确地划分为三类类型报告时机保存的%rip可恢复性Fault故障在引发异常的指令之前的边界上报指向引发故障的那条指令处理程序修正条件后指令可被重新执行Trap陷阱在引发异常的指令之后的边界上报指向引发陷阱指令的下一条指令被中断的程序可以继续执行Abort中止无精确边界不可精确定位不确定通常不允许可靠的程序重启Fault 的典型例子是页故障Page Fault处理器在访问缺失页表项时报告异常内核补上映射后重新执行原指令即可继续。Trap 的典型例子是int3断点与syscall返回路径。Abort 则是严重硬件错误如机器检查#MC、双重故障#DF此时系统状态已不可靠通常无法安全恢复。可屏蔽中断与非屏蔽中断除了同步/异步的划分中断还可按是否受IF标志位约束分为两类可屏蔽中断Maskable interrupts只有RFLAGS.IF 1时才会触发中断处理机制若IF被清零这些中断会一直被挂起pending直到IF重新置位。在 Linux 内核中cli与sti两条指令分别对应清除与设置IF位例如arch/x86/include/asm/irqflags.h中的native_irq_disable()/native_irq_enable()即封装了这两条指令非屏蔽中断Nonmaskable interruptsNMI不受RFLAGS.IF影响总是被处理器接受。但需要注意一旦发生 NMI处理器会屏蔽后续的 NMI直到执行iret在 64 位模式为iretq指令后才解除屏蔽。NMI 的特殊性在于它通常映射硬件级严重故障如 ECC 内存错误、看门狗超时因此它无法被软件推迟Linux 内核为其准备了专用的NMI_STACKIST 栈与独立的嵌套处理逻辑详见下文非屏蔽中断NMI一节。中断向量号与 IDT每个具体的异常或中断源都被分配一个固定的向量标识号vector number也常直接称为中断向量。中断处理机制正是通过这个向量号在 IDT 中定位对应的系统软件服务例程即中断/异常处理程序处理器最多提供256 个唯一的中断向量编号 0255前 32 个向量031被保留给处理器预定义的异常与中断条件向量 32255 未保留操作系统可自由用于外部硬件中断、处理器间中断IPI等用途。这 32 个保留向量的名称与编号在 Linux 内核的arch/x86/include/asm/traps.h中以下述枚举定义原文代码仓库未收录该内核源文件此处作为规范说明/* Interrupts/Exceptions */ enum { X86_TRAP_DE 0, /* 0, Divide-by-zero */ X86_TRAP_DB, /* 1, Debug */ X86_TRAP_NMI, /* 2, Non-maskable Interrupt */ X86_TRAP_BP, /* 3, Breakpoint */ X86_TRAP_OF, /* 4, Overflow */ X86_TRAP_BR, /* 5, Bound Range Exceeded */ X86_TRAP_UD, /* 6, Invalid Opcode */ X86_TRAP_NM, /* 7, Device Not Available */ X86_TRAP_DF, /* 8, Double Fault */ X86_TRAP_OLD_MF, /* 9, Coprocessor Segment Overrun */ X86_TRAP_TS, /* 10, Invalid TSS */ X86_TRAP_NP, /* 11, Segment Not Present */ X86_TRAP_SS, /* 12, Stack Segment Fault */ X86_TRAP_GP, /* 13, General Protection Fault */ X86_TRAP_PF, /* 14, Page Fault */ X86_TRAP_SPURIOUS, /* 15, Spurious Interrupt */ X86_TRAP_MF, /* 16, x87 Floating-Point Exception */ X86_TRAP_AC, /* 17, Alignment Check */ X86_TRAP_MC, /* 18, Machine Check */ X86_TRAP_XF, /* 19, SIMD Floating-Point Exception */ X86_TRAP_IRET 32, /* 32, IRET Exception */ };内核中以X86_TRAP_*命名的这些宏贯穿始终例如早期初始化用X86_TRAP_PF判断页故障用X86_TRAP_NMI、X86_TRAP_DF为 NMI 和双重故障配置 IST 栈。向量号范围还有硬性校验例如set_intr_gate等函数中的BUG_ON((unsigned)n 0xFF)确保不会越界写入 IDT详见 Initialization/linux-initialization-2.md。错误码Error Codes两类硬件格式处理器在把控制权转移到异常处理程序时会为部分异常通过异常机制压入一个错误码error code用于报告错误与状态信息。错误码具有两种格式大多数报错异常的通用格式Selector 错误码页故障专用格式Page-Fault 错误码。Selector 错误码格式31 16 15 3 2 1 0 ------------------------------------------------------------------------------- | | | T | I | E | | Reserved | Selector Index | - | D | X | | | | I | T | T | -------------------------------------------------------------------------------各字段含义EXT位 0置 1 表示异常源在处理器外部清 0 表示异常源在处理器内部IDT位 1置 1 表示错误码的选择子索引字段引用的是IDT中的门描述符清 0 表示该字段引用GDT或LDT中的描述符具体由TI位决定TI位 2置 1 表示选择子索引引用LDT中的描述符清 0 表示引用GDT中的描述符Selector Index位 315根据IDT与TI位的取值指定在 GDT、LDT 或 IDT 中的索引。这类错误码出现在#TS、#NP、#SS、#GP、#AC等与段选择子/权限相关的异常中。Page-Fault 错误码格式31 4 3 2 1 0 ------------------------------------------------------------------------------- | | | R | U | R | - | | Reserved | I/D | S | - | - | P | | | | V | S | W | - | -------------------------------------------------------------------------------各字段含义P位 0Present清 0 表示页故障由页不存在引起置 1 表示由页保护违规引起R/W位 1Read/Write清 0 表示引起故障的访问是内存读置 1 表示是写U/S位 2User/Supervisor清 0 表示管理模式CPL0、1 或 2下的访问引发故障置 1 表示用户模式CPL3下的访问引发故障RSV位 3Reserved置 1 表示处理器在页转换表项page-translation-table entry的保留字段中读到了 1即页表项自身编码非法I/D位 4Instruction/Data置 1 表示引发页故障的访问是指令取指instruction fetch。页故障发生时除错误码外处理器还会把故障线性地址写入cr2控制寄存器。Linux 早期缺页处理正是通过native_read_cr2()读取该值、再调用early_make_pgtable按需补建页表详见 Initialization/linux-initialization-2.md。中断控制转移三种门描述符IDT 中每条表项称为 gate门可以容纳三种门描述符之一任务门Task Gate包含一个 TSS 的段选择子用于把异常/中断处理作为一个任务来切换执行中断门Interrupt Gate包含段选择子与偏移量处理器用它把程序执行转移到中断处理程序所在代码段的处理过程进入中断门时处理器会清除IF标志从而在处理期间屏蔽其它可屏蔽硬件中断陷阱门Trap Gate同样包含段选择子与偏移量用于把程序执行转移到异常处理程序所在代码段的处理过程与中断门不同陷阱门不修改IF标志处理期间仍可被可屏蔽中断打断。注意在 64 位长模式long mode下IDT 中只能引用中断门与陷阱门以及任务门在兼容性上的限制x86 的硬件任务切换机制在长模式下被禁用因此任务门在 x86_64 的 Linux 内核中已基本不再使用。门描述符的 128 位通用格式x86_64 下 IDT 表项是 16 字节128 位的门描述符其通用格式如下127 96 ------------------------------------------------------------------------------- | | | Reserved | | | -------------------------------------------------------------------------------- 95 64 ------------------------------------------------------------------------------- | | | Offset 63..32 | | | ------------------------------------------------------------------------------- 63 48 47 46 44 42 39 34 32 ------------------------------------------------------------------------------- | | | D | | | | | | | | Offset 31..16 | P | P | 0 |Type |0 0 0 | 0 | 0 | IST | | | | L | | | | | | | ------------------------------------------------------------------------------- 31 16 15 0 ------------------------------------------------------------------------------- | | | | Segment Selector | Offset 15..0 | | | | -------------------------------------------------------------------------------各字段含义Selector位 15:0 之上的段选择子字段目标代码段的段选择子OffsetOffset 15..0、Offset 31..16、Offset 63..32三段拼接处理过程入口点的 64 位偏移量DPLDescriptor Privilege Level描述符特权级规定了通过软件指令如int n调用该门的特权下限硬件中断与处理器异常会忽略该字段PSegment Present flag段存在标志必须置 1 才是有效描述符引用P0的门会触发#NP段不存在异常ISTInterrupt Stack Table位 3234中断栈表索引详见下文专节Type位 4043门的类型取值涵盖 LDT 段描述符、TSS 描述符、调用门Call gate、中断门Interrupt gate、陷阱门Trap gate与任务门Task gate其余保留位必须为 0。仓库 Initialization/linux-initialization-2.md 中的示意图 IDT 门描述符 以图形方式展示了同样的位域布局并指出在 64 位模式中只有中断门与陷阱门可以合法出现在 IDT 中二者唯一的区别是进入中断门时处理器会清除IF标志、进入陷阱门时保持IF不变。Linux 内核中的 IDT 结构体表示Linux 内核仅x86_64用如下打包结构体表示一个 IDT 门描述符定义于arch/x86/include/asm/desc_defs.h仓库未收录该内核源文件此处引用自 Interrupts/linux-interrupts-1.mdstruct gate_struct64 { u16 offset_low; u16 segment; unsigned ist : 3, zero0 : 5, type : 5, dpl : 2, p : 1; u16 offset_middle; u32 offset_high; u32 zero1; } __attribute__((packed));位域布局与上文 128 位格式一一对应offset_low/offset_middle/offset_high拼出 64 位处理程序偏移量segment为目标代码段选择子ist3 位、type5 位、dpl2 位、p1 位构成中间的控制字段。__attribute__((packed))保证该结构体在内存中严格按 16 字节紧凑排布与硬件期望的 IDT 表项尺寸一致。任务门描述符任务门描述符不包含IST字段其格式与中断门/陷阱门不同。Linux 内核中对应的结构体同样位于arch/x86/include/asm/desc_defs.hstruct ldttss_desc64 { u16 limit0; u16 base0; unsigned base1 : 8, type : 5, dpl : 2, p : 1; unsigned limit1 : 4, zero0 : 3, g : 1, base2 : 8; u32 base3; u32 zero1; } __attribute__((packed));这个结构体同时被 LDT/TSS 描述符复用其中的limit、base、g粒度位、type、dpl、p字段共同描述了目标段/任务的基址与界限。由于长模式禁用了硬件任务切换该结构体在现代 x86_64 内核中主要用于维护 TSS 与 LDT 描述符本身TSS 在 Linux 中承担了 IST 栈指针的载体职责见下文。任务切换期间的异常处理器在任务切换过程中加载段选择子时可能发生异常访问 TSS 时也可能发生页故障。在这些情况下硬件任务切换机制会先完成从 TSS 加载新任务状态然后再触发相应的异常机制从而保证新任务状态已就绪、异常处理程序可以安全接管。但需要特别强调 x86_64 上的结论在长模式long mode下任务切换期间不可能发生此类异常因为硬件任务切换机制本身已被禁用。这正是 Linux 内核在 x86_64 上可以完全绕开硬件任务切换、改用软件实现上下文切换switch_to宏与__switch_to函数的根本原因。非屏蔽中断NMINMI 对应向量号 2。由于它不受IF标志约束、总是被处理器接受Linux 内核必须为其设计专门的入口与嵌套保护。仓库 Interrupts/linux-interrupts-6.md 对 NMI 处理给出了完整的源码级讲解要点如下NMI 可以通过两种方式产生外部硬件拉高 CPU 的 NMI 引脚处理器在系统总线或 APIC 串行总线上收到投递模式为NMI的消息内核在trap_init中通过set_intr_gate_ist(X86_TRAP_NMI, nmi, NMI_STACK)把向量 2 关联到nmi入口并指定NMI_STACKIST 栈NMI 入口没有使用通用的idtentry宏而是在arch/x86/entry/entry_64.S中拥有独立的nmi汇编入口。其核心难题是嵌套 NMI当 NMI 处理期间发生页故障或断点异常、执行iret返回时会重新使能 NMI新的 NMI 可能覆盖正在执行的 NMI 的栈帧为此内核在栈上维护了一个NMI 正在执行的临时变量进入时压入 1退出nmi_restore时清零同时复制一份原始栈帧用于在嵌套发生时让第一个 NMI 感知并重做处理器在进入nmi入口时会自动压入SS/RSP/RFLAGS/CS/RIP五元组汇编入口随后用paranoid_entry保存通用寄存器、读取MSR_GS_BASE判断来源模式并视需要执行swapgs还会把cr2暂存到r12因为 NMI 处理中可能发生页故障、破坏cr2的值真正的处理逻辑在do_nmiarch/x86/kernel/nmi.c经由nmi_nesting_preprocess/nmi_nesting_postprocess处理调试栈切换nmi_enter/nmi_exit维护 lockdep 递归计数与 RCU 状态最后通过nmi_handle分发本地 NMI、按get_nmi_reason()判断外部 NMI 的来源SERR/IOCHK 等。中断描述符表的加载与早期初始化IDT 的基址存放在IDTR寄存器中由两条指令管理LIDT把操作数含基址与界限的描述符加载进IDTRSIDT把IDTR的内容读出并存入操作数。IDTR在 x86 上共 48 位高 32 位是 IDT 基址Base Address低 16 位是 IDT 界限Limit即表字节数减一。仓库 Initialization/linux-initialization-2.md 中的示意图 IDT 描述符 展示了这个 48 位描述符的布局。内核中加载 IDT 的典型流程是在内核早期进入长模式后、x86_64_start_kernel中调用idt_setup_early_handler()遍历NUM_EXCEPTION_VECTORS值为 32个向量逐个调用set_intr_gate(i, early_idt_handler_array[i])初始化门描述符set_intr_gate把向量号、处理程序地址封装进struct idt_data再经idt_setup_from_table写入idt_table数组最后调用load_idt(idt_descr)执行lidt指令让处理器开始使用这张表。idt_table是gate_desc的数组extern gate_desc idt_table[]其中gate_desc在x86_64下即前述 16 字节gate_struct64。早期阶段只装 32 个异常向量就足够了——此时硬件中断仍被禁用不会到来但页故障等异常随时可能发生例如内核刚清空早期页表的 identity mapping 后访问boot_params会触发缺页do_early_exception会调用early_make_pgtable按需补页。Interrupt Stack TableIST原文档中 IST 一节标注为TODO但仓库其他章节Interrupts/linux-interrupts-1.md、Initialization/linux-initialization-2.md对该机制有完整描述此处整合呈现IST 是 x86_64 引入的、替代传统栈切换机制的设施。传统 x86 在响应中断时依据CPL变化自动切换栈帧IST 则是在启用后无条件切换到指定栈且可以按中断逐个启用——并非所有中断都需要 IST未启用 IST 的中断仍走传统切换路径每个 CPU 最多提供 7 个 IST 指针存放在该 CPU 的 TSSTask State Segment中。Linux 内核为其定义了若干具名 IST 索引#define DOUBLEFAULT_STACK 1 #define NMI_STACK 2 #define DEBUG_STACK 3 #define MCE_STACK 4即双重故障、NMI、调试、机器检查等即使当前栈已损坏也必须能运行的关键处理程序各自独占一个专用栈启用方式把 IDT 门描述符中的IST字段位 3234设为非 0 索引。例如set_intr_gate_ist(X86_TRAP_DB, debug, DEBUG_STACK)为调试异常指定DEBUG_STACKset_intr_gate_ist(X86_TRAP_NMI, nmi, NMI_STACK)为 NMI 指定NMI_STACKINTG(X86_TRAP_DF, double_fault)则让双重故障使用DOUBLEFAULT_STACK执行流程当IST字段非 0 时处理器把对应 IST 指针读入rsp随后把旧ss、rsp、RFLAGS、cs、rip压入新栈形成 40 字节的中断帧若向量带错误码则压入错误码否则压入哑错误码以保证栈布局一致接着从门描述符加载段选择子到cs并校验目标代码段的L位确为 64 位代码段把偏移量加载到rip从而进入处理程序。64 位模式下中断帧的每个单元固定为 8 字节--------------- | SS | 40 | RSP | 32 | RFLAGS | 24 | CS | 16 | RIP | 8 | Error code | 0 ---------------与 per-CPU 栈的关系除 IST 专用栈外Linux 每个 CPU 还有自己的中断栈irq_stack默认 16KBIRQ_STACK_SIZE PAGE_SIZE IRQ_STACK_ORDER与异常栈exception_stacksDOUBLEFAULT_STACK等即属于其中。内核线程栈大小THREAD_SIZE同样由PAGE_SHIFT与THREAD_SIZE_ORDER推导默认 16KB启用CONFIG_KASAN时为 32KB。IST 的意义在于像双重故障这类异常发生时当前内核栈可能已损坏到无法安全压栈而 IST 提供了无论如何都切到一块干净专用栈的兜底能力。中断/异常处理的完整流程小结把上述内容串成一条完整的硬件处理链路中断或异常发生处理器取向量号乘以 16每条 IDT 表项 16 字节得到表内偏移读出对应的门描述符校验门类型中断门/陷阱门与目标代码段的 64 位属性依据IST字段选择栈非 0 则读 TSS 中对应 IST 指针为 0 则沿用当前栈压入中断帧SS/RSP/RFLAGS/CS/RIP必要时加错误码从门描述符加载cs与rip跳转到处理程序入口如early_idt_handler_array生成的桩代码或正式内核中的idtentry宏展开体处理程序完成工作后执行iretq无条件弹出被中断上下文的ss:rsp恢复原栈并恢复RFLAGS继续执行被中断的代码iretq不依赖CPL是否变化。总结与延伸阅读本文以 X86/linux-x86-2.md 为主线完整覆盖了中断/异常的三大来源与同步性区分、Fault/Trap/Abort 三类异常、可屏蔽与非可屏蔽中断、0255 向量空间与前 32 个保留向量、两类错误码格式Selector 格式与 Page-Fault 格式、三种门描述符及其 128 位布局、Linux 内核中的gate_struct64与ldttss_desc64结构体、长模式下任务切换异常的不可能性并结合仓库其他章节补全了 NMI 嵌套处理与 IST 栈机制这两个原文档标注为 TODO 的主题。想进一步深入建议按以下顺序阅读仓库内相关章节Interrupts/linux-interrupts-1.md中断理论、gate_desc/idt_table、IST 与 per-CPU 栈的完整背景Interrupts/linux-interrupts-2.md早期setup_idt、set_intr_gate系列函数与pack_gate/write_idt_entry的实现细节Interrupts/linux-interrupts-6.mdNMI 处理程序的汇编入口、嵌套 NMI 保护与do_nmi分发逻辑Initialization/linux-initialization-2.md031 号向量的完整表格、early_idt_handler_array生成机制、do_early_exception与异常表exception table回退路径以及文中引用的两张 IDT 示意图X86/linux-x86-1.md同一 x86 基础章节中的分页Paging理论理解页故障处理的前提。说明本文涉及的内核源码文件如arch/x86/include/asm/traps.h、arch/x86/include/asm/desc_defs.h、arch/x86/kernel/traps.c、arch/x86/entry/entry_64.S等属于 Linux 内核主源码树未收录于本仓库仓库 X86/README.md 注明本章节对应 Linux 内核 v7.2.0并给出了获取内核源码的git clone方式如需核对上述定义可参考对应版本的内核源码。赞分享文档教程操作系统【免费下载链接】linux-insidesA book-in-progress about the Linux kernel and its insides.项目地址https://gitcode.com/gh_mirrors/li/linux-insides点击查看免费下载相关推荐Linux 内核揭秘中断描述符表IDT——门描述符、错误代码与 x86_64 中断处理机制全解Linux 内核揭秘中断描述符表IDT——门描述符、错误代码与 x86_64 中断处理机制全解 中断描述符表Interrupt Descriptor TLinux 内核揭秘中断描述符表IDT与内核内部系统数据结构深度解析Linux 内核揭秘中断描述符表IDT与内核内部系统数据结构深度解析 导读本文是《Linux 内核揭秘》linux insides zh中内核数据Linux 内核初始化linux-insides 第 2 部分早期异常处理与中断描述符表IDT的构建Linux 内核初始化linux insides 第 2 部分早期异常处理与中断描述符表IDT的构建 本文是 linux insides https:文档教程操作系统上一篇QKeyMapper按键映射教程键鼠手柄跨设备互换指南下一篇ETS2LA欧洲卡车模拟2自动驾驶辅助完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表