我在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();
在foo
函数中,我致电kernel_fpu_begin
和kernel_fpu_end
;但在调用kernel_fpu_begin
之前已调用foo
。这会导致未定义的行为吗?
此外,我是否应该在kernel_fpu_end
函数内调用foo
?我在kernel_fpu_end
调用后返回 double ,这意味着访问浮点寄存器是不安全的吗?
我最初的猜测是不使用kernel_fpu_begin
函数中的kernel_fpu_end
和foo
调用;但如果foo
将 double 强制转换为 unsigned ,那该怎么办呢 - 程序员不会知道使用kernel_fpu_begin
和kernel_fpu_end
在foo
之外?
答案 0 :(得分: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状态
答案 1 :(得分:0)
我正在评论asm/i387.h Linux源代码(版本3.2)与我理解的情况。
static inline void kernel_fpu_begin(void)
{
/* get thread_info structure for current thread */
struct thread_info *me = current_thread_info();
/* preempt_count is incremented by 1
* (preempt_count > 0 disables preemption,
* while preempt_count < 0 signifies a bug) */
preempt_disable();
/* check if FPU has been used before by this thread */
if (me->status & TS_USEDFPU)
/* save the FPU state to prevent clobbering of
* FPU registers, then reset the TS_USEDFPU flag */
__save_init_fpu(me->task);
else
/* clear the CR0.TS bit to prevent
* unnecessary FPU task context saving */
clts();
}
static inline void kernel_fpu_end(void)
{
/* set CR0.TS bit (signifying the processor switched
* to a new task) to enable FPU task context saving */
stts();
/* attempt to re-enable preemption
* (preempt_count is decremented by 1);
* reschedule thread if needed
* (thread will not be preempted if preempt_count != 0) */
preempt_enable();
}
FXSAVE
指令通常用于保存FPU状态。但是,我相信每次在同一个线程中调用kernel_fpu_begin
时,内存目标保持不变;不幸的是,这意味着FXSAVE
将覆盖以前保存的FPU状态。
因此我怀疑你不能安全地嵌套kernel_fpu_begin
来电。
我仍然无法理解的是FPU状态是如何恢复的,因为kernel_fpu_end
调用似乎没有执行FXRSTOR
指令。另外,如果我们不再使用FPU,为什么在CR0.TS
调用中设置了kernel_fpu_end
位?
答案 2 :(得分:-1)
是的,因为你定义了一些双变量&amp; foo
也返回double值;您必须在kernel_fpu_begin
also.
kernel_fpu_end
和foo
来电
Similar Problem也有这样的情况,您可以在不使用kernel_fpu_begin
和kernel_fpu_end
来电的情况下进行编码。