我真的很想在 Linux 内核模块中使用浮点运算,只是为了它。我不想做任何花哨的事情,只需使用 x87 trig 指令和/或 sqrt 指令,然后将结果分配给一个变量。就是这样。到目前为止,我已经尝试过:
float sqroot(float arg){
float returnValue;
asm(
"fld %1\n"
"fsqrt\n"
"fst %0"
:"=r"(returnValue)
: "r"(arg)
);
return returnValue;
}
这惨遭失败并产生以下错误:
Error: operand type mismatch for `fld'
Error: operand type mismatch for `fst'
我们将不胜感激。
最佳答案
从内核模块使用 x87 将“工作”,但会悄悄破坏用户空间 x87/MMX 状态。 Why am I able to perform floating point operations inside a Linux kernel module?
您需要 kernel_fpu_begin()
/kernel_fpu_end()
来确保安全。
不是从内联 asm 加载/存储,而是在 x87 寄存器堆栈的顶部请求输入并产生输出,并让编译器在需要时发出加载/存储指令。编译器已经知道如何做到这一点,您只需要对 sqrt
指令本身使用内联汇编,您可以通过这种方式向编译器描述:
static inline
float sqroot(float arg) {
asm("fsqrt" : "+t"(arg) );
return arg;
}
(请参阅编译器生成的 asm on the Godbolt compiler explorer)
寄存器约束必须告诉 block 使用浮点寄存器。
或者完全避免内联 asm,使用可以内联的 GNU C 内置
您需要使用-fno-math-errno
来使内置函数实际内联为fsqrt
或sqrtss
,没有为将导致 NaN 的输入回退到 调用 sqrtf
。
static inline
float sqroot_builtin(float arg) {
return __builtin_sqrtf(arg);
}
对于 x86-64,我们得到 sqrtss %xmm0, %xmm0
/ret
而对于 i386,我们得到 fld
/fsqrt
/返回
。 (参见上面的 Godbolt 链接)。恒定传播通过 __builtin_sqrt
和其他优化工作。
编辑:结合@iwillnotexist-idontexist 的观点(重新双重加载)。
此外,如果是我,我会在声明中添加 static inline
并将其放入头文件中。这将使编译器能够更智能地管理寄存器并避免堆栈帧开销。
(我也很想在整个过程中将 float
更改为 double
。否则,您将放弃实际浮点指令中使用的额外精度。尽管如果您最终经常将值存储为 float
,则会有一个额外的 cvtpd2ps
指令。OTOH,如果您将参数传递给 printf
,例如,这实际上避免了 cvtps2pd
。)
但是 Linux 内核 kprintf
无论如何都没有 double
的转换。
如果使用 -mfpmath=387
(32 位代码的默认设置)编译,在内联后值将保留在 80 位 x87 寄存器中。但是,是的,对于使用 64 位默认值 -mfpmath=sse
的 64 位代码,这将导致在加载回 XMM 寄存器时四舍五入为 float
。
kernel_fpu_begin()
保存完整的 FPU 状态,避免使用 SSE 寄存器并且仅使用 x87 不会使它或最终的 FPU 恢复在返回用户空间时更便宜。
关于c - Linux 内核模块中 x87 内联汇编中的操作数类型不匹配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35280254/