我正在使用kernel_fpu_begin
and kernel_fpu_end
功能于asm/i387.h保护 Linux 内核模块内一些简单浮点运算的 FPU 寄存器状态。
我很好奇调用的行为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();
In the foo
函数,我称之为kernel_fpu_begin
and kernel_fpu_end
; but kernel_fpu_begin
在调用之前已经被调用foo
。这会导致未定义的行为吗?
此外,我是否应该打电话给kernel_fpu_end
在 - 的里面foo
功能?我返回一个double之后kernel_fpu_end
调用,这意味着访问浮点寄存器是不安全的,对吗?
我最初的猜测是不使用kernel_fpu_begin
and kernel_fpu_end
内部调用foo
功能;但如果foo
返回了double投射到unsigned相反——程序员不知道如何使用kernel_fpu_begin
and 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 状态即可。 (
然而,在“lazy”和“eager”FPU 模式下,FPU 中仍然只有一个插槽。task_struct
保存FPU状态,所以嵌套kernel_fpu_begin()
最终会破坏用户空间的 FPU 状态。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)