Dai un'occhiata a questo ramo: arch / x86 / kernel / fpu (che gestisce tutte le risorse specifiche per FPU di x86):
Leggi questo commento che può fornire la risposta:
link
/*
* FPU context switching strategies:
*
* Against popular belief, we don't do lazy FPU saves, due to the
* task migration complications it brings on SMP - we only do
* lazy FPU restores.
*
* 'lazy' is the traditional strategy, which is based on setting
* CR0::TS to 1 during context-switch (instead of doing a full
* restore of the FPU state), which causes the first FPU instruction
* after the context switch (whenever it is executed) to fault - at
* which point we lazily restore the FPU state into FPU registers.
*
* Tasks are of course under no obligation to execute FPU instructions,
* so it can easily happen that another context-switch occurs without
* a single FPU instruction being executed. If we eventually switch
* back to the original task (that still owns the FPU) then we have
* not only saved the restores along the way, but we also have the
* FPU ready to be used for the original task.
*
* 'lazy' is deprecated because it's almost never a performance win
* and it's much more complicated than 'eager'.
*
* 'eager' switching is by default on all CPUs, there we switch the FPU
* state during every context switch, regardless of whether the task
* has used FPU instructions in that time slice or not. This is done
* because modern FPU context saving instructions are able to optimize
* state saving and restoration in hardware: they can detect both
* unused and untouched FPU state and optimize accordingly.
Quindi, per ripetere ciò che è spiegato:
a. Modalità LAZY: FPU non viene ripristinata / salvata tutto il tempo, ma solo quando viene utilizzata, e l'utilizzo della FPU resetterà anche un flag in CR0: TS, quindi non è necessario rilevare l'utilizzo del registro FPU tutto il tempo . Ma questa modalità non è l'impostazione predefinita, poiché il risparmio di tempo / prestazioni avanzate non è significativo e l'algoritmo diventa molto complesso, aumentando così i costi di elaborazione.
b. Modalità EAGER: questa è la modalità predefinita. FPU è sempre salvato e ripristinato per ogni interruttore di contesto. Ma di nuovo c'è una funzionalità hardware che può rilevare se viene utilizzata la lunga catena di registri FPU - e qualunque sia l'uso, solo quel registro verrà salvato / ripristinato, e quindi è molto efficiente dal punto di vista dell'hardware.
Per fare questo non è un'impresa da poco, perché significava scrivere 208 patch nel 2015:
link
Le istruzioni per salvare tutte le FPU - XMM, MMX, SSE, SSE2 ecc. si chiamano FXSAVE, FNSAVE, FSAVE:
link
e il sovraccarico nel kernel linux è valutato come 87 cicli.
link
Questi metodi ottimizzati per il salvataggio si possono trovare anche nei commenti seguenti:
* When executing XSAVEOPT (or other optimized XSAVE instructions), if
* a processor implementation detects that an FPU state component is still
* (or is again) in its initialized state, it may clear the corresponding
* bit in the header.xfeatures field, and can skip the writeout of registers
* to the corresponding memory layout.
*
* This means that when the bit is zero, the state component might still contain
* some previous - non-initialized register state.
Per rilevare che il kernel è attivato dall'utilizzo della FPU, possiamo impostare breakpoint su fpstate_sanitize_xstate in KGDB, e lo stacktrace del kernel è come segue:
Thread 441 hit Breakpoint 1, fpstate_sanitize_xstate (fpu=0xffff8801e7a2ea80) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/fpu/xstate.c:111
111 {
#0 fpstate_sanitize_xstate (fpu=0xffff8801e7a2ea80) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/fpu/xstate.c:111
#1 0xffffffff8103b183 in copy_fpstate_to_sigframe (buf=0xffff8801e7a2ea80, buf_fx=0x7f73ad4fe3c0, size=<optimized out>) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/fpu/signal.c:178
#2 0xffffffff8102e207 in get_sigframe (frame_size=440, fpstate=0xffff880034dcbe10, regs=<optimized out>, ka=<optimized out>) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/signal.c:247
#3 0xffffffff8102e703 in __setup_rt_frame (regs=<optimized out>, set=<optimized out>, ksig=<optimized out>, sig=<optimized out>) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/signal.c:413
#4 setup_rt_frame (regs=<optimized out>, ksig=<optimized out>) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/signal.c:627
#5 handle_signal (regs=<optimized out>, ksig=<optimized out>) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/signal.c:671
#6 do_signal (regs=0xffff880034dcbf58) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/kernel/signal.c:714
#7 0xffffffff8100320c in exit_to_usermode_loop (regs=0xffff880034dcbf58, cached_flags=4) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/entry/common.c:248
#8 0xffffffff81003c6e in prepare_exit_to_usermode (regs=<optimized out>) at /build/linux-FvcHlK/linux-4.4.0/arch/x86/entry/common.c:283
Usando "info thread 441" (vedi sopra) e scoprirai che "Xorg" è il creatore del suddetto stacktrace, ma altrimenti, la maggior parte dei processi non usa FPU.
Da stacktrace, "get_sigframe ()" è la prima funzione che sembrava analizzare sull'utilizzo della FPU:
if (fpu->fpstate_active) {
unsigned long fx_aligned, math_size;
sp = fpu__alloc_mathframe(sp, 1, &fx_aligned, &math_size);
*fpstate = (struct _fpstate_32 __user *) sp;
if (copy_fpstate_to_sigframe(*fpstate, (void __user *)fx_aligned,
math_size) < 0)
return (void __user *) -1L;
}
Quindi essenzialmente ciò che sta accadendo qui è copiare le informazioni della FPU nel puntatore dello stack userspace (che è "sp").
Quindi, in sintesi, questa parte della logica di salvataggio / copia / ripristino della FPU viene attivata solo dopo l'utilizzo della FPU.