为什么我能够在 Linux 内核模块中执行浮点运算?

Why am I able to perform floating point operations inside a Linux kernel module?

提问人:Vilhelm Gray 提问时间:4/9/2013 最后编辑:Vilhelm Gray 更新时间:11/3/2017 访问量:12241

问:

我在 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);

代码编译(这是没想到的),所以我插入了模块并用 .日志显示:。dmesgx: 7

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

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

C GCC Linux 内核 x86

评论

3赞 Mysticial 4/9/2013
为什么内核不能执行浮点运算?
0赞 Daniel Kamil Kozar 4/9/2013
你为什么这么惊讶?毕竟,内核模块只是 CPU 执行的另一段代码。只要它能执行你扔给它的操作码,你就没问题。
13赞 Daniel Fischer 4/9/2013
此外,很有可能在编译过程中执行算术运算,剩下的只是一个 .return 7;
4赞 jim mcnamara 4/9/2013
这已经在这里回答了:stackoverflow.com/questions/13886338/......这个问题更像是一个不太正确的陈述,答案是解释性的。你可以在内核中做FP。
9赞 Peter Cordes 4/23/2016
kernel_fpu_begin() / end 是必需的,以免破坏用户空间 FPU 状态。如果没有它,您可以在内核中执行 FP,但会损坏当前进程的 FPU 状态。Linux 执行延迟 FPU 上下文保存,因为某些进程根本不使用 FPU 或 SSE 寄存器。(不过,越来越多的进程确实使用 SSE。

答:

2赞 Ziffusion 4/9/2013 #1

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

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

我很好奇,这种看法从何而来?也许我错过了什么。

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

评论

0赞 Vilhelm Gray 4/9/2013
也许我的问题有点误导。我知道 FPU 可以执行这些浮点指令(即机器代码本身与系统无关),但我对如何让我的 C 代码进行编译而没有关于未定义符号的 GCC 错误感到困惑,例如当我编译内核模块时。我怀疑这只是 GCC,具体取决于内核排除的库中的帮助程序例程,那么我该如何解决这个问题,以便生成正确的机器代码——毕竟处理器支持浮点。__fixunssfsi
0赞 Vilhelm Gray 4/9/2013
让我补充一点,我知道浮点寄存器没有被保存;我并不特别关心这种破坏用户空间程序,因为我纯粹是在试验代码以更好地理解行为。
2赞 Vilhelm Gray 4/9/2013
我想通了;我需要将此编译器标志传递给 GCC:。-mhard-float
2赞 Basile Starynkevitch 4/9/2013
关键是浮点状态没有从内核内部正确保存和恢复,例如在调度任务时(它仅从应用程序内部的角度保存)。
1赞 Rajeev Kumar 4/21/2016 #2

操作系统内核可能只是在内核模式下关闭 FPU。

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

但是你不能打印它。

评论

0赞 zx485 4/21/2016
好答案:看这个 SO 答案。它解释说,由于性能原因,FPU 可能被禁用。
8赞 ocirocir 11/1/2017 #3

别这样!

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

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

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

19赞 Peter Cordes 11/2/2017 #4

我以为你不能在 Linux 内核中执行浮点运算

你不能安全地:不使用/并不意味着 FPU 指令会出错(至少在 x86 上不会)。kernel_fpu_begin()kernel_fpu_end()

相反,它会以静默方式破坏用户空间的 FPU 状态。这很糟糕;别这样。

编译器不知道是什么意思,因此它无法检查/警告编译为 FPU 开始区域之外的 FPU 指令的代码。kernel_fpu_begin()

可能存在一种调试模式,内核会禁用 / 区域之外的 SSE、x87 和 MMX 指令,但这会更慢,并且默认情况下不会这样做。kernel_fpu_beginend

不过,这是可能的:设置会使 x87 指令出错,因此延迟 FPU 上下文切换是可能的,并且 SSE 和 AVX 还有其他位。CR0::TS = 1


有缺陷的内核代码有很多种方式会导致严重的问题。这只是其中之一。在 C 语言中,你几乎总是知道何时使用浮点数(除非拼写错误导致常量或实际编译的上下文中的某些内容)。1.


为什么 FP 架构状态与整数不同?

Linux 必须在进入/退出内核时保存/恢复整数状态。所有代码都需要使用整数寄存器(除了以 a 而不是 a 结尾的 FPU 计算的巨大直线块(修改)。jmpretretrsp

但是内核代码通常会避开 FPU,因此 Linux 在系统调用输入时不保存 FPU 状态,仅在实际上下文切换到不同的用户空间进程之前保存。否则,通常会返回到同一内核上的同一用户空间进程,因此不需要恢复 FPU 状态,因为内核没有触及它。(如果内核任务确实修改了 FPU 状态,则会发生损坏。我认为这是双向的:用户空间也可能破坏您的 FPU 状态)。kernel_fpu_begin

整数状态相当小,只有 16 个 64 位寄存器 + RFLAGS 和段 regs。即使没有 AVX,FPU 状态也要大两倍以上:8 个 80 位 x87 寄存器、16 个 XMM 或 YMM 或 32 个 ZMM 寄存器(+ MXCSR 和 x87 状态 + 控制字)。此外,MPX 寄存器与“FPU”混为一谈。此时,“FPU 状态”仅表示所有非整数寄存器。在我的Skylake上,说bnd0-4dmesgx86/fpu: Enabled xstate features 0x1f, context size is 960 bytes, using 'compacted' format.

请参阅了解 Linux 内核中的 FPU 用法;默认情况下,现代 Linux 不会为上下文切换执行延迟 FPU 上下文切换(仅适用于内核/用户转换)。(但那篇文章解释了什么是懒惰。

大多数进程使用 SSE 来复制/清零编译器生成的代码中的小内存块,并且大多数库 string/memcpy/memset 实现使用 SSE/SSE2。此外,硬件支持的优化保存/恢复现在是一回事(xsaveopt / xrstor),因此,如果某些/全部 FP 寄存器实际上没有被使用,“急切”的 FPU 保存/恢复实际上可能会减少工作量。例如,如果 YMM 寄存器被归零,则只保存低 128b 的寄存器,以便 CPU 知道它们是干净的。(并在保存格式中仅用一位标记该事实。vzeroupper

通过“急切”的上下文切换,FPU 指令始终保持启用状态,因此糟糕的内核代码随时可能损坏它们。

评论

3赞 Hadi Brais 5/1/2018
你引用的文章有点过时了。特别是,对延迟模式的支持已从内核中完全删除。因此,默认的 eager 模式是现在唯一的模式。
0赞 Hadi Brais 5/1/2018
https://patchwork.kernel.org/patch/9362413/