Linux 内核揭秘:中断描述符表(IDT)与内核内部系统数据结构深度解析
发布时间:2026/9/28 3:43:40来源:尧图网络
【免费下载链接】linux-insides-zhLinux 内核揭秘项目地址https://gitcode.com/hust-open-atom-club/linux-insides-zh点击查看免费下载导读本文是《Linux 内核揭秘》linux-insides-zh中内核数据结构KernelStructures章节的完整技术指南聚焦 Linux 内核在 x86/x86_64 架构下的内部系统数据结构尤其是中断描述符表Interrupt Descriptor TableIDT与全局描述符表Global Descriptor TableGDT。读完本文你将掌握中断与异常的来源分类、中断向量编号体系、处理器错误代码Error Code的两种格式、三种门描述符的位布局以及 Linux 内核中与之对应的 C 结构体表示gate_struct64、ldttss_desc64并能沿着仓库中的相关章节继续深入内核中断子系统的实现细节。什么是内核内部系统数据结构在《Linux 内核揭秘》这本书中大多数章节沿着内核启动、初始化和子系统实现的时间线展开叙述。但 KernelStructures/README.md 所对应的是一个特殊的专题章节它不再以某个阶段的初始化流程为主线而是聚焦于 Linux 内核中面向体系结构architecture-specific的内部系统数据结构例如中断描述符表IDT保存中断与异常处理程序入口指针的表格CPU 依据中断向量号在表中查找对应的门Gate全局描述符表GDT保存段描述符的系统表用于描述内核代码段、数据段等内存段的基址、界限与访问权限。这类数据结构的位布局、语义与行为绝大部分由处理器厂商的官方手册定义。本仓库的这一章节也明确指出其内容主要来源于 Intel 与 AMD 的软件开发者手册Software Developer Manuals——换句话说读懂这些结构就拿到了理解内核在 x86 平台上如何处理中断的钥匙。在 SUMMARY.md 的目录结构中该章节位于理论Theory与杂项Misc之间其下目前包含一篇正文KernelStructures/linux-kernelstructure-1.md —— 中断描述符表IDT以下内容即以这篇正文为主体展开。中断与异常的来源同步与异步内核要处理的事件来源共有三类它们决定了 CPU 进入处理程序的时机与方式来源同步/异步说明异常Exception同步由 CPU 在执行指令时检测到错误产生与当前指令流同步软中断Software Interrupt同步由软件显式触发通常用于系统调用等场景外部中断External Interrupt异步由外部硬件设备通过中断线或 APIC 异步触发其中异常根据报告时机又可细分为三种类型故障Fault在导致异常的指令执行之前被准确报告%rip保存的指针指向那条引发故障的指令陷阱Trap在导致异常的指令执行之后被准确报告%rip保存的指针同样指向引发陷阱的指令终止Abort不明确的异常由于无法精确定位引发异常的指令系统通常不允许程序可靠地再次启动。理解三者的区别很重要故障如缺页#PF在修正条件后允许被中断的程序继续执行陷阱如断点#BP也允许继续而终止如双重错误#DF通常意味着不可恢复。可屏蔽与不可屏蔽中断处理器通过标志寄存器中的RFLAGS.IF位控制普通中断的递送可屏蔽中断Maskable Interrupt仅当RFLAGS.IF 1时可屏蔽中断才会触发中断处理程序若该位被清零它们将持续处于等待处理状态。不可屏蔽中断Non-Maskable InterruptNMI不受RFLAGS.IF位的影响。无论怎样一个 NMI 的发生都会进一步屏蔽之后的其他 NMI直到执行IRET中断返回指令为止。在 x86_64 上控制IF位的典型指令是sti置位与cli清除Linux 内核通过native_irq_enable/native_irq_disable内联函数封装它们。NMI 与普通外部中断在 Linux 内核中的处理路径差异极大——NMI 处理程序拥有独立入口nmi并搭配专用的中断堆栈表IST条目这一点在本文中断堆栈表IST一节展开。中断向量0 到 255 的编号体系处理器为每一种具体的异常和中断来源分配固定的向量标识号vector number简称向量。中断处理程序使用该向量定位异常或中断从而分发到对应的系统软件服务处理程序。可用的中断向量最多256 个其中前 32 个被保留用于预定义的异常和中断条件。Linux 内核在设置中断门时会校验向量号的合法性BUG_ON((unsigned)n 0xFF);这段检查可在set_intr_gate等中断设置路径中找到参见 Initialization/linux-initialization-2.md 对arch/x86/include/asm/desc.h的剖析。前 32 个保留向量在arch/x86/include/asm/traps.h中以枚举常量定义内核源码对应的定义如下/* 中断/异常 */ enum { X86_TRAP_DE 0, /* 0, 除零错误 */ X86_TRAP_DB, /* 1, 调试 */ X86_TRAP_NMI, /* 2, 不可屏蔽中断 */ X86_TRAP_BP, /* 3, 断点 */ X86_TRAP_OF, /* 4, 溢出 */ X86_TRAP_BR, /* 5, 超出范围 */ X86_TRAP_UD, /* 6, 操作码无效 */ X86_TRAP_NM, /* 7, 设备不可用 */ X86_TRAP_DF, /* 8, 双精度浮点错误 */ X86_TRAP_OLD_MF, /* 9, 协处理器段溢出 */ X86_TRAP_TS, /* 10, 无效的 TSS */ X86_TRAP_NP, /* 11, 段不存在 */ X86_TRAP_SS, /* 12, 堆栈段故障 */ X86_TRAP_GP, /* 13, 一般保护故障 */ X86_TRAP_PF, /* 14, 页错误 */ X86_TRAP_SPURIOUS, /* 15, 伪中断 */ X86_TRAP_MF, /* 16, x87 浮点异常 */ X86_TRAP_AC, /* 17, 对齐检查 */ X86_TRAP_MC, /* 18, 机器检测 */ X86_TRAP_XF, /* 19, SIMD 单指令多数据结构浮点异常 */ X86_TRAP_IRET 32, /* 32, IRET 中断返回异常 */ };仓库的 Initialization/linux-initialization-2.md 还给出了一张更完整的0-31号异常速查表包含每个向量的助记符如#DE、#DF、#GP、#PF、类型Fault/Trap/Abort、是否携带错误代码以及典型产生来源是阅读本节的极佳配套资料。向量32到255则设计为用户定义中断通常分配给外部 I/O 设备使用。错误代码Error Code格式处理器异常处理程序使用错误代码报告某些异常的错误和状态信息。在把控制权交给异常处理程序期间异常处理装置会将错误代码推送到堆栈中。错误代码有两种格式多数异常错误报告格式选择子错误代码页错误格式。选择子错误代码格式31 16 15 3 2 1 0 ------------------------------------------------------------------------------- | | | T | I | E | | Reserved | Selector Index | - | D | X | | | | I | T | T | -------------------------------------------------------------------------------各字段含义如下EXT若该位为 1异常源在处理器外部若为 0异常源位于处理器内部IDT若该位为 1错误代码的选择子索引字段引用的是中断描述符表中的门描述符若为 0选择子索引字段引用的是全局描述符表GDT或本地描述符表LDT中的描述符具体由TI位指示TI若该位为 1选择子索引字段引用 LDT 中的描述符若为 0选择子索引字段引用 GDT 中的描述符Selector Index选择子索引字段指定索引是 GDT、LDT 还是 IDT由IDT与TI位共同指定。页错误代码格式31 4 3 2 1 0 ------------------------------------------------------------------------------- | | | R | U | R | - | | Reserved | I/D | S | - | - | P | | | | V | S | W | - | -------------------------------------------------------------------------------各字段含义如下I/D若该位为 1造成页错误的访问是取指instruction fetchRSV若该位为 1页错误是处理器从保留给分页表的区域中读取的结果U/S若该位为 0是管理员模式CPL 0、1 或 2的访问导致页错误若为 1是用户模式CPL 3的访问导致页错误R/W若该位为 0导致页错误的是内存读取若为 1导致页错误的是内存写入P若该位为 0页错误是由不存在的页面引起的若为 1页错误是由于违反页保护引起的。在 Linux 内核的早期启动阶段#PF向量 14是最重要的异常之一内核初始化早期通过early_idt_handler_common检查栈上的向量号是否为14若是则读取cr2寄存器中的故障地址并调用early_make_pgtable动态建立页表。完整的处理逻辑分析见 Initialization/linux-initialization-2.md 的缺页中断处理程序一节。中断控制传输三种门描述符IDT 可以包含三种门描述符中的任何一种任务门Task Gate包含用于异常与/或中断处理程序任务的 TSS 段选择子中断门Interrupt Gate包含处理器用于将程序执行转移到中断处理程序的段选择子和偏移量陷阱门Trap Gate包含处理器用于将程序执行转移到异常处理程序的段选择子和偏移量。中断门与陷阱门的唯一区别在于 CPU 对IF标志的处理方式经由中断门进入处理程序时CPU 会清除IF标志位从而在当前中断处理程序执行期间屏蔽其他中断而经由陷阱门进入时不会自动屏蔽。中断和陷阱门都包含一个指向处理程序的远指针far pointer。门描述符的通用位布局x86_6416 字节如下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目标代码段的段选择子Offset处理程序入口点的偏移量在 64 位模式下被拆成三段Offset 15..0、Offset 31..16、Offset 63..32DPL描述符权限级别Descriptor Privilege LevelP当前段存在标志PresentIST中断堆栈表Interrupt Stack Table索引TYPE描述符类型可为 LDT 段描述符、TSS 描述符、调用门描述符、中断门描述符、陷阱门描述符或任务门描述符之一。IDT 的表项在 x86 上为 8 字节门在 x86_64 上为 16 字节门。与 GDT 强制要求第一个元素为 NULL 描述符不同IDT 的第一个元素可以直接是一个门例如早期引导阶段就曾用 NULL 门加载 IDT。IDT 可以位于线性地址空间的任意位置只需在 x86 上按 8 字节对齐、在 x86_64 上按 16 字节对齐即可。其基址保存在专门的IDTR 寄存器中由两条指令协同维护LIDT加载 IDT 基址到 IDTRSIDT将 IDTR 内容读出存储到指定操作数。IDTR 在 x86 上是 48 位由 16 位 Limit 与 32 位 Base 组成x86_64 下 Base 扩展为 64 位。引导阶段的setup_idt就通过lidtl指令加载了一个null_idt来临时禁用中断详见 Interrupts/linux-interrupts-1.md。Linux 内核中的门描述符结构体IDT在 Linux 内核中由gate_desc数组描述extern gate_desc idt_table[];在 x86_64 架构下gate_desc即struct gate_struct64定义于arch/x86/include/asm/desc_defs.hstruct 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));这个结构体与上文的门位布局一一对应offset_low/offset_middle/offset_high分别承载处理程序入口偏移量的低 16 位、中 16 位与高 32 位segment承载段选择子在内核中通常为__KERNEL_CS位域ist、type、dpl、p对应 IST 索引、门类型、描述符权限级与存在标志。内核使用pack_gate与PTR_LOW/PTR_MIDDLE/PTR_HIGH宏将处理函数地址拆分填入三个偏移字段再通过_set_gate/set_intr_gate写入idt_table这段写入逻辑在 Initialization/linux-initialization-2.md 中有逐步讲解。任务门描述符不包含IST字段其格式与中断/陷阱门不同内核中对应的结构体为struct ldttss_desc64struct 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));值得注意的是在 x86_64 长模式下只有中断门和陷阱门可以被引用任务门不再用于实际的任务切换。gate_struct64与ldttss_desc64这两个结构体正是内核内部系统数据结构这一章节主题最直接的体现——它们是硬件手册中的位布局在 Linux 内核 C 代码中的精确映射。任务切换期间的异常任务切换在加载段选择子期间可能会发生异常页错误也可能在访问 TSS 时出现。在这些情况下由硬件任务切换机构先从 TSS 加载新的任务状态然后再触发适当的异常处理。但需要注意在长模式下由于硬件任务切换机构被禁用因而在任务切换期间不会发生这类异常。这也解释了为什么 x86_64 上任务门不再扮演实际切换角色的原因——长模式下的任务切换与栈切换由软件内核配合 TSS 中的 IST 指针完成。中断堆栈表IST与 NMI从未完待续到仓库中的实现KernelStructures/linux-kernelstructure-1.md 末尾将不可屏蔽中断Nonmaskable interrupt、API、中断堆栈表Interrupt Stack Table三个小节标注为未完待续。要补全这些主题仓库的 Interrupts 章节提供了完整的实现级讲解这里给出要点式衔接**中断堆栈表IST**是 x86_64 新增的机制用于取代传统的栈切换模式在使能后可无条件地切换栈并且可以被任何与确定中断关联的 IDT 条目启用。TSS 中提供7 个 IST 指针内核为双重错误、NMI、调试异常、机器检测等保留专用栈例如DOUBLEFAULT_STACK 1、NMI_STACK 2、DEBUG_STACK 3、MCE_STACK 4。使用 IST 的门描述符由set_intr_gate_ist初始化如set_intr_gate_ist(X86_TRAP_NMI, nmi, NMI_STACK)见 Interrupts/linux-interrupts-1.md 与 Interrupts/linux-interrupts-6.md。NMI 处理是内核中最小心谨慎的路径之一由于嵌套 NMI 会破坏前一个 NMI 的栈帧内核在栈上保存复制栈帧并维护NMI 执行中标志变量配合paranoid_entry检查MSR_GS_BASE、必要时执行swapgs并在调用do_nmi前后保护cr2寄存器。完整流程见 Interrupts/linux-interrupts-6.md。每个 CPU 还拥有 per-cpu 的中断栈irq_stack_union默认 16KB其入口在引导早期通过写MSR_GS_BASE建立gs基址这部分细节同样收录于 Interrupts/linux-interrupts-1.md。在仓库中继续深入本专题作为内核系统数据结构的入口与仓库中多个章节形成阅读闭环想了解引导阶段如何临时加载 IDT 与 GDT阅读 Booting/linux-bootstrap-2.md想了解早期异常处理程序early_idt_handler_array、set_intr_gate、缺页处理阅读 Initialization/linux-initialization-2.md想系统学习中断与异常处理的全貌PIC/APIC、中断优先级、IST、softirq 等进入 Interrupts/README.md 从 Interrupts/linux-interrupts-1.md 开始。掌握 IDT 与 GDT 这两张系统表格的位级语义是理解内核中断子系统、异常处理与栈切换机制的必备前提——这正是本仓库设置内核数据结构专题的初衷。赞分享【免费下载链接】linux-insides-zhLinux 内核揭秘项目地址https://gitcode.com/hust-open-atom-club/linux-insides-zh点击查看免费下载相关推荐Linux 内核揭秘中断描述符表IDT——门描述符、错误代码与 x86_64 中断处理机制全解Linux 内核揭秘中断描述符表IDT——门描述符、错误代码与 x86_64 中断处理机制全解 中断描述符表Interrupt Descriptor TLinux内核揭秘文件描述符管理的终极指南Linux内核揭秘文件描述符管理的终极指南 在Linux系统中文件描述符是连接用户程序与内核资源的重要桥梁。无论是打开文件、创建管道还是建立网络连接都离不文档教程操作系统10 篇深度剖析Linux 内核揭秘linux-insides-zh中断、异常、IDT 与 Softirq 处理机制全景图解10 篇深度剖析Linux 内核揭秘linux insides zh中断、异常、IDT 与 Softirq 处理机制全景图解 文章导语 按一次键盘按键系上一篇抖音无水印批量下载别求人douyin-downloader 一个命令全搞定下一篇F´安全与可靠性实践完整指南之飞行软件代码审查、SBOM软件物料清单与可信开发清单创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网