@@ -161,8 +161,8 @@ irq_entries_start
161161 * [[ patch V2 00_13
] x86_irq_64 Inline irq stack switching
] ( https://lore.kernel.org/all/[email protected] / ) 162162 * [ x86/entry: Convert system vectors to irq stack macro] ( https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=569dd8b4eb7ef666b467c41b8e8e4f2820d07f67 )
163163 * 对于用户程序被中断或者已经有中断正在 per-CPU 的中断栈上被处理的情况,直接调用` __common_interrupt() `
164- * 中断的是用户程序,用的应该是 trampoline stack
165- * 已经有中断正在 per-CPU 中断栈上被处理,则使用 * 当前内核栈*
164+ * 中断的是用户程序,第一站是 trampoline stack,但随即切换到进程内核栈上去处理中断
165+ * 已经有中断正在 per-CPU 中断栈上被处理,则使用 * 当前内核栈*
166166
167167## 异常栈
168168* 对于无需进行 privilege-level 变化的情况,比如异常发生时 CPU 运行在内核态
@@ -174,8 +174,147 @@ irq_entries_start
174174## Privilege-level 发生变化时的栈
175175* 对于 privilege-level 变化的情况,比如异常或中断发生时 CPU 运行在用户态,handler 要使用的栈的 segment selector 和 stack pointer 是从当前执行任务的 TSS 中获得的。
176176 * 对于 x86-64 Linux 这个栈由` cpu_tss_rw.x86_tss.sp0 ` 指示,也就是 CPU entry trampoline stack
177- * 对于中断,内核会随后切换到中断栈上去处理中断
178- * 对于异常,就接着在 trampoline stack 上去处理异常
177+ * 对于中断,内核会随后切换到中断栈上去处理中断(commit 569dd8b4eb7e 后有变化,见上面,也切换到进程内核栈上去处理中断)
178+ * 对于异常,随即切换到进程内核栈上去处理异常
179+
180+ ``` cpp
181+ /* Device interrupts common/spurious */
182+ DECLARE_IDTENTRY_IRQ (X86_TRAP_OTHER, common_interrupt);
183+
184+ #ifndef __ ASSEMBLY__ //对于 C 代码包含该宏
185+
186+ #define DECLARE_IDTENTRY_IRQ(vector, func) \
187+ DECLARE_IDTENTRY_ERRORCODE(vector, func)
188+
189+ #define DECLARE_IDTENTRY_ERRORCODE(vector, func) \
190+ asmlinkage void asm_ ##func(void); \
191+ asmlinkage void xen_asm_ ##func(void); \
192+ __ visible void func(struct pt_regs * regs, unsigned long error_code)
193+
194+ #else //对于汇编代码包含该宏
195+ /* Entries for common/spurious (device) interrupts * /
196+ #define DECLARE_IDTENTRY_IRQ(vector, func) \
197+ idtentry_irq vector func
198+ #endif
199+ ```
200+ * 中断处理函数的入口的汇编宏 `idtentry_irq`
201+ ```c
202+ /*
203+ * Interrupt entry/exit.
204+ *
205+ + The interrupt stubs push (vector) onto the stack, which is the error_code
206+ * position of idtentry exceptions, and jump to one of the two idtentry points
207+ * (common/spurious).
208+ *
209+ * common_interrupt is a hotpath, align it to a cache line
210+ */
211+ .macro idtentry_irq vector cfunc
212+ .p2align CONFIG_X86_L1_CACHE_SHIFT
213+ idtentry \vector asm_\cfunc \cfunc has_error_code=1
214+ .endm
215+
216+ .macro idtentry vector asmsym cfunc has_error_code:req
217+ SYM_CODE_START(\asmsym)
218+
219+ .if \vector == X86_TRAP_BP //vector = X86_TRAP_OTHER,进不来
220+ /* #BP advances %rip to the next instruction */
221+ UNWIND_HINT_IRET_REGS offset=\has_error_code*8 signal=0
222+ .else
223+ UNWIND_HINT_IRET_REGS offset=\has_error_code*8
224+ .endif
225+
226+ ENDBR
227+ ASM_CLAC
228+ cld
229+
230+ .if \has_error_code == 0
231+ pushq $-1 /* ORIG_RAX: no syscall to restart */
232+ .endif
233+
234+ .if \vector == X86_TRAP_BP //vector = X86_TRAP_OTHER,进不来
235+ /*
236+ * If coming from kernel space, create a 6-word gap to allow the
237+ * int3 handler to emulate a call instruction.
238+ */
239+ testb $3, CS-ORIG_RAX(%rsp)
240+ jnz .Lfrom_usermode_no_gap_\@
241+ .rept 6
242+ pushq 5*8(%rsp)
243+ .endr
244+ UNWIND_HINT_IRET_REGS offset=8
245+ .Lfrom_usermode_no_gap_\@:
246+ .endif
247+
248+ idtentry_body \cfunc \has_error_code
249+
250+ _ASM_NOKPROBE(\asmsym)
251+ SYM_CODE_END(\asmsym)
252+ .endm
253+
254+ /**
255+ * idtentry_body - Macro to emit code calling the C function
256+ * @cfunc: C function to be called
257+ * @has_error_code: Hardware pushed error code on stack
258+ */
259+ .macro idtentry_body cfunc has_error_code:req
260+
261+ /*
262+ * Call error_entry() and switch to the task stack if from userspace.
263+ *
264+ * When in XENPV, it is already in the task stack, and it can't fault
265+ * for native_iret() nor native_load_gs_index() since XENPV uses its
266+ * own pvops for IRET and load_gs_index(). And it doesn't need to
267+ * switch the CR3. So it can skip invoking error_entry().
268+ */
269+ ALTERNATIVE "call error_entry; movq %rax, %rsp", \
270+ "call xen_error_entry", X86_FEATURE_XENPV
271+
272+ ENCODE_FRAME_POINTER
273+ UNWIND_HINT_REGS
274+
275+ movq %rsp, %rdi /* pt_regs pointer into 1st argument*/
276+
277+ .if \has_error_code == 1
278+ movq ORIG_RAX(%rsp), %rsi /* get error code into 2nd argument*/
279+ movq $-1, ORIG_RAX(%rsp) /* no syscall to restart */
280+ .endif
281+
282+ call \cfunc //调用 common_interrupt
283+
284+ /* For some configurations \cfunc ends up being a noreturn. */
285+ REACHABLE
286+
287+ jmp error_return
288+ .endm
289+ ```
290+ * ` sync_regs() ` 帮忙找到进程内核栈,真正地将栈切换到进程内核栈在 ` call error_entry; movq %rax, %rsp `
291+ ``` cpp
292+ /*
293+ * Help handler running on a per-cpu (IST or entry trampoline) stack
294+ * to switch to the normal thread stack if the interrupted code was in
295+ * user mode. The actual stack switch is done in entry_64.S
296+ */
297+ asmlinkage __visible noinstr struct pt_regs * sync_regs(struct pt_regs * eregs)
298+ {
299+ struct pt_regs *regs = (struct pt_regs *)this_cpu_read(pcpu_hot.top_of_stack) - 1;
300+ if (regs != eregs)
301+ *regs = *eregs;
302+ return regs;
303+ }
304+ ```
305+ * `idtentry_body`里切换到进程内核栈,`sync_regs()` 的返回值即 `call error_entry` 的返回值,放在 `$rax`
306+ ```cpp
307+ call error_entry
308+ PUSH_AND_CLEAR_REGS save_ret=1 //寄存器压栈
309+ testb $3, CS+8(%rsp) //中断/异常发生在内核态 or 用户态?
310+ jz .Lerror_kernelspace //内核态不走下面
311+ /* Put us onto the real thread stack. */
312+ jmp sync_regs //帮忙找到进程内核栈,该函数的返回就是 error_entry 的返回
313+ ...
314+ .Lerror_kernelspace:
315+ ...
316+ movq %rax, %rsp //sync_regs() 的返回值即 error_entry 的返回值,真正地将栈切换到进程内核栈
317+ ```
179318
180319> When the processor performs a call to the exception- or interrupt-handler procedure:
181320> * If the handler procedure is going to be executed at a numerically lower privilege level, a stack switch occurs.
@@ -195,7 +334,7 @@ irq_entries_start
195334>
196335> -- SDM, Vol. 3A, 6.14.2 64-Bit Mode Stack Frame
197336
198- * 为什么异常处理发生 oops 时看到的的栈有时是` 0xfffffexxxxxxxxxx ` 有时是` 0xffffc90000000000 ~ ffffe8ffffffffff ` (对于 5 级页表是` 0xffa0000000000000 ~ 0xffd1ffffffffffff ` )?
337+ * 为什么异常处理发生 OOPs 时看到的的栈有时是` 0xfffffexxxxxxxxxx ` 有时是` 0xffffc90000000000 ~ ffffe8ffffffffff ` (对于 5 级页表是` 0xffa0000000000000 ~ 0xffd1ffffffffffff ` )?
199338 * ` 0xfffffexxxxxxxxxx ` 是 cpu_entry_area mapping 的范围,说明此时使用的是 CPU entry trampoline stack,异常发生时 CPU 在运行的是用户态的程序;
200339 * ` 0xffffc90000000000 ~ ffffe8ffffffffff ` 是 vmalloc/ioremap space 的范围,说明当前内核启用了` CONFIG_VMAP_STACK ` ,进程内核栈是通过` vmalloc ` 分配的,异常发生时处于内核态;
201340 * 对于一些出错的极端情况,甚至有可能看到异常在使用中断栈或者 IST 栈。
@@ -380,6 +519,17 @@ struct cea_exception_stacks {
380519};
381520```
382521
522+ ### 用户态发生 ` #MC ` 和 ` #DB ` 时的栈
523+ * 虽然 ` #MC ` 和 ` #DB ` 被 Linux 定义为使用 IST 栈,但发生在用户态时,会通过软件把它切换到进程内核栈上去处理
524+ ``` c
525+ # define DECLARE_IDTENTRY_MCE (vector, func ) \
526+ idtentry_mce_db vector asm_##func func
527+
528+ # define DECLARE_IDTENTRY_DEBUG (vector, func ) \
529+ idtentry_mce_db vector asm_##func func
530+ ```
531+ * ` idtentry_mce_db ` 会判断,如果是发生在用户态,用的是汇编宏 ` idtentry_body ` ,之前已经展示过了它是怎么切换栈的了
532+
383533## CPU Entry Trampoline Stack
384534* 出于安全的目的,trampoline stack 被引入以支持 x86 KAISER
385535 * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7f2590a110b837af5679d08fc25c6227c5a8c497
0 commit comments