Vil*_*ray 2 c linux kernel-module linux-device-driver linux-kernel
我正在使用asm/i387.h中的kernel_fpu_begin和kernel_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_begin和kernel_fpu_end; 但是kernel_fpu_begin在打电话之前已经打过电话了foo.这会导致未定义的行为吗?
此外,我是否应该kernel_fpu_end在foo函数内部调用?我在调用后返回一个doublekernel_fpu_end,这意味着访问浮点寄存器是不安全的吗?
我最初的猜测是不要在函数内部使用kernel_fpu_begin和kernel_fpu_end调用foo; 但是如果foo将双重转换返回到unsigned而不是 - 程序员不会知道使用kernel_fpu_begin和kernel_fpu_end在外面foo?
简短的回答:不,嵌套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状态.
| 归档时间: |
|
| 查看次数: |
2327 次 |
| 最近记录: |