公司动态
IRQ 中断不用 add rsp, 8 跳过错误码,而 Page Fault需要
问题的本质在于中断/异常是否由CPU自动向栈中压入错误码以及这个错误码对后续处理是否必须。核心原因CPU是否自动压栈错误码IRQ外部硬件中断和 Page Fault缺页异常在CPU处理流程上有一个关键区别IRQ中断不会由CPU自动压入错误码栈上只有返回地址RIPCSRFLAGS因此处理完IRQ后直接执行iret返回即可不需要跳过任何额外数据。Page Fault#PF中断向量14会由CPU自动压入一个错误码Error Code到栈中通常位于返回地址之后。因此必须在返回前通过add rsp, 8等方式将栈指针推进跳过这个错误码否则iret会因栈布局错乱而出错。详细对比特性IRQ外部中断Page Fault#PF触发源硬件设备如定时器、键盘内存访问异常缺页、权限违规是否压入错误码❌ 否✅ 是32位/64位均压入错误码内容不适用包含访问类型、页表层级、是否为写操作等栈上布局RIP→CS→RFLAGSRIP→CS→RFLAGS→错误码返回前需要add rsp, 8❌ 不需要✅ 必须跳过错误码才能iret是否必须处理通常由操作系统调度必须处理否则触发三重故障错误码Error Code的结构#PF压入的错误码在 x86_64 下是一个 64 位的值低 32 位有效其位字段含义如下位含义0P保护异常0 页不存在1 权限违规1W写操作0 读1 写2U用户模式0 内核模式1 用户模式3RSVD保留页表项1 使用了保留位4I/D指令获取1 指令获取5其他标志某些 CPU 扩展内核在#PF处理函数中通过读取这个错误码决定如何修复分配新页、检查权限、或将异常转发给应用程序如SIGSEGV。为什么 IRQ 不用跳而 #PF 必须跳因为 IRQ 栈上根本不存在需要跳过的数据。如果错误地执行add rsp, 8栈指针会指向错误的位置iret时拿到的RIP是错的CPU 会跳转到随机地址几乎立刻触发#GP通用保护异常造成系统挂起或崩溃。而对于#PF如果不跳过错误码iret会错误地尝试将错误码当作RIP来使用导致栈展开错乱系统崩溃。因此跳过错误码是正确返回的前置条件。中断/异常处理的标准流程x86_64CPU 压栈取决于异常类型无错误码仅压入RIP、CS、RFLAGS有错误码再压入错误码位于RFLAGS之后找到中断门 / 陷阱门IDT 中的门描述符如果是中断门RFLAGS.IF被清零屏蔽后续中断如果是陷阱门IF不清零切换到内核栈IST或TSS中的RSP0执行内核处理函数读取错误码处理逻辑返回有错误码add rsp, 8跳过错误码无错误码直接iret恢复RFLAGS和用户态上下文补充说明IRETvsIRETQ在 x86_64 中始终使用iretq64 位版本返回。iret本身会根据当前模式自动选择操作数大小但在长模式下建议显式使用iretq。中断嵌套某些 IRQ如定时器中断也可能在内核处理中触发#PF此时栈上会叠加多层错误码需要逐层处理。MSR 与 RFLAGSSYSCALL会在进入内核时通过IA32_FMASK自动屏蔽RFLAGS中的IF但硬件中断不涉及 MSR而是通过RFLAGS.IF的位控制。总结IRQ 不需要add rsp, 8因为它根本没有被压入错误码而#PF必须这样做因为 CPU强制压入了错误码跳过它是正确返回的必要前提。