- 文档
- 教程
- 操作系统
【免费下载链接】linux-insides-zh
Linux 内核揭秘
本文是《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 APICI/O APIC
Local APIC存在于每个 CPU 核心中,负责处理特定于 CPU 的中断配置,常被用于管理来自 APIC 时钟(APIC-timer)、热敏元件以及其他与 I/O 设备相连设备的中断。
I/O APIC则提供多核处理器的中断管理,它被用来在所有的 CPU 核心中分发外部中断。Local APIC 与 I/O APIC 的更多细节将在本系列后续部分展开。
中断可以在任何时间发生。当一个中断发生时,操作系统必须立刻处理它。所谓"处理一个中断",意味着内核必须确保以下步骤顺序:
- 内核必须暂停执行当前进程(取代当前任务);
- 内核必须搜索中断处理程序并转交控制权(执行中断处理程序);
- 中断处理程序结束之后,被中断的进程能够恢复执行。
当然,实际的中断处理过程中还交织着大量错综复杂的细节,但上面三条就是这个过程的基本骨架。
中断向量号与中断描述符表(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; ... ... ... #endifgate_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_STACKNMI_STACKDEBUG_STACKMCE_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 #endifKASan是一个运行时内存调试器。所以:
- 如果
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 wrmsrinitial_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的基址。
中断发生时的硬件行为
当一个中断或异常发生时,硬件会执行一系列固定动作:
- 新的
ss选择器被强制置为NULL,且ss选择器的rpl域被设置为新的cpl; - 旧的
ss、rsp、寄存器标志(RFLAGS)、cs、rip被压入新栈; - 在 64 位模式下,中断栈帧大小固定为 8 字节,因此可以得到下面的栈布局:
+---------------+ | | | SS | 40 | RSP | 32 | RFLAGS | 24 | CS | 16 | RIP | 8 | Error code | 0 | | +---------------+- 如果在中断门中
IST域不是0,就把IST读到rsp中; - 如果该中断向量关联了错误码,再把错误码压入栈;如果没有错误码,就压入一个虚拟错误码——这一步是为了确保栈的一致性;
- 接下来从门描述符中把段选择器域加载到
CS寄存器,并通过验证第21位(即 GDT 中L位)的值来确认目标代码是 64 位代码段; - 最后从门描述符中把偏移域加载到
rip,rip就是中断处理函数的入口指针; - 中断处理函数开始执行;执行结束后,必须通过
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 内核揭秘
相关推荐
Linux 内核揭秘:中断与中断处理入门(Part 1)——从硬件事件到 IDT 与中断栈
Linux 内核揭秘:中断与中断处理入门(Part 1)——从硬件事件到 IDT 与中断栈 导读 中断(interrupt)是 Linux 内核与外部世界交互的
Linux 内核中断与中断处理(一):从中断理论到 IDT 与 x86_64 中断栈
Linux 内核中断与中断处理(一):从中断理论到 IDT 与 x86_64 中断栈 本文是 linux insides 仓库中「Interrupts and
文档教程操作系统如何用Pyfa打造零风险的EVE舰船配置方案
如何用Pyfa打造零风险的EVE舰船配置方案 在EVE Online的宇宙中,每一次舰船配置错误都可能意味着数百万ISK的损失和任务失败的挫折。Pyfa作为一款
文档教程操作系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考