C 调用内核\u fpu\u在内核\u fpu\u结束之前开始两次
我正在使用中的C 调用内核\u fpu\u在内核\u fpu\u结束之前开始两次,c,linux,linux-kernel,linux-device-driver,kernel-module,C,Linux,Linux Kernel,Linux Device Driver,Kernel Module,我正在使用中的kernel\u fpu\u begin和kernel\u fpu\u end函数为Linux内核模块中的一些简单浮点算法保护fpu寄存器状态 我很好奇在调用kernel\u fpu\u end函数之前调用kernel\u fpu\u begin函数两次的行为,反之亦然。例如: #include <asm/i387.h> double foo(unsigned num){ kernel_fpu_begin(); double x = 3.14;
kernel\u fpu\u begin
和kernel\u fpu\u end
函数为Linux内核模块中的一些简单浮点算法保护fpu寄存器状态
我很好奇在调用kernel\u fpu\u end
函数之前调用kernel\u fpu\u 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(无符号num){
内核_fpu_begin();
双x=3.14;
x+=num;
内核_fpu_end();
返回x;
}
...
内核_fpu_begin();
双y=1.23;
无符号z=42;
y-=foo(z);
内核_fpu_end();
在foo
函数中,我调用kernel\u fpu\u begin
和kernel\u fpu\u end
;但是在调用foo
之前已经调用了kernel\u fpu\u begin
。这会导致未定义的行为吗
此外,我是否应该在foo
函数中调用kernel\u fpu\u end
?在调用kernel\u fpu\u end
之后,我返回一个double,这意味着访问浮点寄存器是不安全的,对吗
我最初的猜测是不要在
foo
函数中使用kernel\u fpu\u begin
和kernel\u fpu\u end
调用;但是如果foo
返回了double转换为unsigned——程序员不知道使用kernel\u fpu\u begin
和kernel\u fpu\u end
之外的foo
?是的,正如您定义的一些双变量一样&foo
也在返回双值;您必须使用kernel\u fpu\u begin
和kernel\u fpu\u end
调用foo
还有一些例子,在这些例子中,您可以不使用
kernel\u fpu\u begin
和kernel\u fpu\u end
调用来编写代码。我正在用我理解的发生的事情来评论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();
}
静态内联void内核\u fpu\u开始(void)
{
/*获取当前线程的线程信息结构*/
struct thread_info*me=current_thread_info();
/*抢占计数增加1
*(抢占\u计数>0将禁用抢占,
*而抢占(U计数<0表示存在错误)*/
抢占禁用();
/*检查此线程以前是否使用过FPU*/
如果(me->status&T\u USEDFPU)
/*保存FPU状态以防止碰撞
*FPU寄存器,然后重置TS_USEDFPU标志*/
__保存初始fpu(me->task);
其他的
/*清除CR0.TS位以防止
*不必要的FPU任务上下文保存*/
clts();
}
静态内联void内核\u fpu\u结束(void)
{
/*设置CR0.TS位(表示处理器已切换
*添加到新任务)以启用FPU任务上下文保存*/
stts();
/*尝试重新启用抢占
*(抢占计数递减1);
*如果需要,重新调度线程
*(如果抢占计数!=0,线程将不会被抢占)*/
抢占启用();
}
FXSAVE
指令通常用于保存FPU状态。但是,我相信每次在同一线程中调用kernel\u fpu\u begin
时,内存目标都保持不变;不幸的是,这意味着FXSAVE
将覆盖以前保存的FPU状态
因此,我怀疑您不能安全地嵌套kernel\u fpu\u begin
调用
但我仍然无法理解FPU状态是如何恢复的,因为
内核FPU\u end
调用似乎没有执行FXRSTOR
指令。另外,如果不再使用fpu,为什么在内核fpu\u end
调用中设置CR0.TS
位?简短回答:不,嵌套内核fpu\u begin()
调用是不正确的,它将导致用户空间fpu状态损坏
中等回答:这不起作用,因为kernel\u fpu\u begin()
使用当前线程的struct task\u struct
保存fpu状态(task\u struct
有一个依赖于体系结构的成员thread
,在x86上,thread.fpu
保存线程的fpu状态),并执行第二个kernel\u\u\u begin()
将覆盖原始保存状态。然后执行kernel\u fpu\u end()
将恢复错误的fpu状态
详细回答:正如您在
中看到的实际实现,细节有点棘手。在较旧的内核(如您看到的3.2源代码)中,FPU处理总是“懒惰的”--内核希望避免在真正需要FPU之前重新加载FPU的开销,因为线程可能会再次运行并被调度,而不需要实际使用FPU或需要其FPU状态。因此kernel\u FPU\u end()
只需设置TS标志,这会导致FPU的下一次访问陷井并导致FPU状态重新加载。希望我们实际使用FPU的时间不会太长,这样总体上会更便宜
然而,如果你看一看较新的内核(我相信是3.7或更高版本),你会发现实际上还有第二个代码路径——急切的FPU。这是因为较新的CPU有“优化”的XSAVEOPT指令,而较新的用户空间更经常地使用FPU(对于memcpy中的SSE,等等)。XSAVEOPT/XRSTOR的成本更低,而且延迟优化实际上避免FPU重新加载的可能性也更小,因此在新CPU上安装新内核时,kernel\u FPU\u end()
只需继续并恢复FPU状态(
然而,在“惰性”和“急切”FPU模式下,任务结构中仍然只有一个插槽保存FPU状态,因此嵌套内核\u FPU\u begin()
将最终损坏用户空间的FPU状态