公司动态
Linux配置IST栈
在Linux内核中ISTInterrupt Stack Table中断栈表的配置是一个系统性的过程核心涉及在任务状态段TSS中定义栈指针、在中断描述符表IDT中标记使用哪个IST栈以及由内核在初始化时分配物理内存并建立映射。IST栈的定义与分配IST栈是每个CPU核心专用的其大小和用途在内核启动时就已经预定好。根据x86_64架构的设计一个CPU最多可以有7个IST条目。Linux内核将这些硬件条目分配给了特定的关键异常ESTACK_DF用于#DF双重故障大小为PAGE_SIZE4K。ESTACK_NMI用于NMI不可屏蔽中断大小为PAGE_SIZE。ESTACK_DB用于#DB调试异常大小为PAGE_SIZE。但请注意为了支持嵌套内核实际上为它准备了两个物理栈实例。ESTACK_MCE用于#MC机器检查异常大小为PAGE_SIZE。此外对于像AMD SEV-ES这类特殊功能内核也会为其虚拟化异常#VC动态分配额外的IST栈。如何在IDT中为中断选择IST栈定义了栈还不够内核需要在中断门描述符中明确指示当某个中断发生时CPU应该切换到哪个IST栈。这个过程是通过IDT中断描述符表初始化代码完成的。在Linux内核源码中通常使用宏来定义IDT条目可以清晰地看到IST的指定。1. 标准中断不使用IST大部分硬件中断和普通异常不启用IST功能使用默认栈。代码中通常以INTG宏标记// 使用默认栈 (DEFAULT_STACK) INTG(X86_TRAP_DE, asm_exc_divide_error); // #DE 除法错误 INTG(X86_TRAP_GP, asm_exc_general_protection); // #GP 通用保护这种配置下bits.ist字段的值为DEFAULT_STACK(即0)。2. 关键异常使用IST对于NMI、#DF等关键异常则会使用ISTG宏来指定IST索引// ISTG 宏的第三个参数指定了 IST 索引 // 例如 NMI (不可屏蔽中断) ISTG(X86_TRAP_NMI, asm_exc_nmi, IST_INDEX_NMI); // 例如 #DF (双重故障) ISTG(X86_TRAP_DF, asm_exc_double_fault, IST_INDEX_DF);ISTG宏在定义中断门时会根据传入的IST_INDEX_*值设置bits.ist字段。CPU在处理该中断时看到这个非零的IST字段就会自动从TSS中对应的IST条目加载新的栈指针。实际配置过程cpu_init()的初始化实际的IST栈指针是在每个CPU初始化时通过cpu_init()函数写入TSS的。代码核心逻辑如下void cpu_init(void) { struct tss_struct *t per_cpu(cpu_tss_rw, cpu); // ... // 只有当 TSS 中的 IST 未被设置时才进行初始化 if (!t-x86_tss.ist[0]) { t-x86_tss.ist[DOUBLEFAULT_IST] __this_cpu_ist_top_va(DF); t-x86_tss.ist[NMI_IST] __this_cpu_ist_top_va(NMI); t-x86_tss.ist[DEBUG_IST] __this_cpu_ist_top_va(DB); t-x86_tss.ist[MCE_IST] __this_cpu_ist_top_va(MCE); // ... } }这段代码清晰展示了每个IST栈如DOUBLEFAULT_IST被赋予一个具体的栈顶虚拟地址然后写入当前CPU的TSS结构中。