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

Vil*_*ray 12 c linux x86 gcc linux-kernel

我正在使用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);
Run Code Online (Sandbox Code Playgroud)

编译的代码(这是没有预料到的)所以我插入了模块并检查了日志dmesg.日志显示:x: 7.

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

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

Ric*_*ico 7

不要那样做!

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

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

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


Pet*_*des 7

我以为你无法在Linux内核中执行浮点运算

您无法安全:无法使用kernel_fpu_begin()/ kernel_fpu_end()并不意味着FPU指令会出错(至少不在x86上).

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

编译器不知道是什么kernel_fpu_begin()意思,因此无法检查/警告编译到FPU-begin区域之外的FPU指令的代码.

可能存在调试模式,其中内核在kernel_fpu_begin/ end区域之外禁用SSE,x87和MMX指令,但这样做会更慢并且默认情况下不会完成.

但是有可能:设置CR0::TS = 1使x87指令失败,因此可以进行延迟的FPU上下文切换,并且SSE和AVX还有其他位.


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


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

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

但是内核代码通常会避免使用FPU,因此Linux在系统调用进入时保留FPU状态,仅在实际上下文切换到不同的用户空间进程之前保存kernel_fpu_begin.否则,在同一核心上返回相同的用户空间进程是很常见的,因此不需要恢复FPU状态,因为内核没有触及它.(如果内核任务确实修改了FPU状态,那么这就是腐败发生的地方.我认为这有两个方面:用户空间也可能破坏你的 FPU状态).

整数状态相当小,只有16x 64位寄存器+ RFLAGS和段寄存器.即使没有AVX,FPU状态也是两倍大:8x 80位x87寄存器,16x XMM或YMM,或32x ZMM寄存器(+ MXCSR,x87状态+控制字).MPX bnd0-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保存/恢复实际上可以做更少的工作.例如,如果它们被归零,只保存低128b的YMM寄存器,vzeroupper因此CPU知道它们是干净的.(并在保存格式中仅用一位标记该事实.)

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

  • 您引用的文章有点过时了。特别是,从内核中完全删除了对惰性模式的支持。所以默认的eager模式是现在唯一的模式。 (2认同)