为什么我能够在 Linux 内核模块内执行浮点运算?
- 2024-10-21 09:14:00
- admin 原创
- 114
问题描述:
我在 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 处理器吗?
解决方案 1:
我以为你不能在 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架构状态与整数不同?
jmp
Linux 每次进入/退出内核时都必须保存/恢复整数状态。所有代码都需要使用整数寄存器(除了以而不是ret
(ret
修改)结尾的 FPU 计算的巨大直线块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 状态 + 控制字)。此外,MPXbnd0-4
寄存器与“FPU”合并在一起。此时,“FPU 状态”仅表示所有非整数寄存器。在我的 Skylake 上,dmesg
显示x86/fpu: Enabled xstate features 0x1f, context size is 960 bytes, using 'compacted' format.
请参阅了解 Linux 内核中的 FPU 使用情况;现代 Linux 默认不会对上下文切换执行惰性 FPU 上下文切换(仅对内核/用户转换执行惰性 FPU 上下文切换)。(但那篇文章解释了什么是惰性。)
大多数进程使用 SSE 来复制/清零编译器生成的代码中的小块内存,并且大多数库字符串/memcpy/memset 实现都使用 SSE/SSE2。此外,硬件支持的优化保存/恢复现在已成为现实(xsaveopt
/xrstor),因此如果某些/所有 FP 寄存器实际上没有被使用,“急切的”FPU 保存/恢复实际上可能做的工作更少。例如,如果 YMM 寄存器的低 128b 被清零,则仅保存它们,vzeroupper
以便 CPU 知道它们是干净的。(并在保存格式中仅用一位标记该事实。)
通过“急切”的上下文切换,FPU 指令始终保持启用状态,因此错误的内核代码可能随时破坏它们。
解决方案 2:
不要这样做!
在内核空间中,FPU 模式由于以下几个原因而被禁用:
它允许 Linux 在没有 FPU 的架构中运行
它避免在每次内核/用户空间转换时保存和恢复整个寄存器集(它可能会使上下文切换的时间加倍)
基本上所有的内核函数都使用整数来表示十进制数 - >你可能不需要浮点数
在 Linux 中,当内核空间以 FPU 模式运行时,抢占被禁用
浮点数是邪恶的,可能会产生非常糟糕的意外行为
如果您确实想使用 FP 数字(而您不应该这样做),那么您必须使用kernel_fpu_begin
和kernel_fpu_end
原语来避免破坏用户空间寄存器,并且您应该考虑处理 FP 数字时可能出现的所有问题(包括安全性)。
解决方案 3:
不确定这种看法从何而来。但内核与用户模式代码在同一个处理器上执行,因此可以访问相同的指令集。如果处理器可以执行浮点运算(直接或通过协处理器),那么内核也可以。
也许你正在考虑在软件中模拟浮点运算的情况。但即便如此,它仍可在内核中使用(除非以某种方式禁用)。
我很好奇,这种看法从何而来?也许我忽略了什么。
找到了这个。似乎是一个很好的解释。
解决方案 4:
操作系统内核可能只是在内核模式下关闭 FPU。
当FPU运行时,浮点运算时内核会打开FPU,然后关闭FPU。
但你不能打印它。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件