17

我在 x86 CentOS 6.3(内核 v2.6.32)系统上运行。

我将以下函数编译成一个基本的字符驱动模块作为实验,以了解 Linux 内核如何对浮点操作做出反应。

static unsigned floatstuff(void){
    float x = 3.14;
    x *= 2.5;
    return x;
}

...

printk(KERN_INFO "x: %u", x);

编译的代码(这是意料之外的)所以我插入了模块并使用dmesg. 日志显示:x: 7

这看起来很奇怪;我以为你不能在 Linux 内核中执行浮点运算——除了一些例外,比如kernel_fpu_begin(). 模块是如何进行浮点运算的?

这是因为我使用的是 x86 处理器吗?

4

4 回答 4

15

我以为你不能在 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 retret修改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 上,dmesgx86/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 指令始终保持启用状态,因此糟糕的内核代码可能随时破坏它们。

于 2017-11-01T17:39:29.930 回答
7

不要那样做!

在内核空间中,FPU 模式由于以下几个原因而被禁用:

  • 它允许 Linux 在没有 FPU 的架构中运行
  • 它避免了在每次内核/用户空间转换时保存和恢复整个寄存器集(它可能会使上下文切换时间加倍)
  • 基本上所有的内核函数都使用整数来表示十进制数 -> 你可能不需要浮点数
  • 在 Linux 中,当内核空间在 FPU 模式下运行时,抢占被禁用
  • 浮点数是邪恶的,可能会产生非常糟糕的意外行为

如果您真的想使用 FP 编号(并且您不应该),您必须使用kernel_fpu_beginkernel_fpu_end原语以避免破坏用户空间寄存器,并且您应该考虑处理 FP 编号时所有可能的问题(包括安全性)。

于 2017-11-01T13:39:16.157 回答
2

不知道这种看法是从哪里来的。但是内核与用户模式代码在同一处理器上执行,因此可以访问相同的指令集。如果处理器可以进行浮点运算(直接或通过协处理器),内核也可以。

也许您正在考虑在软件中模拟浮点运算的情况。但即便如此,它也可以在内核中使用(好吧,除非以某种方式禁用)。

我很好奇,这种认知是从哪里来的?也许我错过了一些东西。

找到了这个。似乎是一个很好的解释。

于 2013-04-08T16:57:02.017 回答
1

操作系统内核可以简单地在内核模式下关闭 FPU。

在 FPU 操作时,在浮点操作时内核将打开 FPU,然后关闭 FPU。

但是你不能打印它。

于 2016-04-21T09:47:44.457 回答