在kernel_fpu_end之前调用kernel_fpu_begin两次

时间:2013-04-10 19:22:14

标签: c linux linux-kernel linux-device-driver kernel-module

我在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();

foo函数中,我致电kernel_fpu_beginkernel_fpu_end;但在调用kernel_fpu_begin之前已调用foo。这会导致未定义的行为吗?

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

我最初的猜测是不使用kernel_fpu_begin函数中的kernel_fpu_endfoo调用;但如果foo double 强制转换为 unsigned ,那该怎么办呢 - 程序员不会知道使用kernel_fpu_beginkernel_fpu_endfoo之外?

3 个答案:

答案 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_endfoo来电

Similar Problem也有这样的情况,您可以在不使用kernel_fpu_beginkernel_fpu_end来电的情况下进行编码。