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

我正在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内核中执行浮点操作 – 保存一些exception,比如kernel_fpu_begin() 。 模块是如何执行浮点运算的?

这是因为我在x86处理器上吗?

我以为你不能在Linux内核中执行浮点操作

你不能安全地 :使用kernel_fpu_begin() / kernel_fpu_end()失败并不意味着FPU指令将会kernel_fpu_end()至少不是x86)。

相反,它会默默地破坏用户空间的FPU状态。 这不好; 不要这样做。

编译器不知道kernel_fpu_begin()是什么意思,所以它不能检查/警告编译到FPU开始区域以外的FPU指令的代码。

可能有一个调试模式,内核会禁用kernel_fpu_begin / end区域之外的SSE,x87和MMX指令,但是速度较慢,默认情况下不会执行。

但有可能:设置CR0::TS = 1使得x87指令发生故障,所以懒惰的FPU上下文切换是可能的,并且SSE和AVX还有其他位。


错误的内核代码有很多方法会导致严重的问题。 这只是其中之一。 在C语言中,你几乎总是知道什么时候使用浮点数(除非一个错字导致了一个常量或者在实际编译的上下文中的东西)。


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

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

但是内核代码一般都会避免FPU,所以Linux在系统调用时将FPU状态保留为未保存状态,仅在实际环境切换到不同的用户空间进程或kernel_fpu_begin 。 否则,通常在同一个内核上返回相同的用户空间进程,所以FPU状态不需要被恢复,因为内核没有触及它。 (如果内核任务实际上修改了FPU状态,这就是腐败发生的地方,我认为这是两种方式:用户空间也可能破坏你的 FPU状态)。

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

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

大多数进程使用SSE在编译器生成的代码中复制/归零小块内存,大多数库字符串/ memcpy / memset实现使用SSE / SSE2。 另外,支持硬件优化的保存/恢复是现在的事情( xsaveopt / xrstor),所以如果某些/所有的FP寄存器没有被实际使用,那么“急切”的FPU保存/恢复实际上可能会减少工作量。 例如只保存YMM寄存器的低vzeroupper ,如果它们用vzeroupper归零,那么CPU知道它们是干净的。 (并且在保存格式中只标记一点)

随着“热切”的上下文切换,FPU指令始终保持启用状态,所以坏的内核代码可能会随时破坏它们。

不要这样做!

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

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

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

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

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

我很好奇,这种感觉来自哪里? 也许我错过了一些东西。

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

OS内核可以简单地在内核模式下关闭FPU。

在FPU操作的同时,浮点运算内核将会打开FPU,然后关闭FPU。

但是你不能打印它。