1.内核对进程的浮点数操作切换好保存机制
1.1 当新进程创建时,默认不使用浮点寄存器
int do_fork(unsigned long clone_flags, unsigned long stack_start,
struct pt_regs *regs, unsigned long stack_size)
{
copy_flags(clone_flags, p);
}
static inline void copy_flags(unsigned long clone_flags, struct task_struct *p)
{
unsigned long new_flags = p->flags;
new_flags &= ~(PF_SUPERPRIV | PF_USEDFPU | PF_VFORK);//<<<取消USEDFPU
new_flags |= PF_FORKNOEXEC;
if (!(clone_flags & CLONE_PTRACE))
p->ptrace = 0;
if (clone_flags & CLONE_VFORK)//<<<<<<<<<
new_flags |= PF_VFORK;
p->flags = new_flags;
}
1.2 当该进程被调度时,被禁止使用浮点指令
void __switch_to(struct task_struct *prev_p, struct task_struct *next_p)
{
unlazy_fpu(prev_p);
}
#define unlazy_fpu( tsk ) do { \
if ( tsk->flags & PF_USEDFPU ) /*如果使用了浮点寄存器*/\
save_init_fpu( tsk ); /*保存现场*/\
} while (0)
void save_init_fpu( struct task_struct *tsk )
{
if ( HAVE_FXSR ) {
asm volatile( "fxsave %0 ; fnclex"
: "=m" (tsk->thread.i387.fxsave) );
} else {
asm volatile( "fnsave %0 ; fwait"
: "=m" (tsk->thread.i387.fsave) );
}
tsk->flags &= ~PF_USEDFPU;//清除标志
stts();//设置ts
}
#define stts() write_cr0(8 | read_cr0())//ts位=1,fpu不可用
TS CR0的位3是任务已切換(Task Switched)标志。该标志用於推迟保存任务切換时的辅助运算器內容,直到新任务开始实际执行
辅助运算器指令。处理器在每次任务切換时都会设置该标志,並且在执行辅助运算器指令时测试该标志。
如果设置了TS标志並且CR0的EM标志为0,那麼在执行任何辅助运算器指令之前会產生一个设备不存在DNA(Device Not Available
)異常。如果设置了TS标志但沒有设置CR0的MP和EM标志,那么在执行辅助运算器指令WAIT/FWAIT之前不会產生设备不存在 異常
。如果设置了EM标志,那麼TS标志对辅助运算器指令的执行无影响。见表4-1所示。
在任务切換时,处理器並不自动保存辅助运算器的上下文,而是会设置TS标志。这个标志会使得处理器在执行新任务指令流的任
何时候遇到一条辅助运算器指令时 產生设备不存在異常。设备不存在異常的处理程式可使用CLTS指令清除TS标志,並且保存辅助
运算器的上下文。如果任务从沒有使用过辅助运算器,那麼相应 辅助运算器上下文就不用保存。
1.4 idle和init进程的ts设置
asmlinkage void __init start_kernel(void)
{
trap_init()
}
void __init trap_init(void)
{
set_trap_gate(7,&device_not_available);
cpu_init();
}
void __init cpu_init (void)
{
/*
* Force FPU initialization:
*/
current->flags &= ~PF_USEDFPU;
current->used_math = 0;
stts();
}
所以默认ts都是设置的
1.5用户进程如果执行浮点时的处理
用户进程执行浮点指令会触发DNA异常(device not available),异常号为7
linux/arch/i386/entry.S
ENTRY(device_not_available)//7
pushl $-1 # mark this as an int
SAVE_ALL
GET_CURRENT(%ebx)
pushl $ret_from_exception
movl %cr0,%eax
testl $0x4,%eax # EM (math emulation bit)
je SYMBOL_NAME(math_state_restore) #不模拟,协处理器存在,还原现场
pushl $0 # temporary storage for ORIG_EIP
call SYMBOL_NAME(math_emulate) #软件模拟
addl $4,%esp
ret
asmlinkage void math_state_restore(struct pt_regs regs)
{
__asm__ __volatile__("clts"); /* 允许浮点操作 Allow maths ops (or we recurse) */
if (current->used_math) {//已使用浮点操作
restore_fpu(current);//还原现场
} else {
init_fpu();//否则初始化
}
current->flags |= PF_USEDFPU; /* 正在使用浮点处理器 So we fnsave on switch_to() */
}
void restore_fpu( struct task_struct *tsk )
{
if ( HAVE_FXSR ) {
asm volatile( "fxrstor %0"
: : "m" (tsk->thread.i387.fxsave) );
} else {
asm volatile( "frstor %0"
: : "m" (tsk->thread.i387.fsave) );
}
}
void init_fpu(void)
{
__asm__("fninit");
if ( HAVE_FXSR )
load_mxcsr(0x1f80);
current->used_math = 1;//是否使用了浮点处理器
}
则现在进程可以使用浮点处理器了
当换出时,系统会自动保存现场,并置ts,见1.2
2.内核中的浮点处理器使用
从上面看出,如果内核使用浮点指令,则也可能会触发dna或破环用户的浮点栈,对当前的用户进程造成不必要的干扰,因此
需要透明地使用浮点处理器。
首先要判断用户使用过处理器且现场已回复,如果是则需要保护,然后再使用。
unlazy_fpu(current);//可能保存用户现场
int old=current->used_math;
__asm__ __volatile__("clts");//内核使用浮点指令
init_fpu();
使用浮点指令
stts();//使用完浮点指令
current->used_math=old;//还原
还要保证执行浮点指令时内核不能被抢占和中断。//不可重入
阅读(2729) | 评论(0) | 转发(1) |