c - Linux 内核模块中 x87 内联汇编中的操作数类型不匹配

标签 c linux-kernel kernel inline-assembly x87

我真的很想在 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 来使内置函数实际内联为fsqrtsqrtss,没有为将导致 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/

相关文章:

c - 如何使用c实现 'cat'命令的squeeze功能?

linux-kernel - 等待队列和竞争条件

linux - schedule() 什么时候返回?

如果值为负数,Linux 的 C 不会返回

c++ - 异常行为

linux - 如何在Linux内核模块中为虚拟输入设备(/dev/input/js3)创建event#设备?

linux - 确定被 oom-killer 杀死的进程的虚拟机大小

c - 使用 shm_open 和 mmap 将用户空间映射到内核空间内存

c - 如何在 vxworks 6.7 中设置 wdb 进行内核前调试?

c - 为什么我的开关盒不工作?/如何修复这些构建错误?