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

我将以下功能编译为准字符驱动程序模块,以进行实验,以了解Linux内核如何对浮点运算作出 react 。

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处理器上吗?

最佳答案



您不能安全地 :不使用kernel_fpu_begin()/kernel_fpu_end()并不意味着FPU指令将发生故障(至少不是在x86上)。

相反,它将以静默方式破坏用户空间的FPU状态。这不好;不要那样做。

编译器不知道kernel_fpu_begin()的含义,因此它无法检查/警告有关在FPU开始区域之外编译为FPU指令的代码。

可能存在一种 Debug模式,其中内核确实在kernel_fpu_begin/end区域之外禁用了SSE,x87和MMX指令,但这会比较慢,并且默认情况下不会完成。

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

有问题的内核代码有很多方法可以引起严重的问题。这只是众多之一。在C语言中,您几乎总是知道何时使用浮点数(除非输入错误会导致1.常数或上下文中实际编译的东西)。

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

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

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

整数状态非常小,只有16个64位寄存器+ RFLAGS和段寄存器。即使没有AVX,FPU的状态也要大两倍:8个80位x87寄存器,16个XMM或YMM或32个ZMM寄存器(+ MXCSR和x87状态+控制字)。 MPX bnd0-4寄存器也与“FPU”集中在一起。此时,“FPU状态”仅表示所有非整数寄存器。在我的Skylake上,dmesg表示x86/fpu: Enabled xstate features 0x1f, context size is 960 bytes, using 'compacted' format.
参见Understanding FPU usage in linux kernel;现代Linux默认情况下不对上下文切换执行惰性FPU上下文切换(仅用于内核/用户转换)。 (但是那篇文章解释了什么是懒惰。)

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

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

09-25 22:19