在kernel_fpu_end之前调用kernel_fpu_begin两次

Vil*_*ray 2 c linux kernel-module linux-device-driver linux-kernel

我正在使用asm/i387.h中kernel_fpu_beginkernel_fpu_end函数来保护FPU寄存器状态,以便在Linux内核模块中进行一些简单的浮点运算.

我很好奇在kernel_fpu_begin函数之前调用函数两次的行为,kernel_fpu_end反之亦然.例如:

#include <asm/i387.h>

double foo(unsigned num){
    kernel_fpu_begin();

    double x = 3.14;
    x += num;

    kernel_fpu_end();

    return x;
}

...

kernel_fpu_begin();

double y = 1.23;
unsigned z = 42;
y -= foo(z);

kernel_fpu_end();
Run Code Online (Sandbox Code Playgroud)

foo功能,我打电话kernel_fpu_beginkernel_fpu_end; 但是kernel_fpu_begin在打电话之前已经打过电话了foo.这会导致未定义的行为吗?

此外,我是否应该kernel_fpu_endfoo函数内部调用?我在调用后返回一个doublekernel_fpu_end,这意味着访问浮点寄存器是不安全的吗?

我最初的猜测是不要在函数内部使用kernel_fpu_beginkernel_fpu_end调用foo; 但是如果foo双重转换返回到unsigned而不是 - 程序员不会知道使用kernel_fpu_beginkernel_fpu_end在外面foo

Rol*_*and 5

简短的回答:不,嵌套kernel_fpu_begin()调用是不正确的,这将导致用户空间FPU状态被破坏.

中等答案:这不起作用,因为kernel_fpu_begin()使用当前线程struct task_struct来保存FPU状态(task_struct具有依赖thread于体系结构的成员,并且在x86上,thread.fpu保持线程的FPU状态),并且执行一秒kernel_fpu_begin()将覆盖原始保存状态.然后执行kernel_fpu_end()将最终恢复错误的FPU状态.

答案很长:正如你看到的实际实现<asm/i387.h>,细节有点棘手.在较旧的内核中(如你所看到的3.2源代码),FPU处理总是"懒惰" - 内核希望避免重新加载FPU的开销,直到它确实需要它为止,因为线程可能会运行并再次被调度没有实际使用FPU或需要其FPU状态.因此,kernel_fpu_end()只需设置TS标志,这将导致FPU的下一次访问陷阱并导致重新加载FPU状态.希望我们实际上没有足够的时间使用FPU来使整体更便宜.

但是,如果你看一下更新的内核(3.7或者更新,我相信),你会发现所有这些实际上都有第二个代码路径 - "渴望"的FPU.这是因为较新的CPU具有"优化的"XSAVEOPT指令,较新的用户空间更频繁地使用FPU(对于memcpy中的SSE等).XSAVEOPT/XRSTOR的成本较低,实际上避免FPU重新加载的延迟优化的可能性也较小,因此在新CPU上使用新内核时,kernel_fpu_end()只需继续并恢复FPU状态.(

然而,在"懒惰"和"急切"FPU模式中,仍然只有一个插槽task_struct用于保存FPU状态,因此嵌套kernel_fpu_begin()最终会破坏用户空间的FPU状态.