@@ -161,8 +161,8 @@ irq_entries_start
161
161
* [[ patch V2 00_13
] x86_irq_64 Inline irq stack switching
] ( https://lore.kernel.org/all/[email protected] / )
162
162
* [ x86/entry: Convert system vectors to irq stack macro] ( https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=569dd8b4eb7ef666b467c41b8e8e4f2820d07f67 )
163
163
* 对于用户程序被中断或者已经有中断正在 per-CPU 的中断栈上被处理的情况,直接调用` __common_interrupt() `
164
- * 中断的是用户程序,用的应该是 trampoline stack
165
- * 已经有中断正在 per-CPU 中断栈上被处理,则使用 * 当前内核栈*
164
+ * 中断的是用户程序,第一站是 trampoline stack,但随即切换到进程内核栈上去处理中断
165
+ * 已经有中断正在 per-CPU 中断栈上被处理,则使用 * 当前内核栈*
166
166
167
167
## 异常栈
168
168
* 对于无需进行 privilege-level 变化的情况,比如异常发生时 CPU 运行在内核态
@@ -174,8 +174,147 @@ irq_entries_start
174
174
## Privilege-level 发生变化时的栈
175
175
* 对于 privilege-level 变化的情况,比如异常或中断发生时 CPU 运行在用户态,handler 要使用的栈的 segment selector 和 stack pointer 是从当前执行任务的 TSS 中获得的。
176
176
* 对于 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
+ ```
179
318
180
319
> When the processor performs a call to the exception - or interrupt-handler procedure:
181
320
> * 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
195
334
>
196
335
> -- SDM, Vol. 3A, 6.14.2 64-Bit Mode Stack Frame
197
336
198
- * 为什么异常处理发生 oops 时看到的的栈有时是` 0xfffffexxxxxxxxxx ` 有时是` 0xffffc90000000000 ~ ffffe8ffffffffff ` (对于 5 级页表是` 0xffa0000000000000 ~ 0xffd1ffffffffffff ` )?
337
+ * 为什么异常处理发生 OOPs 时看到的的栈有时是` 0xfffffexxxxxxxxxx ` 有时是` 0xffffc90000000000 ~ ffffe8ffffffffff ` (对于 5 级页表是` 0xffa0000000000000 ~ 0xffd1ffffffffffff ` )?
199
338
* ` 0xfffffexxxxxxxxxx ` 是 cpu_entry_area mapping 的范围,说明此时使用的是 CPU entry trampoline stack,异常发生时 CPU 在运行的是用户态的程序;
200
339
* ` 0xffffc90000000000 ~ ffffe8ffffffffff ` 是 vmalloc/ioremap space 的范围,说明当前内核启用了` CONFIG_VMAP_STACK ` ,进程内核栈是通过` vmalloc ` 分配的,异常发生时处于内核态;
201
340
* 对于一些出错的极端情况,甚至有可能看到异常在使用中断栈或者 IST 栈。
@@ -380,6 +519,17 @@ struct cea_exception_stacks {
380
519
};
381
520
```
382
521
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
+
383
533
## CPU Entry Trampoline Stack
384
534
* 出于安全的目的,trampoline stack 被引入以支持 x86 KAISER
385
535
* https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7f2590a110b837af5679d08fc25c6227c5a8c497
0 commit comments