【问题标题】:Calling kernel_fpu_begin twice before kernel_fpu_end在 kernel_fpu_end 之前调用 kernel_fpu_begin 两次
【发布时间】:2013-04-10 19:22:14
【问题描述】:

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

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

#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;但是在调用foo 之前已经调用了kernel_fpu_begin。这会导致未定义的行为吗?

此外,我什至应该在 foo 函数中调用 kernel_fpu_end 吗?我在kernel_fpu_end 调用之后返回了一个double,这意味着访问浮点寄存器是不安全的,对吧?

我最初的猜测是不要在foo 函数中使用kernel_fpu_beginkernel_fpu_end 调用;但是如果foodouble 转换返回到 unsigned 会怎样——程序员不知道在foo 之外使用kernel_fpu_beginkernel_fpu_end

【问题讨论】:

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


    【解决方案1】:

    简答:不,嵌套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 状态。

    长答案:正如您在&lt;asm/i387.h&gt; 中看到的实际实现,细节有点棘手。在较旧的内核(如您查看的 3.2 源代码)中,FPU 处理总是“惰性”的——内核希望避免重新加载 FPU 的开销,直到它真正需要它,因为线程可能会运行并再次被调度无需实际使用 FPU 或需要其 FPU 状态。所以kernel_fpu_end()只是设置了TS标志,这会导致FPU的下一次访问陷入陷阱并导致FPU状态被重新加载。希望我们实际上并没有足够的时间使用 FPU,从而降低整体成本。

    但是,如果您查看较新的内核(我相信是 3.7 或更新的内核),您会发现实际上所有这些都有第二条代码路径——“eager”FPU。这是因为较新的 CPU 具有“优化的”XSAVEOPT 指令,并且较新的用户空间更频繁地使用 FPU(用于 memcpy 中的 SSE 等)。 XSAVEOPT / XRSTOR 的成本更低,延迟优化实际上避免 FPU 重新加载的机会也更少,因此在新 CPU 上使用新内核时,kernel_fpu_end() 只是继续并恢复 FPU 状态。 (

    但是在“懒惰”和“急切”的 FPU 模式下,task_struct 中仍然只有一个插槽来保存 FPU 状态,因此嵌套 kernel_fpu_begin() 最终会破坏用户空间的 FPU 状态。

    【讨论】:

    • 理论上,如果我生成了一个新的内核线程,调用kernel_fpu_begin 是否安全,因为状态会保存到不同的task_struct 结构中?
    • 当然,在不同线程的上下文中,您可以再次调用kernel_fpu_begin。这并不是真正的嵌套用法,因为您正在创建一个新的执行线程。 (类比是,当且仅当您有两个不同的线程时,您可以在同一个互斥锁上执行两次mutex_lock()
    • 顺便问一下,在kernel_fpu_begin你知道为什么TS标志只有在没有设置TS_USEDFPU的情况下才会被清除;如果 TS_USEDFPU 未设置,是否只是假定已清除 TS 标志?
    • TS_USEDFPU 被设置意味着“这个任务使用了 FPU”。我认为您键入的内容与您的意思相反,但想法是正确的:如果 TS_USEDFPU is 已设置,则该线程已使用 FPU 并且尚未被调度,因此 TS 不能被设置(因为 TS=1 意味着 FPU 指令会导致异常,内核必须已经处理并清除了 TS)。
    【解决方案2】:

    我正在评论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,为什么在 kernel_fpu_end 调用中设置了 CR0.TS 位?

    【讨论】:

      【解决方案3】:

      是的,正如您定义的一些双精度变量 & foo 也返回双精度值;你必须使用kernel_fpu_beginkernel_fpu_endfooalso.之外调用


      Similar Problem 也有这种情况,在某些情况下您可以在不使用 kernel_fpu_beginkernel_fpu_end 调用的情况下进行编码。

      【讨论】:

      • 这是错误的。如果嵌套 kernel_fpu_begin,将会破坏 FPU 状态。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-20
      • 1970-01-01
      • 1970-01-01
      • 2015-09-08
      • 1970-01-01
      • 2017-02-26
      • 1970-01-01
      相关资源
      最近更新 更多