我以为你不能在 Linux 内核中执行浮点运算
您不能安全:使用失败kernel_fpu_begin()
/kernel_fpu_end()
并不意味着 FPU 指令会出错(至少在 x86 上不会)。
相反,它会默默地破坏用户空间的 FPU 状态。这是不好的; 不要那样做。
编译器不知道是什么kernel_fpu_begin()
意思,因此它无法检查/警告编译为 FPU 开始区域之外的 FPU 指令的代码。
可能有一种调试模式,其中内核确实禁用kernel_fpu_begin
/end
区域之外的 SSE、x87 和 MMX 指令,但这会更慢并且默认情况下不会这样做。
但是有可能:设置CR0::TS = 1
会使 x87 指令出错,因此可以进行惰性 FPU 上下文切换,并且 SSE 和 AVX 还有其他位。
有很多错误的内核代码会导致严重的问题。这只是众多之一。在 C 中,您几乎总是知道何时使用浮点数(除非拼写错误导致1.
常量或实际编译的上下文中的某些内容)。
为什么 FP 架构状态与整数不同?
Linux 在任何时候进入/退出内核时都必须保存/恢复整数状态。所有代码都需要使用整数寄存器(除了一个巨大的 FPU 计算直线块,它以 a 结尾jmp
而不是 a ret
(ret
修改rsp
)。)
但是内核代码通常会避免 FPU,因此 Linux 在系统调用进入时不会保存 FPU 状态,仅在实际上下文切换到不同的用户空间进程或 on之前保存kernel_fpu_begin
。否则,通常会返回到同一个内核上的同一个用户空间进程,因此不需要恢复 FPU 状态,因为内核没有触及它。(如果内核任务确实修改了 FPU 状态,就会发生损坏。我认为这是双向的:用户空间也可能损坏您的FPU 状态)。
整数状态相当小,只有 16 个 64 位寄存器 + RFLAGS 和段寄存器。即使没有 AVX,FPU 状态也是两倍多:8 个 80 位 x87 寄存器和 16 个 XMM 或 YMM,或 32 个 ZMM 寄存器(+ MXCSR 和 x87 状态 + 控制字)。MPXbnd0-4
寄存器也与“FPU”集中在一起。此时“FPU 状态”仅表示所有非整数寄存器。在我的 Skylake 上,dmesg
说x86/fpu: Enabled xstate features 0x1f, context size is 960 bytes, using 'compacted' format.
请参阅了解 Linux 内核中的 FPU 使用;现代 Linux 默认情况下不会为上下文切换执行惰性 FPU 上下文切换(仅用于内核/用户转换)。(但那篇文章解释了 Lazy 是什么。)
大多数进程使用 SSE 在编译器生成的代码中复制/归零小块内存,并且大多数库字符串/memcpy/memset 实现使用 SSE/SSE2。此外,硬件支持的优化保存/恢复现在是一件事(xsaveopt
/xrstor),因此如果某些/所有 FP 寄存器实际上没有被使用,“急切”的 FPU 保存/恢复实际上可能会做更少的工作。例如,如果它们被清零,则只保存 YMM 寄存器的低 128b,vzeroupper
以便 CPU 知道它们是干净的。(并在保存格式中仅用一位标记该事实。)
通过“急切”的上下文切换,FPU 指令始终保持启用状态,因此糟糕的内核代码可能随时破坏它们。