news 2026/10/4 16:13:24

Linux 内核揭秘:中断与中断处理(一)——APIC、向量号、IDT 与中断栈的硬件基础

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux 内核揭秘:中断与中断处理(一)——APIC、向量号、IDT 与中断栈的硬件基础
  • 文档
  • 教程
  • 操作系统

【免费下载链接】linux-insides-zh

Linux 内核揭秘

项目地址:https://gitcode.com/gh_mirrors/li/linux-insides-zh
点击查看免费下载

本文是《Linux 内核揭秘》中断与中断处理系列的第一部分。在前面的初始化章节中,我们一路从内核解压后的首个指令跟踪到第一个init进程启动,见识了大量与子系统相关的初始化步骤,但始终没有深入这些子系统内部。本篇将正式开启"中断"这个内核中至关重要的子系统,先厘清中断(Interrupt)、异常(Exception)与中断处理程序(Interrupt Handler)的概念,再逐位剖析x86_64架构下中断描述符表(IDT)的硬件结构、IST中断栈表机制,以及内核如何借助 per-CPU 中断栈安全地完成上下文切换。读完本文,你将掌握中断向量号的分配规则、IDT 门描述符的二进制布局、IRQ_STACK_SIZE与THREAD_SIZE的计算方法,并能在后续章节中看懂 Linux 内核处理真实硬件中断的完整代码路径。

从初始化章节进入中断世界

在初始化系列中,我们从内核初始化的第一步出发,一路见证了大量初始化步骤,最终以第一个init程序的启动收尾。但那十篇文章的侧重点是"初始化本身",对于各子系统"如何工作、如何实现",我们几乎没有深入。本篇作为中断主题的开篇,会从两个最基本的问题入手:

  • 什么是中断(interrupts)?
  • 什么是中断处理程序(interrupt handlers)?

然后逐步深入中断的细节,以及 Linux 内核处理这些中断的方式。

事实上,"中断"在前面的章节中已经多次出现。比如在内核初始化第二部分中,我们看到了一个向 IDT 填入前 32 个异常处理程序的循环:

for (i = 0; i < NUM_EXCEPTION_VECTORS; i++) set_intr_gate(i, early_idt_handler_array[i]);

这段代码位于arch/x86/kernel/head64.c,它正是内核解压后、start_kernel运行前搭建"早期异常处理"骨架的地方。本篇的理论内容,恰好就是理解这段代码为什么这样写的基础。

什么是中断

中断就是当软件或者硬件需要使用 CPU 时引发的事件(event)。例如:当我们在键盘上按下一个键时,操作系统和电脑接下来应该做什么?一个简单的假设是:每一个物理硬件都有一根连接 CPU 的中断线,设备可以通过它对 CPU 发起中断信号。

但中断信号并不会直接发送给 CPU。在老式机器上,中断信号发送给 PIC(Programmable Interrupt Controller,可编程中断控制器)——一个顺序处理各种设备中断请求的芯片。而在新式机器上,这项工作由APIC(Advanced Programmable Interrupt Controller,高级可编程中断控制器)完成。一个 APIC 由两个独立设备组成:

  • Local APIC
  • I/O APIC

Local APIC存在于每个 CPU 核心中,负责处理特定于 CPU 的中断配置,常被用于管理来自 APIC 时钟(APIC-timer)、热敏元件以及其他与 I/O 设备相连设备的中断。

I/O APIC则提供多核处理器的中断管理,它被用来在所有的 CPU 核心中分发外部中断。Local APIC 与 I/O APIC 的更多细节将在本系列后续部分展开。

中断可以在任何时间发生。当一个中断发生时,操作系统必须立刻处理它。所谓"处理一个中断",意味着内核必须确保以下步骤顺序:

  1. 内核必须暂停执行当前进程(取代当前任务);
  2. 内核必须搜索中断处理程序并转交控制权(执行中断处理程序);
  3. 中断处理程序结束之后,被中断的进程能够恢复执行。

当然,实际的中断处理过程中还交织着大量错综复杂的细节,但上面三条就是这个过程的基本骨架。

中断向量号与中断描述符表(IDT)

每个中断处理程序的地址都保存在一个特殊的位置,这个位置被称为中断描述符表(Interrupt Descriptor Table),简称IDT。

处理器使用一个唯一的数字来识别中断和异常的类型,这个数字被称为中断向量号(vector number)。向量号同时也是IDT的索引,其取值范围是有限的:从0到255。在 Linux 内核源码中可以找到对向量号范围的检查代码:

BUG_ON((unsigned)n > 0xFF);

这段检查出现在中断门设置的相关位置,例如set_intr_gate、set_system_intr_gate等函数中(见arch/x86/include/asm/desc.h)。

向量号的空间分配规则如下:

  • 0到31共 32 个向量号被处理器保留,用作处理架构定义的异常和中断。完整的异常向量表及其类型、错误码、来源的说明,可以在内核初始化第二部分中找到——那里列出了#DE(除零)、#BP(断点)、#DF(双重错误)、#PF(缺页)等 0~31 号异常的定义。
  • 32到255的向量号被设计为用户定义中断,不被系统保留。这些中断通常分配给外部 I/O 设备,使设备可以向处理器发送中断。

中断的类型

笼统地讲,可以把中断分为两个主要类型:

  • 外部或硬件引起的中断;
  • 软件引起的中断。

第一种类型(外部中断)由Local APIC或者与Local APIC连接的处理器针脚接收。第二种类型(软件引起的中断)由处理器自身的特殊情况引起(有时使用特殊的架构指令)。一个常见的特殊情况例子就是除零,另一个例子是使用系统调用(syscall)退出程序。

中断可以在任何时间、因为超出代码和 CPU 控制的原因而发生;与之相对,异常与程序执行是同步(synchronous)的,并且可以被分为 3 类:

类型说明
故障(Faults)在执行一条"不完善的"指令(可以在之后被修正)之前被报告的异常。如果发生,它允许被中断的程序继续执行。
陷入(Traps)在执行了陷入指令后立刻被报告的异常。与故障一样,陷入也允许被中断的程序继续执行。
终止(Aborts)从不报告引起异常的精确指令的异常,并且不允许被中断的程序继续执行。

另外,我们在内核启动过程的第三部分中已经知道,中断可以分为可屏蔽的(maskable)和不可屏蔽的(non-maskable)。

可屏蔽中断可以被阻塞,使用的x86_64指令是sti和cli。在 Linux 内核源码中可以找到它们:

static inline void native_irq_disable(void) { asm volatile("cli": : :"memory"); }

以及

static inline void native_irq_enable(void) { asm volatile("sti": : :"memory"); }

这两个指令修改中断寄存器(RFLAGS)中的IF标志位:sti设置IF,cli清除IF。不可屏蔽中断则总是被报告——通常,任何硬件上的失败都被映射为不可屏蔽中断。

中断优先级

如果多个异常或中断同时发生,处理器会按照事先设定好的中断优先级处理它们。下表列出了从最低到最高的优先级(数字越小优先级越高):

+----------------------------------------------------------------+ | | | | Priority | Description | | | | +--------------+-------------------------------------------------+ | | Hardware Reset and Machine Checks | | 1 | - RESET | | | - Machine Check | +--------------+-------------------------------------------------+ | | Trap on Task Switch | | 2 | - T flag in TSS is set | | | | +--------------+-------------------------------------------------+ | | External Hardware Interventions | | | - FLUSH | | 3 | - STOPCLK | | | - SMI | | | - INIT | +--------------+-------------------------------------------------+ | | Traps on the Previous Instruction | | 4 | - Breakpoints | | | - Debug Trap Exceptions | +--------------+-------------------------------------------------+ | 5 | Nonmaskable Interrupts | +--------------+-------------------------------------------------+ | 6 | Maskable Hardware Interrupts | +--------------+-------------------------------------------------+ | 7 | Code Breakpoint Fault | +--------------+-------------------------------------------------+ | 8 | Faults from Fetching Next Instruction | | | Code-Segment Limit Violation | | | Code Page Fault | +--------------+-------------------------------------------------+ | | Faults from Decoding the Next Instruction | | | Instruction length > 15 bytes | | 9 | Invalid Opcode | | | Coprocessor Not Available | | | | +--------------+-------------------------------------------------+ | 10 | Faults on Executing an Instruction | | | Overflow | | | Bound error | | | Invalid TSS | | | Segment Not Present | | | Stack fault | | | General Protection | | | Data Page Fault | | | Alignment Check | | | x87 FPU Floating-point exception | | | SIMD floating-point exception | | | Virtualization exception | +--------------+-------------------------------------------------+

中断描述符表(IDT)的内部结构

了解了各类中断与异常之后,就可以进入更实用的部分了。IDT 保存了中断和异常处理程序的入口指针,它是一个类似于**全局描述符表(Global Descriptor Table,GDT)**的结构——GDT 我们在内核启动过程的第二部分已经介绍过,那里展示了如何通过lgdt指令把 GDT 的基址和大小装入 48 位的GDTR寄存器。

IDT 与 GDT 有一个关键差异:IDT 的表项被称为门(gates),而不是描述符(descriptors)。一个门可以是下面三种之一:

  • 中断门(Interrupt gates)
  • 任务门(Task gates)
  • 陷阱门(Trap gates)

在x86架构中,只有 long mode(长模式)下,中断门和陷阱门才可以在x86_64中被引用。与 GDT 类似,中断描述符表在x86上是 8 字节数组门,而在x86_64上是 16 字节数组门。

回忆内核启动过程的第二部分:全局描述符表必须包含NULL描述符作为它的第一个元素。与 GDT 不同的是,IDT 的第一个元素可以是一个门,这并非强制要求。例如,早期章节在过渡到保护模式时,只是用NULL门加载过 IDT:

/* * Set up the IDT */ static void setup_idt(void) { static const struct gdt_ptr null_idt = {0, 0}; asm volatile("lidtl %0" : : "m" (null_idt)); }

这段代码位于arch/x86/boot/pm.c。

IDT 可以被加载到线性地址空间和基址的任何地方,只要在x86上以 8 字节对齐、在x86_64上以 16 字节对齐。IDT 的基址存储在一个特殊的寄存器——IDTR中。在x86上有两条指令协同修改IDTR寄存器:

  • LIDT——加载 IDT 的基址到 IDTR 的指定操作数;
  • SIDT——在指定操作数中读取并存储 IDTR 的内容。

在x86上,IDTR 寄存器是 48 位的,包含下面的信息:

+-----------------------------------+----------------------+ | | | | Base address of the IDT | Limit of the IDT | | | | +-----------------------------------+----------------------+ 47 16 15 0

注意上面setup_idt中的null_idt是gdt_ptr类型,其定义如下:

struct gdt_ptr { u16 len; u32 ptr; } __attribute__((packed));

这与 IDTR 结构的示意完全吻合:由 2 字节(len,即 Limit)和 4 字节(ptr,即 Base address)两个域组成,共 48 位。这里也解释了为什么引导阶段用gdt_ptr来承载一个"空的 IDT"——两者在硬件寄存器层面的布局完全一致。

IDT 门条目的二进制布局

在x86_64中,IDT 的每个入口是一个 16 字节的门,结构如下:

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 | | | | +-------------------------------------------------------------------------------+

处理器把异常和中断向量号作为索引,去查找对应的中断描述符表条目,其处理方式类似于它看到call指令时处理一个程序调用。IDT 条目的各个域含义如下:

  • 0-15bits —— 段选择器偏移,处理器用它作为中断处理程序的入口指针基址;
  • 16-31bits —— 段选择器基址,包含中断处理程序入口指针(即目标代码段选择子,通常是内核代码段__KERNEL_CS);
  • IST—— 在x86_64上的新机制,下面会专门介绍;
  • DPL—— 描述符特权级;
  • P—— 段存在标志;
  • 48-63bits —— 中断处理程序基址的第二部分(Offset 31..16 之外的高位区);
  • 64-95bits —— 中断处理程序基址的第三部分(Offset 63..32);
  • 96-127bits —— CPU 保留位。

其中Type域描述了 IDT 条目的类型,对应三种不同的中断处理程序:中断门(Interrupt gate)、陷阱门(Trap gate)、任务门(Task gate)。

在 Linux 内核源码中,中断描述符表用gate_desc数组描述:

extern gate_desc idt_table[];

gate_desc的定义如下(x86_64分支):

#ifdef CONFIG_X86_64 ... ... ... typedef struct gate_struct64 gate_desc; ... ... ... #endif

gate_struct64的定义为:

struct 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));

这个结构体把上面示意图中的 16 字节门拆成了offset_low / segment / ist+zero0+type+dpl+p / offset_middle / offset_high / zero1,用位域精确对应硬件的位布局。__attribute__((packed))确保结构体不引入任何填充字节。

中断门、陷阱门与 IF 标志

中断门与陷阱门都包含一个指向中断处理程序的远指针(far pointer),二者唯一的不同在于 CPU 处理IF标志的方式:如果经由中断门进入处理程序,CPU 会清除IF标志位,这样在当前中断处理程序执行期间,CPU 不会响应其他中断;只有当前处理程序返回、iret指令执行时,IF才会被重新设置。如内核初始化第二部分所述,这正是set_intr_gate设置GATE_INTERRUPT类型的用意。

IST:x86_64 的中断栈表机制

IST,即Interrupt Stack Table(中断栈表),是x86_64中的新机制,用来代替传统的栈切换机制。旧式x86架构提供了一种在响应中断时自动切换栈帧的机制;IST是这种栈切换模式的修改版——它使能之后可以无条件切换栈,并且可以被任何与确定中断关联的 IDT 条目中的中断使能。

因此IST并非所有中断都必须使用,一些中断可以继续使用传统的栈切换模式。

IST机制在任务状态段(Task State Segment,TSS)中提供了 7 个IST指针。TSS 是一个包含进程信息的特殊结构,用来在执行中断或处理 Linux 内核异常时做栈切换;每一个指针都被 IDT 中的中断门引用。当发生不可屏蔽中断(NMI)、双重错误(Double Fault)等特殊事件时,IST提供切换到新栈的能力。

x86_64 上可到达 7 个 IST per-CPU 入口,其中一些如下:

  • DOUBLEFAULT_STACK
  • NMI_STACK
  • DEBUG_STACK
  • MCE_STACK

对应内核源码中的定义:

#define DOUBLEFAULT_STACK 1 #define NMI_STACK 2 #define DEBUG_STACK 3 #define MCE_STACK 4

所有被IST切换到新栈的中断门描述符都由set_intr_gate_ist函数初始化。例如内核的默认 IDT 数据def_idts数组:

static const __initconst struct idt_data def_idts[] = { ... INTG(X86_TRAP_NMI, nmi), ... INTG(X86_TRAP_DF, double_fault),

其中&nmi与&double_fault的入口点在arch/x86/entry/entry_64.S中创建:

idtentry double_fault do_double_fault has_error_code=1 paranoid=2 read_cr2=1 ... ... ... SYM_CODE_START(nmi) ... ... ... SYM_CODE_END(nmi) SYM_CODE_END(nmi)

中断处理程序的声明则在arch/x86/include/asm/traps.h中给出:

asmlinkage void nmi(void); asmlinkage void double_fault(void);

内核栈、线程栈与 per-CPU 中断栈

在x86_64架构中,Linux 内核中每一个活动线程都有一个很大的栈,其大小由THREAD_SIZE定义:

#define PAGE_SHIFT 12 #define PAGE_SIZE (_AC(1,UL) << PAGE_SHIFT) ... ... ... #define THREAD_SIZE_ORDER (2 + KASAN_STACK_ORDER) #define THREAD_SIZE (PAGE_SIZE << THREAD_SIZE_ORDER)

PAGE_SIZE是4096字节,THREAD_SIZE_ORDER的值依赖于KASAN_STACK_ORDER。KASAN_STACK_ORDER又由CONFIG_KASAN内核配置参数决定:

#ifdef CONFIG_KASAN #define KASAN_STACK_ORDER 1 #else #define KASAN_STACK_ORDER 0 #endif

KASan是一个运行时内存调试器。所以:

  • 如果CONFIG_KASAN被禁用,THREAD_SIZE为16384字节(4096 << 2);
  • 如果该配置选项打开,THREAD_SIZE为32768字节(4096 << 3)。

这块栈空间保存着有用数据,只要线程处于活动状态或者僵尸状态。当线程运行在用户空间时,内核栈是空的,除非thread_info结构(详细内容见内核初始化第四部分)位于栈空间的底部。

活动或僵尸线程并不是栈中唯一的住户——与每一个 CPU 关联的特殊栈也存在于这块空间。当内核在该 CPU 上执行代码时,这些栈处于活动状态;当执行用户空间代码时,它们不包含任何有用信息。

每个 CPU 都有特殊的 per-CPU 栈。首先是给外部中断使用的中断栈(interrupt stack),大小定义如下:

#define IRQ_STACK_ORDER (2 + KASAN_STACK_ORDER) #define IRQ_STACK_SIZE (PAGE_SIZE << IRQ_STACK_ORDER)

即16384字节(未开 KASAN 时)。

Per-CPU 的中断栈在x86_64架构中使用irq_stack_union联合描述:

union irq_stack_union { char irq_stack[IRQ_STACK_SIZE]; struct { char gs_base[40]; unsigned long stack_canary; }; };

第一个域irq_stack是一个 16KB 的数组。这个联合还包含一个结构体,结构体有两个域:

  • gs_base—— 总是指向irq_stack_union底部的gs寄存器。在x86_64中,per-CPU(更多 per-CPU 变量的内容可阅读Concepts/linux-cpu-1.md)与 stack canary 共享gs寄存器。所有 per-CPU 标志初始值为零,gs指向 per-CPU 区域的开始。虽然段内存模式早已被废除,但我们仍可以通过特殊模块寄存器(Model Specific Registers,MSR)给fs和gs两个段寄存器设置基址,使它们继续被用作地址寄存器。在内核初始化第一部分中,我们就设置过gs寄存器:
movl $MSR_GS_BASE,%ecx movl initial_gs(%rip),%eax movl initial_gs+4(%rip),%edx wrmsr

initial_gs指向irq_stack_union:

GLOBAL(initial_gs) .quad INIT_PER_CPU_VAR(irq_stack_union)
  • stack_canary—— Stack canary(栈金丝雀),对中断栈来说是一个用来验证栈是否被修改的栈保护者(stack protector)。gs_base是 40 字节的数组,GCC 要求 stack canary 位于被修正过的偏移量上:在x86_64架构上gs的偏移必须是40,在x86架构上必须是20。

irq_stack_union是percpu区域的第一份数据,我们可以在System.map中看到它:

0000000000000000 D __per_cpu_start 0000000000000000 D irq_stack_union 0000000000004000 d exception_stacks 0000000000009000 D gdt_page ... ... ...

它在代码中的定义是:

DECLARE_PER_CPU_FIRST(union irq_stack_union, irq_stack_union) __visible;

irq_stack_ptr 的初始化

除了irq_stack_union,在arch/x86/include/asm/processor.h中还能看到下面的 per-CPU 变量:

DECLARE_PER_CPU(char *, irq_stack_ptr); DECLARE_PER_CPU(unsigned int, irq_count);

第一个irq_stack_ptr从名字可知是指向中断栈栈顶的指针;第二个irq_count用来检查 CPU 是否已经在中断栈中。

irq_stack_ptr的初始化发生在arch/x86/kernel/setup_percpu.c的setup_per_cpu_areas函数中(该函数正是 per-CPU 区域的初始化入口,相关背景可参考 Concepts/linux-cpu-1.md):

void __init setup_per_cpu_areas(void) { ... ... #ifdef CONFIG_X86_64 for_each_possible_cpu(cpu) { ... ... ... per_cpu(irq_stack_ptr, cpu) = per_cpu(irq_stack_union.irq_stack, cpu) + IRQ_STACK_SIZE - 64; ... ... ... #endif ... ... }

这里遍历所有可能的 CPU,并把irq_stack_ptr设置为"中断栈顶减去 64"。为什么是 64?这与load_percpu_segment(位于arch/x86/kernel/cpu/common.c)中的栈金丝雀布局有关:

void load_percpu_segment(int cpu) { ... ... ... __loadsegment_simple(gs, 0); wrmsrl(MSR_GS_BASE, cpu_kernelmode_gs_base(cpu)); ... load_stack_canary_segment(); }

我们知道gs寄存器指向中断栈的栈底:

movl $MSR_GS_BASE,%ecx movl initial_gs(%rip),%eax movl initial_gs+4(%rip),%edx wrmsr SYM_DATA(initial_gs, .quad INIT_PER_CPU_VAR(fixed_percpu_data))

这里的wrmsr指令把edx:eax指向的数据加载到由ecx指定的 MSR 寄存器中。此处 MSR 是MSR_GS_BASE,它保存gs寄存器所指向内存段的基址;edx:eax指向initial_gs的地址,即fixed_percpu_data的基址。

中断发生时的硬件行为

当一个中断或异常发生时,硬件会执行一系列固定动作:

  1. 新的ss选择器被强制置为NULL,且ss选择器的rpl域被设置为新的cpl;
  2. 旧的ss、rsp、寄存器标志(RFLAGS)、cs、rip被压入新栈;
  3. 在 64 位模式下,中断栈帧大小固定为 8 字节,因此可以得到下面的栈布局:
+---------------+ | | | SS | 40 | RSP | 32 | RFLAGS | 24 | CS | 16 | RIP | 8 | Error code | 0 | | +---------------+
  1. 如果在中断门中IST域不是0,就把IST读到rsp中;
  2. 如果该中断向量关联了错误码,再把错误码压入栈;如果没有错误码,就压入一个虚拟错误码——这一步是为了确保栈的一致性;
  3. 接下来从门描述符中把段选择器域加载到CS寄存器,并通过验证第21位(即 GDT 中L位)的值来确认目标代码是 64 位代码段;
  4. 最后从门描述符中把偏移域加载到rip,rip就是中断处理函数的入口指针;
  5. 中断处理函数开始执行;执行结束后,必须通过iret指令把控制权交还给被中断的进程。iret无条件地弹出栈指针(ss:rsp)来恢复被中断的进程,且不依赖于cpl的改变。

顺带一提,早期引导阶段的中断栈帧与此略有出入——由于 CPU 压栈在先,在早期通用处理程序early_idt_handler_common(arch/x86/kernel/head_64.S)执行前,栈上自顶向下依次是错误码、%rip、%cs、%rflags,这与内核初始化第二部分中对early_idt_handler_array的objdump反汇编观察完全吻合。

与初始化章节的呼应:早期 IDT 是怎么搭起来的

本篇的硬件理论,正好可以解释内核初始化第二部分中那几段关键代码的动机。

set_intr_gate宏定义在arch/x86/include/asm/desc.h:

#define set_intr_gate(n, addr) \ do { \ BUG_ON((unsigned)n > 0xFF); \ _set_gate(n, GATE_INTERRUPT, (void *)addr, 0, 0, \ __KERNEL_CS); \ _trace_set_gate(n, GATE_INTERRUPT, (void *)trace_##addr,\ 0, 0, __KERNEL_CS); \ } while (0)

BUG_ON正是本篇开头那个BUG_ON((unsigned)n > 0xFF)检查的真实出处。随后_set_gate调用pack_gate填充gate_desc结构,把 64 位处理程序地址拆成低 16 位(PTR_LOW)、中间 16 位(PTR_MIDDLE)和高 32 位(PTR_HIGH),分别写入offset_low、offset_middle、offset_high,并把段选择子设为内核代码段__KERNEL_CS、把ist和dpl置 0、把type设为GATE_INTERRUPT。这与本篇介绍的gate_struct64位域一一对应。

早期初始化只填充前NUM_EXCEPTION_VECTORS(即 32)个表项,是因为在初期设置阶段中断处于禁用状态;每个表项都指向early_idt_handler_array中生成的通用处理程序,它们先压入虚拟错误码与向量号,再跳转到early_idt_handler_common。至此,"IDT 长什么样、为什么这样填"就有了完整的答案。

总结

关于 Linux 内核中断和中断处理的第一部分到此结束。我们初步了解了与中断和异常相关的理论与初始化条件:

  • 中断的本质与 APIC(Local APIC / I/O APIC)的中断投递路径;
  • 向量号 0~255 的分配规则、0~31 保留异常区与 32~255 用户定义区;
  • 中断与异常的完整分类(外部/软件中断、故障/陷入/终止、可屏蔽/不可屏蔽)及 10 级中断优先级;
  • IDT 与 GDT 的异同、IDTR 寄存器与LIDT/SIDT指令、门条目的 16 字节位布局与gate_struct64结构体;
  • IST中断栈表机制、TSS 中的 7 个 IST 指针,以及 NMI、双重错误等特殊中断的入口初始化;
  • 内核栈与 per-CPU 中断栈的大小计算(THREAD_SIZE、IRQ_STACK_SIZE、KASAN 的影响)、irq_stack_union与 stack canary 的关系,以及setup_per_cpu_areas中irq_stack_ptr的初始化;
  • 中断发生时硬件的完整压栈/取门/iret恢复流程。

在下一部分,我们将更深入地走进中断和中断处理的真实实现。如果想从结构层面先快速浏览中断描述符表的全貌,可以参考本仓库 KernelStructures/linux-kernelstructure-1.md;中断主题的完整章节索引见 Interrupts/README.md。

  • 文档
  • 教程
  • 操作系统

【免费下载链接】linux-insides-zh

Linux 内核揭秘

项目地址:https://gitcode.com/gh_mirrors/li/linux-insides-zh
点击查看免费下载
上一篇:图像分类模型的傅里叶频率热图分析:Google Research frequency_analysis 模块实战指南
下一篇:tfjs-converter 夜间测试自动化:Cloud Scheduler + Cloud Functions + Cloud Build 流水线深度解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 16:04:28

30-seconds-of-code:使用 JavaScript 获取某月的第一天与最后一天

教程文档 【免费下载链接】30-seconds-of-code Coding articles to level up your development skills 项目地址&#xff1a; https://gitcode.com/gh_mirrors/30/30-seconds-of-code 点击查看 免费下载 导读 在原生 JavaScript 中处理日期经常让人头疼&#xff0c;但"获…

作者头像 李华
网站建设 2026/10/4 16:02:58

插件系统加载机制与排查:从plugin.json到激活失败

1. 从"plugins"这个标题说起&#xff1a;插件系统到底在解决什么问题"plugins"这个词单独拎出来&#xff0c;信息量其实非常有限。但结合热搜词里反复出现的cursor、plugin.json、TypeScript SDK、CLI&#xff0c;以及failed to load plugins web boot: 2 …

作者头像 李华
网站建设 2026/10/4 16:02:50

OpenShell完全指南:让Win10/Win11开始菜单回归经典与高效

Windows 11 的开始菜单越做越“Metro”&#xff0c;磁贴一排排铺开好看是好看&#xff0c;但我身边同事里有一半人装完系统第一件事就是装个第三方开始菜单。OpenShell 就是我现在最常用、也最愿意推荐的一个开源方案——它其实是当年 Classic Shell 的继任者&#xff0c;项目托…

作者头像 李华
网站建设 2026/10/4 16:00:06

Cursor插件开发全链路指南:从plugin.json到harness加载

1. 项目概述&#xff1a;从“plugins”这个词开始&#xff0c;我们到底在谈什么&#xff1f; “plugins”——这个词在开发者日常里出现频率高得有点扎眼。它不是某个具体工具、也不是某家公司的产品名&#xff0c;而是一个通用概念&#xff1a; 可插拔的、独立封装的功能扩展…

作者头像 李华