当前位置: 首页 > 面试题库 >

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

虞正业
2023-03-14
问题内容

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


问题答案:

我以为您不能在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架构状态为何与整数不同?

Linux必须在每次进入/退出内核时保存/恢复整数状态。所有代码都需要使用整数寄存器(FPU计算的巨型直线块以a jmp而不是a
retret修饰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上dmesgx86/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寄存器的低128b,vzeroupper以便CPU知道它们是干净的。(并以保存格式中的一位来标记该事实。)

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



 类似资料:
  • 问题内容: 我是Linux内核的新手。 我知道有两个上下文 1.进程上下文,在用户空间或内核空间中运行(例如:作为系统调用的一部分) 2.中断上下文 在什么情况下运行内核线程(与ex:flush任务无关的任何用户线程)? 除了Linux内核中的Process和Interrupt上下文之外,还有其他上下文吗? 问题答案: 内核线程在内核空间中的进程上下文中运行。虽然也有一些内核线程可以处理中断。它们

  • 问题内容: 我正在阅读Robert Love的“ Linux内核开发”,并且遇到了以下段落: 无需(轻松)使用浮点数 当用户空间进程使用浮点指令时,内核将管理从整数到浮点模式的转换。内核使用浮点指令时必须执行的操作因体系结构而异,但是内核通常会捕获陷阱,然后启动从整数模式到浮点模式的转换。 与用户空间不同,内核不具有对浮点​​的无缝支持的奢侈,因为它无法轻易地陷入陷阱。在内核内部使用浮点数需要手动

  • 问题内容: 我在暑期研究中从事内核工作。我们希望在特定的RTT计算中对TCP进行修改。我想做的是将tcp_input.c中的功能之一的分辨率替换为由动态加载的内核模块提供的功能。我认为这将改善我们开发和分发修改的速度。 我感兴趣的函数被声明为静态的,但是我用非静态函数重新编译了内核,并由EXPORT_SYMBOL导出。这意味着该功能现在可供内核的其他模块/部分访问。我已经通过“ cat / pro

  • MANAGING THE LINUX KERNEL AND LOADABLE KERNEL MODULES 所有操作系统至少由两个主要组件组成。其中第一个也是最重要的是内核。 内核位于操作系统的中心,控制着操作系统所做的一切,包括管理内存,控制 CPU,甚至控制用户在屏幕上看到的内容。操作系统的第二个元素通常被称为用户区域,几乎包括其他所有元素。 内核被设计成一个受保护或特权的区域,只能由 roo

  • 主要内容:initramfe虚拟文件系统GRUB 加载了内核之后,内核首先会再进行二次系统的自检,而不一定使用 BIOS 检测的硬件信息。这时内核终于开始替代 BIOS 接管 Linux 的启动过程了。 内核完成再次系统自检之后,开始采用动态的方式加载每个硬件的模块,这个动态模块大家可以想象成硬件的驱动(默认 Linux 硬件的驱动是不需要手工安装的,如果是重要的功能,则会直接编译到内核当中;如果是非重要的功能,比如硬件驱动会编译为模块

  • 主要内容:内核模块保存位置与模块保存文件,内核模块的查看,内核模块的添加与删除Linux 的内核会在启动过程中自动检验和加载硬件与文件系统的驱动。一般这些驱动都是用模块的形式加载的,使用模块的形式保存驱动,可以不直接把驱动放入内核,有利于控制内核大小。 模块的全称是 动态可加载内核模块,它是具有独立功能的程序,可以被单独编译,但不能独立运行。模块是为内核或其他模块提供功能的代码集合。这些模块可以是 Linux 源码中自带的,也可以是由硬件厂商开发的(可以想象成驱动)。不过内